From e41cd40820a6ca0c2bfa5c440f6c85ca61a3071e Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 4 Nov 2023 10:56:25 +0100 Subject: [PATCH] Some new tests - Some variants of typeclasses - Revised Pouring.scala with some namign improvements --- tests/pos/typeclasses.scala | 198 ++++++++++++++++++++++++++++++++++++ tests/run/Pouring.check | 3 +- tests/run/Pouring.scala | 48 ++++----- 3 files changed, 223 insertions(+), 26 deletions(-) create mode 100644 tests/pos/typeclasses.scala diff --git a/tests/pos/typeclasses.scala b/tests/pos/typeclasses.scala new file mode 100644 index 000000000000..07fe5a31ce5d --- /dev/null +++ b/tests/pos/typeclasses.scala @@ -0,0 +1,198 @@ +class Common: + + // this should go in Predef + infix type at [A <: { type This}, B] = A { type This = B } + + trait Ord: + type This + extension (x: This) + def compareTo(y: This): Int + def < (y: This): Boolean = compareTo(y) < 0 + def > (y: This): Boolean = compareTo(y) > 0 + + trait SemiGroup: + type This + extension (x: This) def combine(y: This): This + + trait Monoid extends SemiGroup: + def unit: This + + trait Functor: + type This[A] + extension [A](x: This[A]) def map[B](f: A => B): This[B] + + trait Monad extends Functor: + def pure[A](x: A): This[A] + extension [A](x: This[A]) + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = x.flatMap(f `andThen` pure) +end Common + + +object Instances extends Common: + +/* + instance Int: Ord as intOrd with + extension (x: Int) + def compareTo(y: Int) = + if x < y then -1 + else if x > y then +1 + else 0 +*/ + given intOrd: Ord with + type This = Int + extension (x: Int) + def compareTo(y: Int) = + if x < y then -1 + else if x > y then +1 + else 0 +/* + instance List[T: Ord]: Ord as listOrd with + extension (xs: List[T]) def compareTo(ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) +*/ + + // Proposed short syntax: + // given listOrd[T: Ord as ord]: Ord at T with + given listOrd[T](using ord: Ord { type This = T}): Ord with + type This = List[T] + extension (xs: List[T]) def compareTo(ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + end listOrd + +/* + instance List: Monad as listMonad with + extension [A](xs: List[A]) def flatMap[B](f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) +*/ + + given listMonad: Monad with + type This[A] = List[A] + extension [A](xs: List[A]) def flatMap[B](f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + +/* + type Reader[Ctx] = X =>> Ctx => X + instance Reader[Ctx: _]: Monad as readerMonad with + extension [A](r: Ctx => A) def flatMap[B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x +*/ + + given readerMonad[Ctx]: Monad with + type This[X] = Ctx => X + extension [A](r: Ctx => A) def flatMap[B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + + extension (xs: Seq[String]) + def longestStrings: Seq[String] = + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + + extension [T](xs: List[T]) + def second = xs.tail.head + def third = xs.tail.tail.head + + //Proposed short syntax: + //extension [M: Monad as m, A](xss: M[M[A]]) + // def flatten: M[A] = + // xs.flatMap(identity) + + extension [M, A](using m: Monad)(xss: m.This[m.This[A]]) + def flatten: m.This[A] = + xss.flatMap(identity) + + // Proposed short syntax: + //def maximum[T: Ord](xs: List[T]: T = + def maximum[T](xs: List[T])(using Ord at T): T = + xs.reduceLeft((x, y) => if (x < y) y else x) + + // Proposed short syntax: + // def descending[T: Ord as asc]: Ord at T = new Ord: + def descending[T](using asc: Ord at T): Ord at T = new Ord: + type This = T + extension (x: T) def compareTo(y: T) = asc.compareTo(y)(x) + + // Proposed short syntax: + // def minimum[T: Ord](xs: List[T]) = + def minimum[T](xs: List[T])(using Ord at T) = + maximum(xs)(using descending) + + def test(): Unit = + val xs = List(1, 2, 3) + println(maximum(xs)) + println(maximum(xs)(using descending)) + println(maximum(xs)(using descending(using intOrd))) + println(minimum(xs)) + +// Adapted from the Rust by Example book: https://doc.rust-lang.org/rust-by-example/trait.html +// +// lines words chars +// wc Scala: 30 115 853 +// wc Rust : 57 193 1466 +trait Animal: + type This + // Associated function signature; `This` refers to the implementor type. + def apply(name: String): This + + // Method signatures; these will return a string. + extension (self: This) + def name: String + def noise: String + def talk(): Unit = println(s"$name, $noise") +end Animal + +class Sheep(val name: String): + var isNaked = false + def shear() = + if isNaked then + println(s"$name is already naked...") + else + println(s"$name gets a haircut!") + isNaked = true + +/* +instance Sheep: Animal with + def apply(name: String) = Sheep(name) + extension (self: This) + def name: String = self.name + def noise: String = if self.isNaked then "baaaaah?" else "baaaaah!" + override def talk(): Unit = + println(s"$name pauses briefly... $noise") +*/ + +// Implement the `Animal` trait for `Sheep`. +given Animal with + type This = Sheep + def apply(name: String) = Sheep(name) + extension (self: This) + def name: String = self.name + def noise: String = if self.isNaked then "baaaaah?" else "baaaaah!" + override def talk(): Unit = + println(s"$name pauses briefly... $noise") + +/* + + - In a type pattern, A <: T, A >: T, A: T, A: _ are all allowed and mean + T is a fresh type variable (T can start with a capital letter). + - instance definitions + - `as m` syntax in context bounds and instance definitions + +*/ diff --git a/tests/run/Pouring.check b/tests/run/Pouring.check index f07f29105c0b..c9ab84a226bb 100644 --- a/tests/run/Pouring.check +++ b/tests/run/Pouring.check @@ -1,2 +1 @@ -Vector(Empty(0), Empty(1), Fill(0), Fill(1), Pour(0,1), Pour(1,0)) -Fill(1) Pour(1,0) Empty(0) Pour(1,0) Fill(1) Pour(1,0) --> Vector(4, 6) +Illegal command line: more arguments expected diff --git a/tests/run/Pouring.scala b/tests/run/Pouring.scala index 6f4611af8bfc..5bb2a92ff8e3 100644 --- a/tests/run/Pouring.scala +++ b/tests/run/Pouring.scala @@ -1,37 +1,35 @@ -class Pouring(capacity: Vector[Int]): - type Glass = Int - type Content = Vector[Int] +type Glass = Int +type Levels = Vector[Int] - enum Move: - def apply(content: Content): Content = this match - case Empty(g) => content.updated(g, 0) - case Fill(g) => content.updated(g, capacity(g)) - case Pour(from, to) => - val amount = content(from) min (capacity(to) - content(to)) - extension (s: Content) def adjust(g: Glass, delta: Int) = s.updated(g, s(g) + delta) - content.adjust(from, -amount).adjust(to, amount) +class Pouring(capacity: Levels): + enum Move: case Empty(glass: Glass) case Fill(glass: Glass) case Pour(from: Glass, to: Glass) + + def apply(levels: Levels): Levels = this match + case Empty(glass) => + levels.updated(glass, 0) + case Fill(glass) => + levels.updated(glass, capacity(glass)) + case Pour(from, to) => + val amount = levels(from) min (capacity(to) - levels(to)) + levels.updated(from, levels(from) - amount) + .updated(to, levels(to) + amount) end Move + val glasses = 0 until capacity.length val moves = - val glasses = 0 until capacity.length - - (for g <- glasses yield Move.Empty(g)) + (for g <- glasses yield Move.Empty(g)) ++ (for g <- glasses yield Move.Fill(g)) ++ (for g1 <- glasses; g2 <- glasses if g1 != g2 yield Move.Pour(g1, g2)) - class Path(history: List[Move], val endContent: Content): + class Path(history: List[Move], val endContent: Levels): def extend(move: Move) = Path(move :: history, move(endContent)) override def toString = s"${history.reverse.mkString(" ")} --> $endContent" - end Path - - val initialContent: Content = capacity.map(x => 0) - val initialPath = Path(Nil, initialContent) - def from(paths: Set[Path], explored: Set[Content]): LazyList[Set[Path]] = + def from(paths: Set[Path], explored: Set[Levels]): LazyList[Set[Path]] = if paths.isEmpty then LazyList.empty else val extensions = @@ -44,6 +42,8 @@ class Pouring(capacity: Vector[Int]): paths #:: from(extensions, explored ++ extensions.map(_.endContent)) def solutions(target: Int): LazyList[Path] = + val initialContent: Levels = capacity.map(_ => 0) + val initialPath = Path(Nil, initialContent) for paths <- from(Set(initialPath), Set(initialContent)) path <- paths @@ -51,7 +51,7 @@ class Pouring(capacity: Vector[Int]): yield path end Pouring -@main def Test = - val problem = Pouring(Vector(4, 7)) - println(problem.moves) - println(problem.solutions(6).head) +@main def Test(target: Int, capacities: Int*) = + val problem = Pouring(capacities.toVector) + println(s"Moves: ${problem.moves}") + println(s"Solution: ${problem.solutions(target).headOption}")