4
votes

I`m experimenting with Scala and slightly confused by type inference problem.

Given the definition below, which compiles successfully:

case class SequentialHistory[+E](events: Seq[E])

trait Event

case class SampleEvent(value: String) extends Event

def addEvent[E, E2 >: E](event: E)(history:SequentialHistory[E2]):
 SequentialHistory[E2] = history.copy(events = event +: history.events)

I would like to understand why type inference not working properly and compilation of:

val hist = SequentialHistory(Seq.empty[Event])
//hist: SequentialHistory[Event] = SequentialHistory(List())

val histWithEvent = addEvent(SampleEvent("Does not compile"))(hist)
//error

result in compiler error:

Error:(21, 62) type mismatch;
               found   : SequentialHistory[Event]
               required: SequentialHistory[SampleEvent]
               addEvent(SampleEvent("Does not compile"))(hist)
                              ^

but, if I swap parameter lists in addEvent method definition:

def addEvent2[E, E2 >: E](history: SequentialHistory[E2])(event: E):
 SequentialHistory[E2] = history.copy(events = event +: history.events)

that would solve the problem, types inferred correctly and snippet below compiles:

val hist2 = SequentialHistory(Seq.empty[Event])
//hist2: SequentialHistory[Event] = SequentialHistory(List())

val histWithEvent2 = addEvent2(hist)(SampleEvent("Compiles"))
//histWithEvent1: SequentialHistory[Event] =
// SequentialHistory(List(SampleEvent1(Compiles)))

Why Scala compiler can`t infer types correctly in first version of addEvent?

1

1 Answers

1
votes

I can't explain it with references to the compiler code or quotes from the Scala specification, but here's my reasoning.

When your function has multiple parameter lists, it's "curried", so it's a function from the first argument that returns another function, etc. So if you try to apply it just to one argument, all types will be inferred:

scala> val partial = addEvent(SampleEvent(""))(_)
partial: SequentialHistory[SampleEvent] => SequentialHistory[SampleEvent] = <function1>

Now you can't pass the second argument with a wider type. What happens here is eta expansion. But another way round it won't work:

scala> val partial = addEvent2(hist)(_)
<console>:14: error: missing parameter type for expanded function ((x$1) => addEvent2(hist)(x$1))
       val partial = addEvent2(hist)(_)

Here E2 is fixed by the hist argument, but compiler can't infer E. But if you provide it both arguments, it sees that their types conform to the restrictions. I think in this sense the second definition is the same as defining it like def addEvent2[E, E2 >: E](event: E, history: SequentialHistory[E2]).