I saw that in F# its very easy to define a type which is a combined from a set of other types such as
type MyFiveNumbers = One | Two | Three | Four | Five
This looks just great!
What is the simplest way to do that in Scala?
One and the rest are not types, but union cases. The Scala equivalent in fact does make them types:
sealed trait MyFiveNumbers
case object One extends MyFiveNumbers
case object Two extends MyFiveNumbers
...
In such a simple case you might be best off just using a Java enum. However, if any constructors have parameters (e.g. add | Other of int to the end), they correspond to Scala case classes:
case class Other(x: Int) extends MyFiveNumbers
You can use pattern matching just as in F#:
// x has type MyFiveNumbers
x match {
case One => ...
...
case Other(n) => ...
}
and get compiler warnings about incomplete matches (only if the sealed keyword is used; otherwise you can create additional cases in other files).
Related
Why does this produce an error?
import scala.io.StdIn
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
def eval [T](flow: Flow[T]): T = flow match {
case Flow.Say(text) => println(text)
case Flow.Ask => StdIn.readLine // error here
}
18 | case Flow.Ask => StdIn.readLine
| ^^^^^^^^^^^^^^
| Found: String
| Required: T
(no error if the enum is left invariant on T)
But when the enum is replaced with a (seemingly equivalent?) ADT implemented with a sealed trait, no such error is reported:
sealed trait Flow[+A]
object Flow {
case class Say(text: String) extends Flow[Unit]
case object Ask extends Flow[String]
}
// match statement in eval works fine
We can look at the initial proposal which added enums to Scala 3 for some insight.
By the wording of this proposal, enum cases fall into three categories.
Class cases are those cases that are parameterized, either with a type parameter section [...] or with one or more (possibly empty) parameter sections (...).
Simple cases are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only.
Value cases are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body.
Let's take a look at your enum declaration.
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
Now, simple cases are right out, because your enum is generic. Your Say is pretty clearly a class case, since it's parameterized. Ask, on the other hand, is non-generic and non-enumerated, so it's a value case. If we scroll down a bit, we see what value cases do
(6) A value case
case C extends <parents> <body>
expands to a value definition
val C = new <parents> { <body>; def enumTag = n; $values.register(this) }
where n is the ordinal number of the case in the companion object, starting from 0. The statement $values.register(this) registers the value as one of the enumValues of the enumeration (see below). $values is a compiler-defined private value in the companion object.
The bottom line here is that Ask is not defined as
object Ask extends Flow[String]
but more like
val Ask = new Flow[String]() { ... }
So your pattern match
case Flow.Ask => StdIn.readLine
is actually an equality check against the value Flow.Ask, rather than a type check against a singleton object. The former, unfortunately, proves nothing to the compiler about the generic type of your value, so it remains T, as opposed to specializing to String.
The rationale for this is provided on the same page
Objectives
...
Enumerations should be efficient, even if they define many values. In particular, we should avoid defining a new class for every value.
...
So it seems the Scala developers wanted to avoid the bloat that would result from an enum declaring tons of unparameterized values (i.e. a type that looks like an ordinary Java-style enum).
The simple solution, then, is to provide a parameter list
case Ask() extends Flow[String]
It's ever so slightly more cumbersome to work with, but it does define a new type, and then your pattern match
case Flow.Ask() => StdIn.readLine
will succeed
I though if something like this makes sense in scala:
object CaseClassUnion extends App {
case class HttpConfig(bindUrl: String, port: String)
case class DbConfig(url: String, usr: String, pass: String)
val combined: HttpConfig with DbConfig = ???
//HttpConfig("0.0.0.0", "21") ++ DbConfig("localhost", "root", "root")
//would be nice to have something like that
}
At least this compiles... Is there a way, probably with macros magic to achieve union of two classes given their instances?
In zio I believe there is something like in reverse:
val live: ZLayer[ProfileConfiguration with Logging, Nothing, ApplicationConfiguration] =
ZLayer.fromServices[ProfileConfigurationModule.Service, Logger[String], Service] { (profileConfig, logger) => ???
where we convert ProfileConfiguration with Logging to function of ProfileConfigurationModule.Service, Logger[String] => Service
Several things.
When you have several traits combined with with Scala does a trait linearization to combine them into one class with a linear hierarchy. But that's true for traits which doesn't have constructors!
case class (which is not a trait) cannot be extended with another case class (at all) because that would break contracts like:
case class A(a: Int)
case class B(a: Int, b: String) extends A(a)
A(1) == B(1, "") // because B is A and their respective fields match
B(1, "") != A(1) // because A is not B
B(1, "").hashCode != A(1).hashCode // A == B is true but hashCodes are different!
which means that you cannot even generate case class combination manually. You want to "combine" them, use some product: a tuple, another case class, etc.
If you are curious about ZIO it:
uses traits
uses them as some sort of type-level trick to represent an unordered set of dependencies, where type inference would calculate set sum when you combine operations and some clever trickery to remove traits from the list using .provide to remove dependency from the set
ZLayers are just making these shenanigans easier
so and if you even pass there some A with B you either combined it yourself by using cake pattern, or you passed dependencies one by one. ZIO developer might never be faced with the problem of needing some macro to combine several case classes (.provide(combineMagically(A, B, C, D, ...)) as they could pass implementations of each dependency one by one (.provide(A).provide(B)) and the code underneath would never need the combination of these types as one value - it's just a compile-time trick that might never translate to the requirement of an actual value of type A with B with C with D ....
TL;DR: You cannot generate a combination of 2 case classes; ZIO uses compound types as some sort of type-level set to trace dependencies and it doesn't actually require creating values of the compound types.
Let's assume we have a map indexed by enum values like this
sealed trait A
case object B extends A
case object C extends A
type SafeMap = Map[A, String]
Would it be possible to somehow enforce the exhaustiveness of such map?
Maybe with dependent types? If not in Scala than Idris is interesting as well.
To clarify, I would like to have a generic constraint for any ADT that check that Map has an entry defined for each variant of ADT.
well a Map[A,String] for an ADT A is pretty much a function A => String
You can do
def foo(a:A):String = a match {
case ...
}
with the fatal-warning flag for the compiler (else non-exhaustive patternmatches are only warnings) this should get you pretty far.
PS: you should probably make your case classes/object final
Looking at the Scala doc for sealed classes, it says:
If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
I don't quite understand what they meant in this paragraph. My understanding is that if a switch case, doesn't cover all the possibilities, then we'll get a warning at compile time, saying we might get an error at run time. Is this correct?
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings, which is just silly, so I take it my understanding is incorrect. Someone care to elucidate, please?
What the paragraph is saying is that in-case you have a fixed hierarchy structure like this:
sealed trait Foo
class Bar extends Foo
class Baz extends Foo
class Zab extends Foo
Then when you pattern match on it, the compiler can infer if you've attempted to match on all possible types extending the sealed trait, in this example:
def f(foo: Foo) = foo match {
| case _: Bar => println("bar")
| case _: Baz => println("baz")
| }
<console>:13: warning: match may not be exhaustive.
It would fail on the following input: Zab()
def f(foo: Foo) = foo match {
^
f: (foo: Foo)Unit
Note the beginning of the document says:
A sealed class may not be directly inherited, except if the inheriting
template is defined in the same source file as the inherited class.
This is unique to Scala, and can't be done in Java. A final class in Java cannot be inherited even if declared inside the same file. This is why this logic won't work for String in Scala, which is an alias for java.lang.String. The compiler warning may only be emitted for Scala types that match the above criteria.
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings
Yes, if the selector has type String (except it isn't a sealed class, because that's a Scala concept and String is a Java class).
which is just silly
No. For strings, you just need a catch-all case, e.g.
val x: String = ...
x match {
case "a" => ...
case "b" => ...
case _ => ...
}
is an exhaustive match: whatever x is, it matches one of the cases. More usefully, you can have:
val x: Option[A] = ...
x match {
case Some(y) => ...
case None => ...
}
and the compiler will be aware the match is exhaustive even without a catch-all case.
The wildcard character allows us to cover all the scenarios.
something match {
case one => ...
case two => ...
case _ => ...
}
It is selected whenever all other cases don't match; that is, it is the default case. Further information here.
I am trying to translate some code from haskell to scala language.
In haskell I implemented an enum type like that :
data Rank = Jack | Queen | King | Ace | Num Int deriving (Show, Eq)
I would Like to implement it in scala using selaled case Objects
sealed trait Rank
case object Jack extends Rank
case object Queen extends Rank
case object King extends Rank
case object Ace extends Rank
case object Num Int extends Rank
The problem that for the Num Int type i get an error. I think it should be written as one word ! Any help !
In Haskell Num is a class that requires a single type argument, such as Int, to produce a constraint such as Num Int. So in scala you should expect something like that:
case class Num(value: Int) extends Rank
Notice that scala requires you to give the argument a name, unlike haskell
Also you are missing the instances of Show and Eq defined for Rank in scala code, but that doesn't seem to be a part of the question