Is there a standard sum type like Either but for 3 cases? Haskell has These
but it's not quite that.
To start, it selects the column department from the table subject. Then comes the curious use of a SUM () with a CASE WHEN. This expression says whenever the number_of_lectures is higher than 20, the row is assigned the value 1. If the condition is not met, the assigned value is 0.
For example, the sum type of a boolean and an unsigned integer ( uint+bool) represents exactly one value in the set {true, false, 0, 1, .., 4294967295}. This type can be written in C++ using the following syntax, which we call C-style unions:
It’s trivial to define sum types in languages like Haskell, Scala, Rust, Swift, OCaml, F#, Kotlin because algebraic data types are the core concept of these languages by their design. The most expressive languages are Haskell, OCaml, and F# — they offer the most laconic syntax of an ADT definition.
Well, really, all you can do is run different code depending on whether the Maybe is a Nothing or Just. This is the visitor pattern, which is another way to approximate sum types in languages without them. Visitor has the right maintainability characteristics (easy to add uses, hard to add cases), but it involves a great deal of boilerplate.
These are called co-products really an Either
is simply a 2 argument co-product. You can use helpers from the shapeless library to build arbitrary length co-products using:
type CP = Int :+: String :+: Boolean :+: CNil
val example = Coproduct[CP]("foo")
You can then use all the fun poly
magic to map them or perform other operations:
object printer extends Poly1 {
implicit def caseInt = at[Int](i => i -> s"$i is an int")
implicit def caseString = at[String](s => s -> s"$s is a string")
implicit def caseBoolean = at[Boolean](b => s -> s"$b is a bool")
}
val mapped = example map printer
mapped.select[(String, String)] shouldEqual "foo is a string"
Scala.JS + Shapeless can work together as far as I know, so that may give you what you want.
In recent Haskell, I'd switch on a bit of kitchen sink.
{-# LANGUAGE PolyKinds, DataKinds, GADTs, KindSignatures,
TypeOperators, PatternSynonyms #-}
Then I'd define type-level list membership
data (:>) :: [x] -> x -> * where
Ze :: (x ': xs) :> x
Su :: xs :> x -> (y ': xs) :> x
and now I have all the finite sums, without cranking out a whole raft of OneOfN type definitions:
data Sum :: [*] -> * where
(:-) :: xs :> x -> x -> Sum xs
But, to address Tomas's issue about readability, I'd make use of pattern synonyms. Indeed, this sort of thing is the reason I've been banging on about pattern synonyms for years.
You can have a funny version of Maybe
:
type MAYBE x = Sum '[(), x]
pattern NOTHING :: MAYBE x
pattern NOTHING = Ze :- ()
pattern JUST :: x -> MAYBE x
pattern JUST x = Su Ze :- x
and you can even use newtype
to build recursive sums.
newtype Tm x = Tm (Sum '[x, (Tm x, Tm x), Tm (Maybe x)])
pattern VAR :: x -> Tm x
pattern VAR x = Tm (Ze :- x)
pattern APP :: Tm x -> Tm x -> Tm x
pattern APP f s = Tm (Su Ze :- (f, s))
pattern LAM :: Tm (Maybe x) -> Tm x
pattern LAM b = Tm (Su (Su Ze) :- b)
The newtype wrapper also lets you make instance
declaration for types built that way.
You can, of course, also use pattern synonyms to hide an iterated Either
nicely.
This technique is not exclusive to sums: you can do it for products, too, and that's pretty much what happens in de Vries and Löh's Generics-SOP library.
The big win from such an encoding is that the description of data is itself (type-level) data, allowing you to cook up lots of deriving
-style functionality without hacking the compiler.
In the future (if I have my way), all datatypes will be defined, not declared, with datatype descriptions made of data specifiying both the algebraic structure (allowing generic equipment to be computed) of the data and its appearance (so you can see what you're doing when working with a specific type).
But the future is sort of here already.
I think that heavily relying on type like this is an anti-pattern.
One of the nicest things you get from using algebraic data types is that the resulting type tells you something about the domain that you are working with. With a generic type like Choice<T1, T2, T3>
, you are really not saying anything about the domain.
I think option<T>
(aka Maybe
) is quite clear in that it says that a value of type T
is either there or it is missing for some reason. I think Either<'T, exn>
is still quite clear in that it says you get a value or an exception. However when you have more than two cases, it becomes quite hard to understand what is meant by the cases and so explicitly defining a type with names to match the domain might be a good idea.
(I do use Choice<T1, T2, T3>
in F# occasionally, but the usage is typically limited to a small scope - less than 50 lines of code - so that I can easily find what the meaning is in the surroundings of the code that consumes it.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With