Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • 17:59
    griggt review_requested #11642
  • 17:59
    griggt review_requested #11642
  • 17:59
    griggt opened #11642
  • 16:49
    lihaoyi commented #11639
  • 15:32
    martijnhoekstra commented #11640
  • 15:31
    valencik edited #11641
  • 15:29
    valencik labeled #11641
  • 15:29
    valencik opened #11641
  • 15:23
    martijnhoekstra commented #11640
  • 15:20
    martijnhoekstra commented #11640
  • 14:56
    gaeljw commented #10437
  • 14:35
    martijnhoekstra commented #11640
  • 13:54
    dotbg commented #11625
  • 13:27
    smarter commented #11640
  • 13:24
    smarter closed #11639
  • 13:24
    smarter commented #11639
  • 13:22
    smarter commented #11639
  • 12:58
    lihaoyi labeled #11640
  • 12:58
    lihaoyi opened #11640
  • 12:56
    lihaoyi labeled #11639
Guillaume Martres
@smarter
but typescript is all about structural things
Jakub Kozłowski
@kubukoz:matrix.org
[m]
true
I'm envious :D
Guillaume Martres
@smarter
@kubukoz:matrix.org feel free to review the discussion from 4.5 years ago when that change was made, I was also in favor of keeping that behavior: https://github.com/lampepfl/dotty/pull/1550#discussion_r81457947 :)
Jakub Kozłowski
@kubukoz:matrix.org
[m]
do intersection types have methods of both "parts"?
Guillaume Martres
@smarter
well yeah
A & B is a subtype of A and a subtype of B, so of course it has methods of both of them
liskov substitution and all of that
Jakub Kozłowski
@kubukoz:matrix.org
[m]
yeah, it just feels odd that the dual (unions) doesn't have something similar
but I'm okay with this :P
Guillaume Martres
@smarter

Is there a reason why the with part shouldn't work for defs/vals?

given ... with is a special syntax

Jakub Kozłowski
@kubukoz:matrix.org
[m]
right, I'm just curious why it's reserved for givens
Guillaume Martres
@smarter
it's supposed to be the natural way to define givens
given ... = is a given alias

also, I find it utterly confusing that there's no : after with... 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) = ...)

Guillaume Martres
@smarter
@kubukoz:matrix.org actually there was some discussion on introducing a with like syntax for defs see lampepfl/dotty#10637
Jakub Kozłowski
@kubukoz:matrix.org
[m]
I think the reasoning to use different symbols/keywords makes sense, but wasn't indentation based syntax supposed to make the language more consistent? Having different prefixes for blocks kind of defeats the purpose
Braces used to be braces, now there are 4 kinds of braces.
Guillaume Martres
@smarter
Jakub Kozłowski
@kubukoz:matrix.org
[m]
yep
Bjorn Regnell
@bjornregnell
Thanks @smarter for the explanation of why unions don't unite on common methods. I was thinking of the use case of not having to do heavy inheritance, just light-weight unions and then not having to do that verbose matching. 2016 was a while ago and I'm late to the table...
Guillaume Martres
@smarter
I mean, it's something that I could see being reintroduced if enough people wanted it
Bjorn Regnell
@bjornregnell
Aha! Union types are really nice, but the extra match plumbing can be a bit daunting. I'll see if I can minimize my current ongoing stuff to make a compelling use case. Where do I in that case best propose this re-introduced support? Is it a feature request or better a new thread in contributors?
Guillaume Martres
@smarter
either works, contributors is more widely read so it's easier to see if there's support for a proposal there
Bjorn Regnell
@bjornregnell
ok, thanks. I'll see if I can come up with a good starting-point for discussion there tomorrow.
Guillaume Martres
@smarter
but also, we shouldn't encourage the (over)use of unions, if you can make a hierarchy that's probably a better idea
Ciara O'Brien
@CiaraOBrien
is extends A with B with C equivalent to extends A with B & C?
It seems like it should be but I want to make sure
I mostly just use union types to express an AST node that can have children of certain types but not others
e.g.
type ScopeNode = Comment | Expression | ScopeBlock | ListBlock | EmptyBlock
type ListNode  = Comment | Element
which previously you generally would have done by having ScopeNode and ListNode as mixins within the ADT hierarchy
which is very verbose and repeat-yourselfy
Ciara O'Brien
@CiaraOBrien
also this example from a different project, which shows another good use: ad-hoc inclusion of out-of-hierarchy types
/** 
  * 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
Also the hierarchy in question was originally written as opaque types all equaling String, except for Frag which was an Array[Element] IIRC
but then I decided that it would be better for the types to actually exist
for the opaque types, this was only way to implement any sort of hierarchy, even disregarding the inclusion of Strings
The only disadvantage of union types relative to mixins is that under certain situations it results in weaker type inference
Ciara O'Brien
@CiaraOBrien
specifically when going from a 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 mapped with said Chain[ListNode]-taking function, it can't infer the type parameter on commentChain.append[ /*Should infer as ListNode*/](element)
when ListNode is a mixin that Comment and Element both extend, it infers it just fine
but when it's a union type, you need to explicitly use append[ListNode]
I got around the boilerplate by just switching from maps and combines with appending/concating lambdas to custom mappend/mconcat/cappend
even made some operators for maximum aesthetic code
The weakened type inference is slightly annoying but no big deal
Ciara O'Brien
@CiaraOBrien
inference with lambdas is always slightly iffy anyway so
megri
@megri

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.

Bjorn Regnell
@bjornregnell

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?

Bjorn Regnell
@bjornregnell

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

Tomas
@xhudik
a newbie question: what are the differences between enum and trait (or sealed trait)? In scala 3 of course. I couldnt google any useful info on this
Bjorn Regnell
@bjornregnell
@xhudik 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
1 reply
Bjorn Regnell
@bjornregnell
@CiaraOBrien @megri @smarter I've started this thread now here at contributors: https://contributors.scala-lang.org/t/making-union-types-more-useful-by-enhanced-type-inference/4927 so if you want to share your thought from earlier there, it would be great :)