2

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.

Community
  • 1
  • 1
alxest
  • 21
  • 2

2 Answers2

3

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

Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • Thanks for your reply. As you mentioned, what I need is common supertype of `Int => String` and `=> Int => String`, and the least one should be "`Int` and `=> Int`'s common subtype" => String. From the mentioned link's code, I get `Int => String` and `=> Int => String`'s least common supertype is `=> Int with Int => String`. Which also implies `Int` and `=> Int`'s common subtype is `=> Int with Int`. (I am not sure if `=> Int` can stand alone as a type, but if so) Could you give me some more explanation about _Second - unfortunately there is not such subtype._? – alxest Oct 19 '15 at 16:38
  • Thanks for your nice working code. However, what I needed was to type-check given code without changing implicitly or explicitly the given paramater `h`. If it is not possible as you mentioned, I want to be fully convinced. Again, thanks a lot for your reply! – alxest Oct 19 '15 at 16:49
1

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(_)).

Till Rohrmann
  • 13,148
  • 1
  • 25
  • 51