given ... =
is a given alias
also, I find it utterly confusing that there's no
:
afterwith
... similarly for extensions
There's always a keyword for indentation like in Haskell (then
, do
, while
, ...), except for classes where it's :
because Martin thought that worked better, and extension blocks (mostly because they're not really scopes and because you can also write them on one like extension (x: Int) def foo (y: Int) = ...
)
with
like syntax for defs see lampepfl/dotty#10637
type ScopeNode = Comment | Expression | ScopeBlock | ListBlock | EmptyBlock
type ListNode = Comment | Element
ScopeNode
and ListNode
as mixins within the ADT hierarchy
/**
* An [[Entity]] that is not allowed to be part of a [[Frag]].
* We use types instead of trait inheritance (at the moment) because we need to
* transparently include [[String]]s in the type hierarchy.
* @see [[Element]]
*/
type Modifier = Attr | Style
/**
* An [[Entity]] that is allowed to be part of a [[Frag]].
* We use types instead of trait inheritance (at the moment) because we need to
* transparently include [[String]]s in the type hierarchy.
* @see [[Modifier]]
*/
type Element = Raw | Tag | Frag | String
/**
* Something [[String]]y (see [[render]]) that represents, in and of itself, a coherent
* component of an XML tree. [[TagClass!]]es, [[AttrClass!]]es, and [[StyleClass!]]es do not
* make the cut because while they are valid names that may appear in an XML document, they cannot exist in an XML document outside of
* their respective entities. We use types instead of trait inheritance (at the moment) because we need to
* transparently include [[String]]s in the type hierarchy.
* @see [[Modifier]]
* @see [[Element]]
*/
type Entity = Modifier | Element
String
, except for Frag
which was an Array[Element]
IIRC
String
s
Chain[Comment]
to a Chain[ListNode]
by appending an Element
, it works fine when the resulting Chain[ListNode]
is passed directly into a functionthat takes a Chain[ListNode]
, but when the append is done inside a map
lambda and then map
ped with said Chain[ListNode]
-taking function, it can't infer the type parameter on commentChain.append[ /*Should infer as ListNode*/](element)
ListNode
is a mixin that Comment
and Element
both extend, it infers it just fine
append[ListNode]
map
s and combine
s with append
ing/concat
ing lambdas to custom mappend
/mconcat
/cappend
but also, we shouldn't encourage the (over)use of unions, if you can make a hierarchy that's probably a better idea
I've seen this argument been made before and it kinda feels a bit like a tease to me:
–"Here you go, union types!"
–"Oh cool, I can use them for simplifying stuff.."
–"You shouldn't really use them, they're mostly for the compiler."
My point is I don't understand why, fundamentally, overusing unions could be a bad thing. They look really cool to me, and seem to make a lot of sense in a lot of places. For fun I tried implementing a Result-type where error types are combined with unions instead of widened to a common supertype, and it's awesome! The error domain went from being whatever to actually showing me warnings for unchecked types.
In my mind, a combination of nominal and structural typing—like letting A.x | B.x actually work again—would be greater than the sum of its parts. This has already been explored a tiny bit with explicit nulls and how "flow typing" works within that context.
Yes, @megri @CiaraOBrien @smarter on the (under/over)use of unions, I think the modelling opportunities are great. And I agree that it is better to make a language feature as ergonomic as possible and then instead help with warnings etc if there are some risk of badness. But I would also want to know more about the over-use problems. Myself, when looking at my older code, I see a lot of inheritance stuff that could have been re-modelled as unions. So I perhaps rather have an under-use of them :) I have been playing around with it and find that improved inference would be really helpful if possible. E.g. in the below example, it would be nice to have the compiler figure out enough for me not having to resort to asInstanceOf
in the below model of an Empty
type that can be used when you don't need all the monadic stuff by the heavier, parameterized Option
, e.g. by just def f(x: Input): Result | Empty = ???
:
sealed abstract class Empty
object Empty extends Empty:
override def toString = "Empty"
extension [T](x: T | Empty)
def toOption: Option[T] = x match
case Empty => None
case _ => Option(x.asInstanceOf[T])
def isEmpty: Boolean = x.isInstanceOf[Empty]
def nonEmpty: Boolean = !isEmpty
def get: T = x match
case Empty => (throw new NoSuchElementException("Empty.get")).asInstanceOf[T]
case _ => x.asInstanceOf[T]
def getOrElse(default: T): T = x match
case Empty => default
case _ => x.asInstanceOf[T]
end extension
And if you like to do all the monadic coolness with map and flatmap you can just .toOption
on any union with Empty. But perhaps is there already a concise way to get rid of the asInstanceOf
above that I missed?
And it would be nice if the last evaluation below would have been Int | String
scala> var x: Int | String | Empty = 42
var x: Int | String | Empty = 42
scala> val ie: Int | Empty = 42
val ie: Int | Empty = 42
scala> ie.get
val res3: Int = 42
scala> val ise: Int | String | Empty = 42
val ise: Int | String | Empty = 42
scala> ise.get
val res4: Matchable = 42
See also: lampepfl/dotty#11449
enum
is translated to a sealed abstract class (similar to a sealed trait) under the hood. You can see all the gory details of the translation made by the compiler here: http://dotty.epfl.ch/docs/reference/enums/desugarEnums.html