Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type inference changes in Scala 3

What changes in type inference will Scala 3 bring? Currently documentation simply states TODO. For example,

Weak conformance

Scala 2.13

scala> val i: Int = 42
val i: Int = 42

scala> val c: Char = 'a'
val c: Char = a

scala> List(i,c)
val res0: List[Int] = List(42, 97)

Scala 3 (dotty 0.24.0-RC1)

scala> val i: Int = 42
val i: Int = 42

scala> val c: Char = 'a'
val c: Char = a

scala> List(i,c)
val res0: List[AnyVal] = List(42, a)

Equality

Scala 2.13

scala> 42 == Some(42)
          ^
       warning: comparing values of types Int and Some[Int] using `==` will always yield false
val res2: Boolean = false

Scala 3

scala> 42 == Some(42)
1 |42 == Some(42)
  |^^^^^^^^^^^^^^
  |Values of types Int and Some[Int] cannot be compared with == or !=
like image 388
Mario Galic Avatar asked Mar 03 '23 09:03

Mario Galic


2 Answers

So as for your Equality example it is actually caused by the new Multiversal Equality which pretty much means that if you have an Eql[A, B] where A is B then type A can only be compared to things it has an Eql instance for them (of the form Eql[A, C] or Eql[C, A]).

In terms of general type inference for scala 3, the main things are:

  • Union Types: We can now represent union types and expressions like

     if (condition) 1 else "1"
    

    should be of inferred as of type Int | String.

  • Explicit Nulls: One of the new uses for union types is a way to describe nullable types, so for example we could write such a code in Java:

     public String getUser(int id) {
         if (id == 0) {
             return "Admin";
         }
         return null;
     }
    

    And also in scala 2 we could write:

    def getUser(id: Int): String = if (id == 0) return "Admin" else null
    

    But in scala 3 such a method will also have to be declared as of type String | Null to represent its nullability, and will not compile by default in newer versions of scala 3.

    When working with Java it gets more complicated so if you want to know more about it I suggest reading in the link.

  • GADT: Similar to how @functionalInterface works in java we know have GADTs. That means that if you were to have a trait with one unimplemented method:

    trait Fooable {
        def foo(): Unit
    }
    

    You could create an instance of it by passing a lambda with that signature, so in this example:

    val fooable: Fooable = () => print("Fooing")
    

    There are a few more, including Context Functions, Implicit Conversions and Parameter untupling but those are the main ones.

like image 109
Guyde Avatar answered Mar 06 '23 14:03

Guyde


Inferred types are now propagated through the remainder of the single parameter list, which means using multiple parameter lists to aid inference might not be necessary.

Scala 2.13

scala> def f[T](i: T, g: T => T) = g(i)
def f[T](i: T, g: T => T): T

scala> f(41, x => x + 1)
             ^
       error: missing parameter type

Scala 3

scala> def f[T](i: T, g: T => T) = g(i)
def f[T](i: T, g: T => T): T

scala> f(41, x => x + 1)
val res0: Int = 42

I guess this change might be related to Allow inter-parameter dependencies #2079


Better inference when type parameters are not surfaced in terms

Scala 2.13

scala> def f[F <: List[A], A](as: F) = as
def f[F <: List[A], A](as: F): F

scala> f(List(42))
       ^
       error: inferred type arguments [List[Int],Nothing] do not conform to method f's type parameter bounds [F <: List[A],A]
             ^
       error: type mismatch;
        found   : List[Int]
        required: F

Scala 3

scala> def f[F <: List[A], A](as: F) = as                                                                                                                
def f[F <: List[A], A](as: F): F

scala> f(List(42))                                                                                                                                       
val res0: List[Int] = List(42)
like image 42
2 revs Avatar answered Mar 06 '23 14:03

2 revs