2

I am composing function literals, though unlike most examples I've seen I'm starting with a multi-argument function that is then curried.

I have:

//types
case class Thing1(v: Double)
case class Thing2(v: Double)
case class Thing3(v: Double)
type Multiplier = Double

//functions
val f1 = (t: Thing1, m: Multiplier) => Thing2(m * t.v)
val f2 = (t: Thing2) => Thing3(t.v)

I want to compose f1 and f2 to get a combined function

Thing1 => (Multiplier => Thing3)

As expected, the following doesn't compile:

val fcomposed1 = f1.curried.andThen(f2) // does not compile

By experimentation, I was able to work out that the following does compile and has the right signature for fcomposed:

val fcomposed2 = f1.curried(_:Thing1).andThen(f2)   

I've read various sources like What are all the uses of an underscore in Scala? and possibly relevant Why does Scala apply thunks automatically, sometimes? but unfortunately I still cannot work out exactly step-by-step what is happening here and why it works.

Furthermore, I would expect the above separated into two expressions to work identically to fcomposed2, however instead the second does not compile:

val f1partial = f1.curried(_:Thing1)
val fcomposed3 = f1partial.andThen(f2) // does not compile - same error as fcomposed1

Looks like f1partial returns the same signature as f1.curried, which makes me wonder further how the earlier fcomposed2 works.

Could someone please explain both behaviours step by step?

theStrawMan
  • 235
  • 2
  • 9

1 Answers1

3

Here, the _ is acting as syntactical sugar for a lambda expression, but at a level you might not expect.

f1.curried has type Thing1 => Multiplier => Thing2

f1.curried(_:Thing1) is the same as { x: Thing1 => f1.curried(x) }. Since the result of f1.curried(x) has type Multiplier => Thing2, the final type of the whole expression is still Thing1 => Multiplier => Thing2. So it is not valid to call andThen(f2) on the result (f1partial) because the input type of function f2 (Thing2) is not the same as the output of the previous function (Multiplier => Thing2).

By contrast, f1.curried(_:Thing1).andThen(f2) expands to { x: Thing1 => f1.curried(x).andThen(f2) }. f1.curried(x) evaluates to type Multiplier => Thing2, like stated earlier, so you can call andThen(f2) on that, resulting in a Multiplier => Thing3. So then the entire expression evaluates to a Thing1 => Multiplier => Thing3

Perhaps it's more clear if you think about the differences between these two expressions:

val fcomposed1 = { x: Thing1 => f1.curried(x).andThen(f2) } // valid
val fcomposed2 = { x: Thing1 => f1.curried(x) }.andThen(f2) // error
Joe K
  • 18,204
  • 2
  • 36
  • 58
  • Thanks, this looks like the answer though I'm going to have to read a few more times to really grasp it! PS: There's a missing closing curly brace in the second paragraph, the code block after 'expands to'. – theStrawMan Jun 16 '17 at 01:45
  • The answer showing how the expressions expand and the final two lines clearly explaining the ultimate difference are excellent thanks. I do still find it a little strange though that Scala _does_ expand the two expressions (fcomposed2 and fcomposed3, ref my question) differently - that's something I might need more reading/thinking to understand. – theStrawMan Jun 16 '17 at 02:03
  • Extra note: Once I understood what was happening here with underscore lambda expressions (not partial application after all), I found [this](https://stackoverflow.com/questions/7673545/usage-of-in-scala-lambda-functions) and [this](https://stackoverflow.com/questions/4422016/scala-underscore-minimal-function) question worthwhile reading. – theStrawMan Jun 29 '17 at 05:21