Skip to content

Commit 5620e3e

Browse files
committed
Add test from errorhandling strawman
1 parent 04e7bc1 commit 5620e3e

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

tests/run/errorhandling/Result.scala

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package scala.util
2+
import boundary.Label
3+
4+
abstract class Result[+T, +E]
5+
case class Ok[+T](value: T) extends Result[T, Nothing]
6+
case class Err[+E](value: E) extends Result[Nothing, E]
7+
8+
object Result:
9+
extension [T, E](r: Result[T, E])
10+
11+
/** `_.?` propagates Err to current Label */
12+
transparent inline def ? (using Label[Err[E]]): T = r match
13+
case r: Ok[_] => r.value
14+
case err => break(err.asInstanceOf[Err[E]])
15+
16+
/** If this is an `Err`, map its value */
17+
def mapErr[E1](f: E => E1): Result[T, E1] = r match
18+
case err: Err[_] => Err(f(err.value))
19+
case ok: Ok[_] => ok
20+
21+
/** Map Ok values, propagate Errs */
22+
def map[U](f: T => U): Result[U, E] = r match
23+
case Ok(x) => Ok(f(x))
24+
case err: Err[_] => err
25+
26+
/** Flatmap Ok values, propagate Errs */
27+
def flatMap[U](f: T => Result[U, E]): Result[U, E] = r match
28+
case Ok(x) => f(x)
29+
case err: Err[_] => err
30+
31+
/** Similar to `Try`: Convert exceptions raised by `body` to `Err`s.
32+
*/
33+
def apply[T](body: => T): Result[T, Exception] =
34+
try Ok(body)
35+
catch case ex: Exception => Err(ex)
36+
end Result
37+
38+
/** A prompt for `_.?`. It establishes a boundary to which `_.?` returns */
39+
object respond:
40+
inline def apply[T, E](inline body: Label[Err[E]] ?=> T): Result[T, E] =
41+
boundary:
42+
val result = body
43+
Ok(result)
44+
45+

tests/run/errorhandling/Test.scala

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import scala.util.*
2+
3+
/** boundary/break as a replacement for non-local returns */
4+
def indexOf[T](xs: List[T], elem: T): Int =
5+
boundary:
6+
for (x, i) <- xs.zipWithIndex do
7+
if x == elem then break(i)
8+
-1
9+
10+
def breakTest() =
11+
println("breakTest")
12+
assert(indexOf(List(1, 2, 3), 2) == 1)
13+
assert(indexOf(List(1, 2, 3), 0) == -1)
14+
15+
/** traverse becomes trivial to write */
16+
def traverse[T](xs: List[Option[T]]): Option[List[T]] =
17+
optional(xs.map(_.?))
18+
19+
def optTest() =
20+
println("optTest")
21+
assert(traverse(List(Some(1), Some(2), Some(3))) == Some(List(1, 2, 3)))
22+
assert(traverse(List(Some(1), None, Some(3))) == None)
23+
24+
/** A check function returning a Result[Unit, _] */
25+
inline def check[E](p: Boolean, err: E): Result[Unit, E] =
26+
if p then Ok(()) else Err(err)
27+
28+
/** Another variant of a check function that returns directly to the given
29+
* label in case of error.
30+
*/
31+
inline def check_![E](p: Boolean, err: E)(using l: boundary.Label[Err[E]]): Unit =
32+
if p then () else break(Err(err))
33+
34+
/** Use `Result` to convert exceptions to `Err` values */
35+
def parseDouble(s: String): Result[Double, Exception] =
36+
Result(s.toDouble)
37+
38+
def parseDoubles(ss: List[String]): Result[List[Double], Exception] =
39+
respond:
40+
ss.map(parseDouble(_).?)
41+
42+
/** Demonstrate combination of `check` and `.?`. */
43+
def trySqrt(x: Double) = // inferred: Result[Double, String]
44+
respond:
45+
check(x >= 0, s"cannot take sqrt of negative $x").? // direct jump
46+
math.sqrt(x)
47+
48+
/** Instead of `check(...).?` one can also use `check_!(...)`.
49+
* Note use of `mapErr` to convert Exception errors to String errors.
50+
*/
51+
def sumRoots(xs: List[String]) = // inferred: Result[Double, String]
52+
respond:
53+
check_!(xs.nonEmpty, "list is empty") // direct jump
54+
val ys = parseDoubles(xs).mapErr(_.toString).? // direct jump
55+
ys.reduce((x, y) => x + trySqrt(y).?) // need exception to propagate `Err`
56+
57+
def resultTest() =
58+
println("resultTest")
59+
assert(sumRoots(List("1", "4", "9")) == Ok(6))
60+
assert(sumRoots(List("1", "-2", "4")) == Err(s"cannot take sqrt of negative -2.0"))
61+
assert(sumRoots(List()) == Err("list is empty"))
62+
assert(sumRoots(List("1", "3ab")) == Err("java.lang.NumberFormatException: For input string: \"3ab\""))
63+
64+
@main def Test =
65+
breakTest()
66+
optTest()
67+
resultTest()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package scala.util
2+
import boundary.Label
3+
4+
/** A mockup of scala.Option */
5+
abstract class Option[+T]
6+
case class Some[+T](x: T) extends Option[T]
7+
case object None extends Option[Nothing]
8+
9+
object Option:
10+
/** This extension should be added to the companion object of scala.Option */
11+
extension [T](r: Option[T])
12+
inline def ? (using label: Label[None.type]): T = r match
13+
case Some(x) => x
14+
case None => break(None)
15+
16+
/** A prompt for `Option`, which establishes a boundary which `_.?` on `Option` can return */
17+
object optional:
18+
inline def apply[T](inline body: Label[None.type] ?=> T): Option[T] =
19+
boundary(Some(body))
20+

0 commit comments

Comments
 (0)