Complex Scala Output Type with Lambda Expressions

I am working on DSL for an experimental library that I create in Scala, and I came across some unpleasant features of Scala type output, as it relates to lambda expression arguments that don't seem to be discussed in Scala's Programming book.

In my library, I have the Effect [-T] property, which is used to represent temporary modifiers that can be applied to an object of type T. I have an myEffects object that has a method called + = that takes an argument of type Effect [PlayerCharacter] . Finally, I have a general method when [T], which is used to construct conditional effects, taking a conditional expression and another effect as an argument. The signature is as follows:

def when[T](condition : T => Boolean) (effect : Effect[T]) : Effect[T] 

When I call the when method with the specified signature, passing the result to the + = method, it cannot infer the type of the argument in the lambda expression.

 myEffects += when(_.hpLow()) (applyModifierEffect) //<-- Compiler error 

If I combine the when arguments into a single parameter list, Scala is able to infer the type of the lambda expression simply.

 def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T] /* Snip */ myEffects += when(_.hpLow(), applyModifierEffect) //This works fine! 

It also works if I completely remove the second parameter.

 def when[T](condition : T => Boolean) : Effect[T] /* Snip */ myEffects += when(_.hpLow()) //This works too! 

However, for aesthetic reasons, I really want the arguments to be passed to the when method as separate parameter lists.

My understanding from section 16.10 Programming in Scala is that the compiler first checks to see if the type of the method is known, and if so, it uses this to output the expected types of arguments. In this case, the external method call is + =, which takes an argument of type Effect [PlayerCharacter]. Since the return type when [T] is the Effect [T] and the method to which the result is passed expects an argument of type Effect [PlayerCharacter], he can conclude that T is PlayerCharacter, and therefore the lambda expression type is passed as the first argument " when "is PlayerCharacter => Boolean. This seems to work when arguments are provided in one parameter list, so why breaking arguments in two parameter lists breaks it?

+4
source share
2 answers

I'm relatively new to Scala myself, and I don't have much detailed technical knowledge on how type inference works. So better take it with salt.

I think the difference is that the compiler has a problem proving that the two T same in condition : T => Boolean and effect : Effect[T] in the version with two parameters.

I believe that when you have several parameter lists (since Scala sees this as a method definition that returns a function that uses the following parameter list), the compiler considers the parameter list one at a time, and not all together, as in the version of the list of single parameters.

So in this case:

 def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T] /* Snip */ myEffects += when(_.hpLow(), applyModifierEffect) //This works fine! 

The applyModifierEffect type and the required parameter type myEffects += can help limit the parameter type _.hpLow() ; all T must be PlayerCharacter . But in the following:

 myEffects += when(_.hpLow()) (applyModifierEffect) 

The compiler must independently determine the type when(_.hpLow()) so that it can check whether it really applies to applyModifierEffect . And _.hpLow() does not provide enough information for the compiler to infer that it is when[PlayerCharacter](_.hpLow()) , so it does not know that the return type is a function like Effect[PlayerCharacter] => Effect[PlayerCharacter] , so he does not know that this is true for applying this function in this context. I assume that type inference simply does not connect the dots and does not find out that there is only one type that avoids type errors.

And as for another case that works:

 def when[T](condition : T => Boolean) : Effect[T] /* Snip */ myEffects += when(_.hpLow()) //This works too! 

Here, the when return type and its parameter type are more directly related, without going through the parameter type and return type of the extra function created by currying. Since myEffects += requires an Effect[PlayerCharacter] , T must be PlayerCharacter , which has an hpLow method, and the compiler is complete.

I hope someone more knowledgeable can correct me in detail, or indicate that I generally bark on the wrong tree!

+2
source

I am a little confused because, in my opinion, none of the versions you say should work, and I cannot get them to work.

Type inferences work from left to right from one list of parameters (not a parameter) to the next. A typical example is the foldLeft method in collections (for example, Seq [A])

 def foldLeft[B] (z: B)(op: (B, A) => B): B 

Type z will make B known, so op can be written without specifying B (or A, which is known from the beginning, enter the type parameter). If the procedure was written as

 def foldLeft[B](z: B, op: (B,A) => B): B 

or

 def foldLeft[B](op: (B,A) => B)(z: B): B 

this would not work, and you would need to make sure that the type op is clear or pass B explicity when you call foldLeft .

In your case, I think that the most enjoyable for reading equivalent would be to make when the Effect method (or make it look like an implicit conversion), after which you will write

 Effects += applyModifierEffect when (_.hpLow()) 

As you noted that the effect is contravariant, the when signature is not allowed for the Effect method (due to T => Boolean , the function is contravariant in its first type of parameter, and the condition appears as a parameter, so in the contravariant position two contravariants form a covariant), but this can still be done with implicit

 object Effect { implicit def withWhen[T](e: Effect[T]) = new {def when(condition: T => Boolean) = ...} } 
+2
source

All Articles