11

Rank N Types in Scala 3

 3 years ago
source link: https://blog.oyanglul.us/scala/dotty/en/rank-n-type
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rank N Types in Scala 3

Rank N Types in Scala 3

Table of Contents


I'm recently migrating some libs and projects to Scala 3, I guess it would be very helpful to me or anyone interested to learn some new functional programming features that Scala 3 is bringing to us.

Source code 👉 https://github.com/jcouyang/meow


Scala 2

There is no Rank N Types in Scala 2, but you can use FunctionK from Cats to mimic Rank N Types.

This is a Rank 1:

// forall a b c. (b, c) -> (a -> a) -> (b, c)
def rank[A,B,C](a: (B, C), doSomething: A => A): (B, C) = (doSomething(a._1), doSomething(a._2))

It won't compile because `doSomething` is rank 1 in rank 2 place.

When compiler tried to figure our what type doSomething(a._1) really is, since =a._1: B fix the type doSomething: B => B so compiler knows that A = B, but latter on there is a doSomething(a._2) now compiler confused, A cannot be both B and C while there are no proof of B = C.

Simplest way to make Scala 2 is to use Cats FunctionK:

def rank[B,C](a: (B, C), doSomething: Id ~> Id): (B, C) = (doSomething(a._1), doSomething(a._2))

Note that the trick of FunctionK, which basically remove A from type parameter rank[A, B, C], which means compiler then has nothing to confuse about.

While it is not totally free to use cats, there are some boilerplate you have to do when defining doSomething:

def doSomething: Id ~> Id = new (Id ~> Id) {
  def apply[A](a: Id[A]): Id[A] = a
}

Type A is hidden in the apply method.

Scala 3

Now in Scala 3, you can simply do the same thing natively with Polymorphic function types.

// rank 2 type (forall a. a -> a)
val id = [A] => (a: A) => a

// forall b c. (b, c) -> (forall a. a -> a) -> (b, c)
def rank2[B,C](a: (B, C), doSomething: [A] => A => A): (B, C) = (doSomething(a._1), doSomething(a._2))

def main(args: Array[String]): Unit = {
  println(
    rank2((1, "2"), id)
  )
}

The trick is now A is defined [A] => A => A, no in method type parameters. It is quite similar to how we define rank n types in Haskell

forall b c. (b, c) -> (forall a. a -> a) -> (b, c)

So basically [A] => is the same thing as forall a.

Play around with the above examples in scatie: https://scastie.scala-lang.org/jcouyang/3hNle3faQ7SpS4mCcoMSGA/29

Or clone the repo and play locally: https://github.com/jcouyang/meow


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK