Yep, the multiple same type issue definitely happens and that compicates client side matching. In my experience it has been infrequent enough that having to make the a couple wrapper classes would be preferable. Sometime tuples can be very ambiguous, take points for example. Point(x:Int,y:Int) is similar to (Int,Int), however sometimes the anonymity is nice so you will want to have both options.
The Boilerplate grows really fast as you try to pass results up a call hierachy.
So let me extend the example to demonstrate it how it doesn't scale:
//Sorry for the Scala-ness
//Presume a mapByType partial function on all discriminated unions if the union value is of that type, then it calls
//the partial function otherwise it just returns whatever it's current value is
def lexAndParse : ParseError | LexError | Int | String = lex().mapByType{ case t : Token => parse(t) }
newtype LexError = Err
def lex() : LexError | Token = ...
newtype ParseError = Err
def parse(t : Token) : ParseError | Int | String = {
tryParseInt(t).orElse(tryParseString(t)).getOrElse(ParseError("$t not Int Or String"))) }
}
def tryParseInt(token : Token) : Option[Int] = ...
def tryParseString(token : Token) : Option[String] = ...
The Boilerplate grows really fast as you try to pass results up a call hierachy.
So let me extend the example to demonstrate it how it doesn't scale:
versus: