Skip to content

Commit

Permalink
Some new tests
Browse files Browse the repository at this point in the history
 - Some variants of typeclasses
 - Revised Pouring.scala with some namign improvements
  • Loading branch information
odersky committed Nov 4, 2023
1 parent e2c9dc0 commit e41cd40
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 26 deletions.
198 changes: 198 additions & 0 deletions tests/pos/typeclasses.scala
Original file line number Diff line number Diff line change
@@ -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
*/
3 changes: 1 addition & 2 deletions tests/run/Pouring.check
Original file line number Diff line number Diff line change
@@ -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
48 changes: 24 additions & 24 deletions tests/run/Pouring.scala
Original file line number Diff line number Diff line change
@@ -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 =
Expand All @@ -44,14 +42,16 @@ 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
if path.endContent.contains(target)
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}")

0 comments on commit e41cd40

Please sign in to comment.