OCaml modules, but in Scala

2 min

Scala 3 是好文明👍

签名

/** Not quite natural numbers :) */
trait Nat {
  type A
  def zero: A
  def succ(a: A): A
  def display(a: A): String
}

对应

module type Nat = sig
  type t
  val zero: t
  val succ: t -> t
  val display: t -> string
end

模块

object IntNat extends Nat {
  opaque type A = Int
  def zero: Int = 0
  def succ(a: Int): Int = a + 1
  def display(a: Int): String = s"($a : Int)"
}

object TerminalNat extends Nat {
  opaque type A = Unit
  def zero: Unit = ()
  def succ(a: Unit): Unit = ()
  def display(a: Unit): String = "()"
}

对应

module IntNat: Nat = struct
  type t = int
  let zero = 0
  let succ n = n + 1
  let display n = "(" ^ (Int.to_string n) ^ " : int)"
end

module TerminalNat: Nat = struct
  type t = unit
  let zero = ()
  let succ _ = ()
  let display _ = "()"
end

用一用

@main def main(): Unit = {
  import IntNat.*
  println(display(succ(succ(zero)))) // (2 : Int)
  println(display(succ(zero))) // (1 : Int)
  println(display(zero)) // (0 : Int)
}

First-class Modules!

我们的 Module 本来就是用 object 实现的,这很 Trivial

import scala.util.chaining.*

@main def main(): Unit = {
  def five(n: Nat): n.A = {
    import n.*;
    succ(succ(succ(succ(succ(zero)))))
  }

  def print_five(n: Nat): Unit = {
    import n.*;
    five(n) pipe display pipe println
  }

  print_five(IntNat) // (5 : Int)
  print_five(TerminalNat) // ()
  print_five(new Nat {
    type A = Boolean
    def zero: A = false
    def succ(a: A): A = !a
    def display(a: A): String = a.toString
  }) // true
}

First-class Modules,但我不想装箱

等 Scala 3 有个好的单态化/特化方案吧。届时直接改写成

def five[N <: Nat](n: N): n.A
def print_five[N <: Nat](n: N): Unit

然后单态化应该就没问题

Functor

final case class NatDisplayPrefixed(prefix: String)(val n: Nat) extends Nat {
  type A = n.A
  def zero: A = n.zero
  def succ(a: A): A = n.succ(a)
  def display(a: A): String = prefix + n.display(a)
}

@main def main(): Unit = {
  // ...
  print_five(NatDisplayPrefixed("value = ")(IntNat)) // value = (5 : Int)
}

Dotty 嘛,能耍的花活太多了,这个东西平平无奇