alxest
alxest

Reputation: 21

Scala supertype of call-by-name and call-by-value

I want below code to be type-checked.

val f: Int => String = x => "a"
val g: (=> Int) => String = x => "b"
def t(h: ???): String = h(42)
t(f)
t(g)

What should be put in "???"? I have read this link and tried it.

t[Int => String, (=> Int) => String].common
res4s: reflect.runtime.universe.TypeTag[=> Int with Int => String] = TypeTag[=> Int with Int => String]

So I put "=> Int with Int => String" in the ???, but t(g) does not type-check. I have tried "(=> Int with Int) => String" but t(f) does not type-check.

So the question is,

  1. What is the meaning of "=> Int with Int => String" and why t(g) does not type-check

  2. What is the meaning of "(=> Int with Int) => String" and why t(f) does not type-check

  3. What should be put in ???

Thanks a lot.

Upvotes: 2

Views: 132

Answers (2)

Odomontois
Odomontois

Reputation: 16308

First of all type X => Y i.e. Function1[X,Y] is contravariant on type parameter X so you should search least common subtype, not greatest common supertype of X and =>X

Second - unfortunately there is not such subtype. More over it's very hard to define anything except function using type =>X. Although it's known such definition desugars later to Function0[X] on typecheck level types =>X and ()=>X are not equivalent.

Happily we could define complex relations between types for our needs using typeclasses.

As we could use =>X only in parameter types we could define such a thing:

case class ParamConvertible[X, Y, T](apply: ((Y => T) => X => T))

Which looks and works like some narrow version of contravariant functor

And provide two obvious implementations

implicit def idParamConvertible[X, T] = ParamConvertible((f: X => T) => (x: X) => f(x))

implicit def byNameParamConvertible[X, T] = ParamConvertible((f: (=> X) => T) => (x: X) => f(x))

Now we can generalize your t function as

def t[T](h: T => String)
        (implicit conv: ParamConvertible[Int, T, String]): String =
  conv.apply(h)(42)

At this point your

t(f)
t(g)

Should compile and run nicely

Upvotes: 3

Till Rohrmann
Till Rohrmann

Reputation: 13346

The problem is that Int => String and (=> Int) => String don't have useful common super type. Int => String is the type of a call-by-value function which takes an Int value as first parameter and returns a String.

In contrast to that (=> Int) => String is a call-by-name function which takes an expression of type Int as the first parameter. Every time you access the call-by-name parameter the expression is evaluated. Thus, it is effectively a zero argument function returning an Int.

What you can do is to convert the call-by-name function into a call-by-value function so that t(h: Int => String): String type checks also with g. You simply have to call t(g(_)).

Upvotes: 1

Related Questions