awalterschulze / goderive

Derives and generates mundane golang functions that you do not want to maintain yourself
Apache License 2.0
1.22k stars 45 forks source link

put function argument last in higher order functions #31

Open rogpeppe opened 6 years ago

rogpeppe commented 6 years ago

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.

awalterschulze commented 6 years ago

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.

rogpeppe commented 6 years ago

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.

awalterschulze commented 6 years ago

Good points. I'll really think about it.

awalterschulze commented 6 years ago

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.