goderive icon indicating copy to clipboard operation
goderive copied to clipboard

put function argument last in higher order functions

Open rogpeppe opened this issue 8 years ago • 4 comments

When writing deriveFmap or deriveFilter, it's often nice to write the predicate function inline. Since that can often take multiple arguments, the code reads more nicely when the function comes second and the slice comes first, so it's clear at first glace what slice is being operated on before starting to read the function itself.

For example:

 items := deriveFmap(otherItems, func(i OtherItem) Item {
      return OtherItem{
          Foo: i.Foo,
          Bar: i.Bar,
      }
 })

rather than:

 items := deriveFmap(func(i OtherItem) Item {
      return OtherItem{
          Foo: i.Foo,
          Bar: i.Bar,
      }
 }, otherItems), 

Alternatively, the goderive code could allow both forms and generate the expected function signature based on the argument types that are passed in.

rogpeppe avatar Aug 25 '17 08:08 rogpeppe

I would prefer not to change the parameter order now. And allowing both forms feels wrong. I got this form from haskell and it feels natural to me. https://wiki.haskell.org/Functor But maybe you want to change the issue title, to be fmap specific (or not), and see if more users feel like you do.

awalterschulze avatar Aug 25 '17 09:08 awalterschulze

In Haskell, it makes sense as it's common to curry the fmap call with its function argument. Go doesn't have currying, so that argument doesn't apply here. Also, in Haskell, functions are often only a few characters long, but in Go they're usually at least a couple of lines.

See the strings package, for example - all the functions that take function arguments put the function argument last in the parameter list, and I suspect this is the reason.

I don't think this is particularly specific to fmap - it applies to any function that takes only a single function argument.

rogpeppe avatar Aug 25 '17 10:08 rogpeppe

Good points. I'll really think about it.

awalterschulze avatar Aug 25 '17 10:08 awalterschulze

So here is an example that shows that its not always possible to distinguish which parameter we are mapping over which.

I am getting this example from monadic error handling. I just decided not to implement compose using fmap and join, but it does not mean that its not currently possible.

func deriveFmap(f func(b) (c, error), g func(a) (b, error)) func(a) (func() (c, error), error)

If types a, b and c are the same then we have:

func deriveFmap(f func(a) (a, error), g func(a) (a, error)) func(a) (func() (a, error), error)

Then its impossible to tell whether we are mapping f over g or g over f if we support both ways.

I am not saying no. I am just saying we need to be careful.

awalterschulze avatar Aug 26 '17 17:08 awalterschulze