functional.cafe is one of the many independent Mastodon servers you can use to participate in the fediverse.
functional.cafe is an instance for people interested in functional programming and languages.

Server stats:

207
active users

So... am I missing something, or am I breaking the compiler a little by overriding the `equals` method of a sum type?

scastie.scala-lang.org/nrinaud

scastie.scala-lang.orgScastie - An interactive playground for Scala.//> using scala 3 //> using option -Xkind-projector:underscores def assertEquals[A](observed: A, expected: A) = assert(observed == expected) // - Boundary / Break ----------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------------------ // `boundary` sets a label, `break(a)` goes to that label and returns `a`. object boundary: final class Label[-A] private final case class Break[A](value: A, label: Label[A]) extends RuntimeException(null, null, false, false) def break[A](value: A)(using l: Label[A]): Nothing = throw Break(value, l) inline def apply[A](inline body: Label[A] ?=> A): A = val label = new Label[A] try body(using label) catch case Break(value, `label`) => value // - Results -------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------------------ // Kind of like `Either`, but specifically meant for error handling. enum Result[+E, +A]: case Success(value: A) case Failure(error: E) // Syntactic sugar to lift values in `Result`. extension [A](value: A) def success[E]: Result[E, A] = Result.Success(value) def failure[E]: Result[A, E] = Result.Failure(value) // - Working with Results -------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------------------ // Type of things that can fail. infix type failsWith[A, E] = boundary.Label[Result[E, Nothing]] ?=> A object results: // Forces evaluation `body`, wrapping the result in `Result.Success`. // We know failures are encoded as `Failure` because of the type of // the label we're carrying. inline def apply[E, A](inline body: A failsWith E): Result[E, A] = boundary: Result.Success(body) // Fails with the specified error. def fail[E](error: E)(using boundary.Label[Result[E, Nothing]]): Nothing = boundary.break(error.failure) // De-references the specified result if a `Success`, or fails to the // enclosing label. extension [E, A](result: Result[E, A]) def ?(using boundary.Label[Result[E, Nothing]]): A = result match case Result.Success(a) => a case Result.Failure(e) => boundary.break(Result.Failure(e)) // `fail` demonstration: removes the optional layer, failing on the first `None`. def unwrap[A](as: List[Option[A]]): List[A] failsWith String = as.map: case Some(a) => a case None => fail("found a None") assertEquals( observed = results(unwrap(List(Some(1), Some(2)))), expected = List(1, 2).success ) assertEquals( observed = results(unwrap(List(Some(1), Some(2), None))), expected = "found a None".failure ) // `?` demonstration: sequence is quite a bit more pleasant implement that way, isn't it? def sequence[A, E](as: List[Result[E, A]]): Result[E, List[A]] = results: as.map(_.?) assertEquals( observed = sequence(List(1.success, 2.success)), expected = List(1, 2).success ) assertEquals( observed = sequence(List(1.success, 2.success, "some failure".failure)), expected = "some failure".failure )
Quiet public

@NicolasRinaudo
As far as I remember, you can expect strange behaviour if you override `equals` without doing the same with `hashCode` in a consistent way.

Quiet public

@turb right, but that's sort of unrelated - the problem would be the same if I updated `hashCode` to be consistent with `equals`