fun with union types

1,099 views
Skip to first unread message

Paul Phillips

unread,
Jun 12, 2013, 4:47:07 AM6/12/13
to scala-l...@googlegroups.com
https://gist.github.com/paulp/5763757

class Cell[T](value: T) {
  var other: T = value
}
object Test {
  def g[T, U](x: T, y: U) = if (util.Random.nextBoolean) x else y
  def f1 = g(1f, "abc")
  def f2 = g(1: Byte, "abc")
  def f3 = g(f1, f2)

  /*** Inferred return types:
  def g[T, U](x: T, y: U): T|U
  def f1: Float|String
  def f2: Byte|String
  def f3: String|Float|Byte
  ***/

  def main(args: Array[String]): Unit = {
    val x = new Cell(f3)
    x.other = 1f          // ok ( Float <:< Byte|Float|String)
    x.other = (1: Byte)   // ok (  Byte <:< Byte|Float|String)
    x.other = "def"       // ok (String <:< Byte|Float|String)
    x.other = 1d          // nope
    // a.scala:15: error: type mismatch;
    //  found   : Double(1.0)
    //  required: Byte|Float|String
    //     x.other = 1d
    //               ^
  }
}

Simon Ochsenreither

unread,
Jun 12, 2013, 4:54:41 AM6/12/13
to scala-l...@googlegroups.com
I. want. this.

Stefan Hoeck

unread,
Jun 12, 2013, 4:59:13 AM6/12/13
to scala-l...@googlegroups.com
Me. Too.

Am Mittwoch, 12. Juni 2013 10:54:41 UTC+2 schrieb Simon Ochsenreither:
I. want. this.

Simon Ochsenreither

unread,
Jun 12, 2013, 5:04:05 AM6/12/13
to scala-l...@googlegroups.com
Union types would solve a lot of our issues, including many cases where LUBs are arbitrarily truncated.
(My proposal regarding inference of List(1, 1.0) as List[AnyVal] vs. List[Double] was a temporary fix until we get proper union types, anyway.)

How does unification work?
Is List[Int] | List[String] <:< List[Int|String]? The other way around? Is it =:=, too?

Paul Phillips

unread,
Jun 12, 2013, 5:31:57 AM6/12/13
to scala-l...@googlegroups.com
On Wed, Jun 12, 2013 at 5:04 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
Is List[Int] | List[String] <:< List[Int|String]? The other way around? Is it =:=, too?

Since each of List[Int] and List[String] individually conforms to List[Int|String], it is true that

  List[Int]|List[String] <:< List[Int|String]

However,

  !(List[Int|String] <:< List[Int]|List[String])

because a List[Int|String] could be e.g. List(1, "abc"), which is neither a list of Ints nor a list of Strings.

Which means of course they aren't =:= either.


Rich Oliver

unread,
Jun 12, 2013, 5:47:55 AM6/12/13
to scala-l...@googlegroups.com
This looks good! However what I most desire though is

1) to have proper enumerations

2) To be able to sub type Enumerations, Numerations and Strings

3) To be able to apply set operations on the basic types and their sub types.

Integer odd & (> 4 )

Double (> 2.0) | (< 4.0)

String (First character is 'd')  & (is > 4 characters long)

Oliver Ruebenacker

unread,
Jun 12, 2013, 8:10:45 AM6/12/13
to scala-l...@googlegroups.com

     Hello,

  Automatic typing plus union types looks to me, at first glance, like entire loss of type safety. Maybe I'll change my mind after another cup of coffee, but for now I'm scared.

  If union types needed to be declared explicitly, then I guess it would be fine. I suppose then you could just use something like Either[A, B].

     Take care
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Head of Systems Biology Task Force at PanGenX (http://www.pangenx.com)
Any sufficiently advanced technology is indistinguishable from magic.

Paul Phillips

unread,
Jun 12, 2013, 8:13:20 AM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 8:10 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
  Automatic typing plus union types looks to me, at first glance, like entire loss of type safety. Maybe I'll change my mind after another cup of coffee, but for now I'm scared.

Mark Hammons

unread,
Jun 12, 2013, 8:26:02 AM6/12/13
to scala-l...@googlegroups.com

How so? Currently, if I make a list like List(BigInt(5),"a",1.5f), type inference will infer the greatest common ancestor as the type for the List, while with union types it would be a List[BigInt|String|Float]. I think that's more descriptive of what the List contains than List[Any].

Oliver Ruebenacker

unread,
Jun 12, 2013, 8:36:20 AM6/12/13
to scala-l...@googlegroups.com

     Hello,

  OK, no total loss of type safety. But it looks like a step towards duck-typing. Or what methods can you call on A|B?

     Take care
     Oliver

Mark Hammons

unread,
Jun 12, 2013, 8:53:45 AM6/12/13
to scala-l...@googlegroups.com

With def x(a: String|Int) you could probably do something like

a match {
case v: String =>v.length

case v: Int => v + v

}

 

if there's no cleanlier way of doing things.

Paul Phillips

unread,
Jun 12, 2013, 9:01:44 AM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 8:53 AM, Mark Hammons <markeh...@gmail.com> wrote:

With def x(a: String|Int) you could probably do something like

a match {
case v: String =>v.length

case v: Int => v + v

}


Also, it's sealed.

(a: String|Int|Double) match {

  case v: String => v.length

  case v: Int    => v + v

}
<console>:10: warning: match may not be exhaustive.
It would fail on the following input: Double

Not that I implemented that last night, but it's a few minutes work.


Ka Ter

unread,
Jun 12, 2013, 9:04:25 AM6/12/13
to scala-l...@googlegroups.com
It would be better if the compiler creates a virtual supertype 'String|Int' that has the intersection of attributes and methods of both types String and Int (see also Curry-Howard-Isomorphism). So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.
Best Regards

Ka Ter

Paul Hudson

unread,
Jun 12, 2013, 9:08:27 AM6/12/13
to scala-l...@googlegroups.com

On 12 June 2013 14:04, Ka Ter <ter....@googlemail.com> wrote:
So, on a 'A|B' you can call only methods that are within A *and* B.

Sounds scary. What is a method that is within A and B? Just because it has the same signature in both A and B doesn't make it the same function. Which one do you call? The one from A or the one from B? (this isn't a problem for methods from the supertype, if any) 

Ka Ter

unread,
Jun 12, 2013, 9:14:59 AM6/12/13
to scala-l...@googlegroups.com
As we speak of methods the concrete method of either A or B is called. 'A|B' is only a dynamic type that masks the object's concrete type. Both methods have the same signature and therefore the same interface but can have different implementations and therefore also different results and side effects. But that is not a problem and not different from the current polymorphic system. Just think of overriding toString() in A and B. If you cast an object of type A to Any and if you call toString, the method of A is used and not the one of Any. With union types there would be no different behavior, but just a more specific supertype that lies between Any and A and B.

Simon Ochsenreither

unread,
Jun 12, 2013, 9:31:00 AM6/12/13
to scala-l...@googlegroups.com, ter....@googlemail.com

As we speak of methods the concrete method of either A or B is called. 'A|B' is only a dynamic type that masks the object's concrete type. Both methods have the same signature and therefore the same interface but can have different implementations and therefore also different results and side effects. But that is not a problem and not different from the current polymorphic system. Just think of overriding toString() in A and B. If you cast an object of type A to Any and if you call toString, the method of A is used and not the one of Any. With union types there would be no different behavior, but just a more specific supertype that lies between Any and A and B.

 Agree. Support for union types means that the type checker can assign more precise types to some expressions.
Especially where we currently have “infinite” types, this would be a huge improvement:

scala> List(1L: java.lang.Long) ++ List(1: java.lang.Integer)
res0: List[Number with Comparable[_ >: Long with Integer <: Number with Comparable[_ >: Long with Integer <: Number with Comparable[_ >: Long with Integer <: Number]]]] = List(1, 1)


I would certainly prefer

res0: List[java.lang.Long|java.lang.Integer] = List(1, 1)

Jan Vanek

unread,
Jun 12, 2013, 9:34:11 AM6/12/13
to scala-l...@googlegroups.com
import Numeric.Implicits._
def neg[T : Numeric](l: List[T]) = l.map(-_)

Will it automagically work for List(1, 2.3, BigDecimal(4))?



John Nilsson

unread,
Jun 12, 2013, 9:46:08 AM6/12/13
to scala-l...@googlegroups.com
So is that a
PartialFunction[String|Int,Int]
or a
PartialFunction[String, Int] | PartialFunction[Int,Int]
?


On Wed, Jun 12, 2013 at 3:01 PM, Paul Phillips <pa...@improving.org> wrote:

Simon Ochsenreither

unread,
Jun 12, 2013, 9:49:41 AM6/12/13
to scala-l...@googlegroups.com, ter....@googlemail.com
In a sense, union types are to explicit type hierarchies what structural types are to interfaces.

One interesting question will be were one should favor union types over explicit type hierarchies, e. g.

trait Pet
class Dog extends Pet
class Cat extends Pet


vs.

class Dog
class Cat
type Pet = Dog | Cat


With these questions solved, union types are a huge simplification over the status quo and fill in the missing piece (we already have intersection types (with) and sum types (Tuples) which work exceptionally well compared to our current library-based union/sum type (Either)).

Andrés Testi

unread,
Jun 12, 2013, 10:06:00 AM6/12/13
to scala-l...@googlegroups.com, ter....@googlemail.com
Another question is what to choice, method overloading or sum types?

def isZero(value: Boolean) = !value
def isZero(value: Int) = value == 0

vs.

def isZero(value: Boolean | Int) = value match {
  case false | 0 => true
  case _ => false

Simon Ochsenreither

unread,
Jun 12, 2013, 10:27:43 AM6/12/13
to scala-l...@googlegroups.com, ter....@googlemail.com

Another question is what to choice, method overloading or sum types?

None of these.

1) Name the methods differently.

2) If that's not possible, use default parameters.

3) If that's not possible, use overloading.

I think Ceylon shows pretty well that trying to replace overloading with sum types has been a disaster, especially because the combinatory explosion of arguments is almost never what people intend.

Rodrigo Cano

unread,
Jun 12, 2013, 10:46:48 AM6/12/13
to scala-l...@googlegroups.com, ter....@googlemail.com
I've just implemented this with a macro so that:

intersectTypes[A] (f: => A)

returns a f => B|C|D...

based on the exit branches of f. Obviously what I did is so limited (not sealed for pattern matching for example, generated type is not a subintance of their common ancestors and a long etc). So I must also say: I. want. this.


--

Lex Spoon

unread,
Jun 12, 2013, 11:03:58 AM6/12/13
to scala-l...@googlegroups.com
Is there a clear explanation of what union types are supposed to help
with? To put a point on it, Scala has positioned itself as a language
intended for mainstream development. Is the feeling at Typesafe that
union types will help with that use case, or is it for furthering some
other aim such as research publications?

I have consulted a few sources now on union types for Scala, and I
haven't found any motivation posted. Specifically, I looked on these
sites:

http://stackoverflow.com/questions/3508077/does-scala-have-type-disjunction-union-types
https://issues.scala-lang.org/browse/SUGGEST-22
https://groups.google.com/forum/?fromgroups#!topic/scala-language/z89k3_8U528
https://issues.scala-lang.org/browse/SI-3749


In all cases, I can only find people saying "gee whiz this is
possible" and "we are working on it". The former is not an adequate
justification for a mainstream language. What's the missing step?

Lex

Oliver Ruebenacker

unread,
Jun 12, 2013, 11:29:32 AM6/12/13
to scala-l...@googlegroups.com

     Hello,

  As suggested, it's duck typing and a loss of type safety.

  val myThing = if(isAvailable(ak47)) ak47 else m4
  myThing.testFire

  Wait? I thought m4 was my shotgun? Turns out it's not???

     Take care
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Simon Ochsenreither

unread,
Jun 12, 2013, 11:32:27 AM6/12/13
to scala-l...@googlegroups.com

  As suggested, it's duck typing and a loss of type safety.

This makes roughly zero sense.

Paul Phillips

unread,
Jun 12, 2013, 11:33:04 AM6/12/13
to scala-l...@googlegroups.com
On Wed, Jun 12, 2013 at 11:03 AM, Lex Spoon <l...@lexspoon.org> wrote:
What's the missing step?

I wrote it last night in a bar; I didn't say it was going anywhere; I rarely get what I want even if I thought it was.

That said, motivations exist, but I'll have to let others elaborate.

Paul Phillips

unread,
Jun 12, 2013, 11:35:22 AM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 11:29 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:
  As suggested, it's duck typing and a loss of type safety.

It's funny how on twitter the people displaying the highest interest are all fervent type-safety lovers. I wonder how it happened.

√iktor Ҡlang

unread,
Jun 12, 2013, 11:36:31 AM6/12/13
to scala-l...@googlegroups.com
On Wed, Jun 12, 2013 at 11:33 AM, Paul Phillips <pa...@improving.org> wrote:

On Wed, Jun 12, 2013 at 11:03 AM, Lex Spoon <l...@lexspoon.org> wrote:
What's the missing step?

I wrote it last night in a bar; I didn't say it was going anywhere; I rarely get what I want even if I thought it was.

It was some classic sliders-and-ice-cream driven development ;)
 

That said, motivations exist, but I'll have to let others elaborate.

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Viktor Klang
Director of Engineering

Twitter: @viktorklang

Oliver Ruebenacker

unread,
Jun 12, 2013, 11:38:33 AM6/12/13
to scala-l...@googlegroups.com

     Hello,

  Automatically deduced union types will allow you to substitute an object with one of an unrelated type, having a completely unrelated method called that just accidentally has the signature matching a call. If that is not loss of type safety, then I don't know what is.

     Take care
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Lex Spoon

unread,
Jun 12, 2013, 11:39:48 AM6/12/13
to scala-l...@googlegroups.com
>> I wrote it last night in a bar; I didn't say it was going anywhere; I
>> rarely get what I want even if I thought it was.
>
>
> It was some classic sliders-and-ice-cream driven development ;)

That is the source of some very good code in the world! -Lex

Simon Ochsenreither

unread,
Jun 12, 2013, 11:40:05 AM6/12/13
to scala-l...@googlegroups.com

Is there a clear explanation of what union types are supposed to help
with? To put a point on it, Scala has positioned itself as a language
intended for mainstream development. Is the feeling at Typesafe that
union types will help with that use case, or is it for furthering some
other aim such as research publications?

ACADEMICS?!?!?!?!??

No, seriously. From my point of view, this is all about beginner-friendliness.

Union types manage to fix the issue that the typechecker can arrive at horrible, unreadable and sometimes infinite types for innocently looking code.

And all these new-fangled “industrial” languages claim it is a great idea, because there is no mismatch anymore between what the user might reasonably expect and what the compiler comes up with, so I think it is not some “let's write papers about it”-stuff:

The type system is based on analysis of "best" or principal types. For every expression, a unique, most specific type may be determined, without the need to analyze the rest of the expression in which it appears. And all types used internally by the compiler are denotable - that is, they can be expressed within the language itself. What this means in practice is that the compiler always produces errors that humans can understand, even when working with complex generic types. The Ceylon compiler never produces error messages with mystifying non-denotable types like Java's List<capture#3-of ?>.

An integral part of this system of denotable principal types is first-class support for union and intersection types. ...

If you want, you can guess from which language I copied that.

Vlad Patryshev

unread,
Jun 12, 2013, 11:47:05 AM6/12/13
to scala-l...@googlegroups.com
Cool! Would be even cooler to have all finite colimits...

Thanks,
-Vlad


--

Paul Phillips

unread,
Jun 12, 2013, 11:47:42 AM6/12/13
to scala-l...@googlegroups.com
One use case which has been mentioned to me is

scala> def untypedActorReceive(msg: Any) = msg match { case s: String => s ; case t: Throwable => t }
untypedActorReceive: (msg: Any)String|Throwable

I find it difficult to favor "Any" as a return type.


Paul Hudson

unread,
Jun 12, 2013, 11:52:59 AM6/12/13
to scala-l...@googlegroups.com

On 12 June 2013 16:40, Simon Ochsenreither <simon.och...@gmail.com> wrote:

The Ceylon compiler...


If you want, you can guess from which language I copied that.


I think I might be able to guess correctly... 


Mark Hammons

unread,
Jun 12, 2013, 11:55:39 AM6/12/13
to scala-l...@googlegroups.com

It's not any more a loss of type safety than overriding a method is. Further, var a: Int|String cannot be assigned any type other than Int or String. It looks more typesafe to me than just having the greatest common ancestor used.

Simon Ochsenreither

unread,
Jun 12, 2013, 11:59:56 AM6/12/13
to scala-l...@googlegroups.com

I think I might be able to guess correctly...

I didn't want to make it toooo hard, I guess. :-)

Rodrigo Cano

unread,
Jun 12, 2013, 12:02:44 PM6/12/13
to scala-l...@googlegroups.com
My case is similar to what Paul described, I have an ESB like library with transports that act as endpoints, different transports support different payloads (for example, JMS supports string, byte array, and java.io.serializable, using different format for those). I encoded what a transport accepts as a typelist, but then when pasing in the function that returns the payload, each individual branch may be of a valid type, but write a match like the one paulp wrote where a branch returns a string, and another returns a java.io.serializable, and you get any, which of course doesn't conform to any of the supported payloads. Thats why I had to write the macro (for several reasons, implicit didn't make the cut).


Oliver Ruebenacker

unread,
Jun 12, 2013, 12:03:31 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

  When you override a method, you are supposed to fulfil the contract of the method overridden. Given a method call, you can find all possible method declarations that may be called.

  If two types just happen to have a method with the same signature, there is no common contract. And you never know whether two methods may be called from the same call.

     Take care
     Oliver

Oliver Ruebenacker

unread,
Jun 12, 2013, 12:07:57 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

  Could some one explain the purpose of that method?

     Take care
     Oliver

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Mark Hammons

unread,
Jun 12, 2013, 12:09:33 PM6/12/13
to scala-l...@googlegroups.com

That contract is not type-safety though. It's just good design.

Simon Ochsenreither

unread,
Jun 12, 2013, 12:23:20 PM6/12/13
to scala-l...@googlegroups.com

When you override a method, you are supposed to fulfil the contract of the method overridden. Given a method call, you can find all possible method declarations that may be called.

If two types just happen to have a method with the same signature, there is no common contract. And you never know whether two methods may be called from the same call.

... and there is no reason why the compiler should allow calling these methods. It can be similar to methods in traits: They need to have a common supertype if you want them to have the same name.

A rule like “the only methods allowed to call on union types are those declared in a common nominal interface of the types in question”

trait Foo { def foo }
class A { def foo; def bar } extends Foo
class B { def foo; def bar } extends Foo

AorB.foo // allowed, common nominal supertype Foo
AorB.bar // not allowed
AorB.toString // allowed, common nominal supertype AnyRef


Anyway, that's all just convenience. One can also specify, that no methods are allowed to be called until the type is free of unions.

Andrés Testi

unread,
Jun 12, 2013, 12:32:34 PM6/12/13
to scala-l...@googlegroups.com
For consistence, I think type intersection should be represented with "&" (like in Java) instead of "with". "with" denotes overriding and intersection, while '&' denotes only intersection. "&" is commutable while "with" is not commutable.

Mark Hammons

unread,
Jun 12, 2013, 12:34:02 PM6/12/13
to scala-l...@googlegroups.com

An actor receives a message and acts on it, returning either a string or a throwable. This is pretty common for actors, and sadly the common return type is Any, which does not at all reflect that in reality the actor will return 1 of two types. The union type makes it clear what kind of responses you can expect from an actor.

John Nilsson

unread,
Jun 12, 2013, 12:38:28 PM6/12/13
to scala-l...@googlegroups.com

Using the suggested & syntax I of course meant
PartialFunction[String, Int] & PartialFunction[Int,Int]

BR
John

Den 12 jun 2013 15:46 skrev "John Nilsson" <jo...@milsson.nu>:
So is that a
PartialFunction[String|Int,Int]
or a
PartialFunction[String, Int] | PartialFunction[Int,Int]
?


On Wed, Jun 12, 2013 at 3:01 PM, Paul Phillips <pa...@improving.org> wrote:

On Wed, Jun 12, 2013 at 8:53 AM, Mark Hammons <markeh...@gmail.com> wrote:

With def x(a: String|Int) you could probably do something like

a match {
case v: String =>v.length

case v: Int => v + v

}


Also, it's sealed.

(a: String|Int|Double) match {

  case v: String => v.length

  case v: Int    => v + v

}
<console>:10: warning: match may not be exhaustive.
It would fail on the following input: Double

Not that I implemented that last night, but it's a few minutes work.


Paul Phillips

unread,
Jun 12, 2013, 12:39:01 PM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 12:32 PM, Andrés Testi <andres....@gmail.com> wrote:
For consistence, I think type intersection should be represented with "&" (like in Java) instead of "with". "with" denotes overriding and intersection, while '&' denotes only intersection. "&" is commutable while "with" is not commutable.

This is definitely on the roadmap (such as it is.)

Andrés Testi

unread,
Jun 12, 2013, 12:43:47 PM6/12/13
to scala-l...@googlegroups.com
Nice!

Paul Phillips

unread,
Jun 12, 2013, 12:47:00 PM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 9:04 AM, Ka Ter <ter....@googlemail.com> wrote:
So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.

This is how it will be implemented; I didn't do it last night only because the relevant compiler code is a nightmare.
 

Andrés Testi

unread,
Jun 12, 2013, 12:57:16 PM6/12/13
to scala-l...@googlegroups.com
Is backward compatibility guaranteed with the new type inference? Old code expecting the most common supertype will not break with the new inferred union type?

Paul Phillips

unread,
Jun 12, 2013, 1:04:54 PM6/12/13
to scala-l...@googlegroups.com

On Wed, Jun 12, 2013 at 12:57 PM, Andrés Testi <andres....@gmail.com> wrote:
Is backward compatibility guaranteed with the new type inference? Old code expecting the most common supertype will not break with the new inferred union type?

Andrés Testi

unread,
Jun 12, 2013, 1:10:43 PM6/12/13
to scala-l...@googlegroups.com
Ok sorry, I thought this was part of a plan to support full DOT calculus. ;-)

Scott Carey

unread,
Jun 12, 2013, 2:33:25 PM6/12/13
to scala-l...@googlegroups.com
I assume that the vision is that you can define a type that is a synonym for a union:

Type Opt[A] = A | Null

How to define methods on these types?  Define them as traits? (making up some syntax here):

Trait Opt[A] extends A|Null {  // or perhaps 'extends AnyVal, with the wrapped value of type A|Null
  def getOrElse(a : A) : A = _ match { case _ : A => _ ; case _ : Null => a }   // expressing my intent, I don't like the syntax
}

val foo : String|Null = if (util.random.NextBoolean) "notNull" else Null
val opt : Opt[String] = foo
foo.getOrElse("wasNull")


I can imagine having Null as a sibling to AnyRef, rather than a subtype --- and java interop simply assumes that the the types are unions with the Null type when interacting from outside Scala (which presumably can recognize one of its own call sites somehow).  Then one could implicitly make unions with null Opt (no boxing!) and force Opt methods to be used before accessing the value.   This means the domain and range of functions on AnyRef does would not necessarily have to include Null, you could prove to the compiler that within some scopes Null can't creep in.
Message has been deleted

Simon Ochsenreither

unread,
Jun 12, 2013, 3:27:45 PM6/12/13
to scala-l...@googlegroups.com
 So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.

I just discussed these things with a few people and I think one important issue is with if and when we can safely exclude structural types. I expect that inferring a union type might break code which led to the inference of a structural type before. We could of course allow all methods which are structurally equivalent, but I think that's a nightmare, and we want a rule which is comparable to the semantics of method overriding in traits.

Therefore, only inferring nominal supertypes seems be the right thing (and those supertypes can of course be structural, but they should be explicit, not ad-hoc).

A different question is whether we actually want to have union types built into the language.
From my perspective, it feels like | is to Either what T? is to Option.

In the case of nullable types vs. Option, I argued strongly for Option, because it's not actually Option, but Option + Either + Try + Validation + ..., => the language doesn't hard-code a single construct into the language, but allows the developer to choose the appropriate API.

It feels weird that I'm now arguing for the reverse position with union types vs. Either, ...

(One could of course suggest that the compiler implements | in terms of Either, e. g. transforming def foo: String|Int = if (true) "" else 0 to def foo: Either[String,Int] = if (true) Left("") else Right(0), but this would be completely incompatible with existing code.)

Scott Carey

unread,
Jun 12, 2013, 4:21:09 PM6/12/13
to scala-l...@googlegroups.com
I'm not exactly sure how my message got deleted; but it was accidental.

My hunch is that tracking of union types gives the ability to track whether a reference can be null or not, as long as all of the ways that Null can be introduced and eliminated can be encoded as a union of a null type and types that cannot be null.

Haoyi Li

unread,
Jun 12, 2013, 4:49:42 PM6/12/13
to scala-l...@googlegroups.com
+1 for the idea in general; apart from being a pain to use (imo), Either[A, B] is too powerful (i.e. general) for many use cases as it allows Left[A] and Right[B] where A and B are not disjoint. For example, it allows Left(s: String) and Right(s: String), which this necessitates the added boilerplate (left and right) to distinguish the two alternatives when the types are the same.

One question I have is: how would union types work (or not work) when you tried doing something like String | Any? or String | String for that matter? Is there a theoretical basis on which this is built upon that I can go read up to enlighten myself?


Jan Vanek

unread,
Jun 12, 2013, 5:00:13 PM6/12/13
to scala-l...@googlegroups.com
http://www.cs.uwm.edu/~boyland/fool2012/papers/fool2012_submission_3.pdf

This is what search for "Dependent Object Types" gives...

Vlad Patryshev

unread,
Jun 12, 2013, 5:06:11 PM6/12/13
to scala-l...@googlegroups.com
Right; and can it be associative (and commutative)?

Thanks,
-Vlad

Jan Vanek

unread,
Jun 12, 2013, 5:31:17 PM6/12/13
to scala-l...@googlegroups.com
On 12.06.2013 23:06, Vlad Patryshev wrote:
Right; and can it be associative (and commutative)?

I believe it is associative and commutative.

If T, U are well formed types, then
T <: T v U (by sub-typing rule <:-v1 taking T1 = T, T2 = U)
U <: T v U (by sub-typing rule <:-v2)
and
U v T <: T v U (by sub-typing rule v-<: taking T1  = U, T2 = T)
Similarly you can prove T v U <: U v T, so commutativity.

For associativity you need T v (U v V) <: (T v U) v V and vice versa. Simplifying:
T <: T v U => T <: (T v U) v V (1)
U <: (T v U) => U <: (T v U) v V (2)
V <: V => V <: (T v U) v V (3)
U v V <: (T v U) v V (4 from 2 and 3)
T v (U v V) <: (T v U) v V (from 1 and 4)
The other way similarly.

Jan Vanek

unread,
Jun 12, 2013, 5:42:34 PM6/12/13
to scala-l...@googlegroups.com
On 12.06.2013 23:06, Vlad Patryshev wrote:
Right; and can it be associative (and commutative)?


Vlad, sorry, you very probably meant that Either can't be associative and commutative, so, ignore my previous mail.

nafg

unread,
Jun 12, 2013, 6:04:49 PM6/12/13
to scala-l...@googlegroups.com
Please? :)

On Wednesday, June 12, 2013 4:59:13 AM UTC-4, Stefan Hoeck wrote:
Me. Too.

Am Mittwoch, 12. Juni 2013 10:54:41 UTC+2 schrieb Simon Ochsenreither:
I. want. this.

Oliver Ruebenacker

unread,
Jun 12, 2013, 6:04:58 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

  That's not really an answer. It seems that method just returns the argument, and I don't see why that is useful.

  Second, I don't see why actors don't just return Either[String, Throwable] or something equivalent.

     Take care
     Oliver

Oliver Ruebenacker

unread,
Jun 12, 2013, 6:37:06 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

  I'm not sure I understand every one's position on this, so let's give an example. Would the following be legal or illegal?

  class Employee { def fire = println("bye") }
  class Missile { def fire = println("boom") }
  val e = new Employee
  val m = new Missile
  val bigGuy = if(util.Random.nextBoolean) e else m
  bigGuy.fire

  If this is legal, I think it is way too dangerous.

  If this is illegal, then the advantage of union over Either[A, B] seems marginal.

     Take care
     Oliver

Please? :)
--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Vlad Patryshev

unread,
Jun 12, 2013, 7:03:21 PM6/12/13
to scala-l...@googlegroups.com
def blindFire(doicare:{def fire:Unit}) = doicare.fire

can do the duck-firing job without any unions involved

Thanks,
-Vlad

Oliver Ruebenacker

unread,
Jun 12, 2013, 7:13:53 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

  I think that is dangerous, too. But at least, if you duck-type arguments, you know it.

  If two branches merge and the most specific supertype is Any/AnyRef/AnyVal, and the value is kept, I would suspect in most cases, that is due to a bug. As it is, most likely it won't compile. If we allow implicit union types and duck-typing, we will have cases where it compiles with unexpected results.

  Duck-typing should only be legal if it is explicit.

     Take care
     Oliver

Scott Carey

unread,
Jun 12, 2013, 7:36:44 PM6/12/13
to scala-l...@googlegroups.com


On Wednesday, June 12, 2013 3:37:06 PM UTC-7, Oliver Ruebenacker wrote:

     Hello,

  I'm not sure I understand every one's position on this, so let's give an example. Would the following be legal or illegal?

  class Employee { def fire = println("bye") }
  class Missile { def fire = println("boom") }
  val e = new Employee
  val m = new Missile
  val bigGuy = if(util.Random.nextBoolean) e else m
  bigGuy.fire

  If this is legal, I think it is way too dangerous.

  If this is illegal, then the advantage of union over Either[A, B] seems marginal.


This has been answered.  There is no duck typing (aside from no discussion with respect to how unions of duck typing types behaves.  quack.).  Only methods on common parents qualify -- the common (perhaps virtual) super type of the two.

Here is the quote:
---------------------------------------

On Wednesday, June 12, 2013 9:47:00 AM UTC-7, Paul Phillips wrote:
On Wed, Jun 12, 2013 at 9:04 AM, Ka Ter <ter....@googlemail.com> wrote:
So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.

This is how it will be implemented; I didn't do it last night only because the relevant compiler code is a nightmare.
-----------------------------------------


One huge non-syntactic benefit over Either or similar is that there is (potentially) no unnecessary boxing.  Creating objects on the heap purely so the runtime can do what the compiler could infer is a waste.

It also seems like it would play well with the other discussion on implicit type widening -- List(1, 2.0) becoming type List[Int|Double] is quite interesting.
 

David Hall

unread,
Jun 12, 2013, 7:38:11 PM6/12/13
to scala-l...@googlegroups.com
I'm going to guess that if/when this feature makes it into Scala, it will come equipped with feature-import training wheels, so you can avoid them as you want.

Actually, Paul... want to sneak in an alias for -language:_ called -Wno-training-wheels? :-)

Oliver Ruebenacker

unread,
Jun 12, 2013, 8:33:53 PM6/12/13
to scala-l...@googlegroups.com

     Hello,

On Wed, Jun 12, 2013 at 7:36 PM, Scott Carey <scott...@gmail.com> wrote:


On Wednesday, June 12, 2013 3:37:06 PM UTC-7, Oliver Ruebenacker wrote:

     Hello,

  I'm not sure I understand every one's position on this, so let's give an example. Would the following be legal or illegal?

  class Employee { def fire = println("bye") }
  class Missile { def fire = println("boom") }
  val e = new Employee
  val m = new Missile
  val bigGuy = if(util.Random.nextBoolean) e else m
  bigGuy.fire

  If this is legal, I think it is way too dangerous.

  If this is illegal, then the advantage of union over Either[A, B] seems marginal.


This has been answered.  There is no duck typing (aside from no discussion with respect to how unions of duck typing types behaves.  quack.).  Only methods on common parents qualify -- the common (perhaps virtual) super type of the two.

Here is the quote:
---------------------------------------

On Wednesday, June 12, 2013 9:47:00 AM UTC-7, Paul Phillips wrote:
On Wed, Jun 12, 2013 at 9:04 AM, Ka Ter <ter....@googlemail.com> wrote:
So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.

This is how it will be implemented; I didn't do it last night only because the relevant compiler code is a nightmare.
-----------------------------------------

  I didn't think it means that, but I'm glad it does.
 

One huge non-syntactic benefit over Either or similar is that there is (potentially) no unnecessary boxing.  Creating objects on the heap purely so the runtime can do what the compiler could infer is a waste.

  Can't we teach the compiler to better optimize Either? Or wouldn't it do that already?

It also seems like it would play well with the other discussion on implicit type widening -- List(1, 2.0) becoming type List[Int|Double] is quite interesting.

  How about List[Number]? I wonder why Scala doesn't have a Number supertype for Int, Long, Double, Float, etc, especially since their APIs overlap greatly? You could do Number.toDouble, but you can't do (Int|Long).toDouble.

     Take care
     Oliver

Scott Carey

unread,
Jun 13, 2013, 12:16:55 AM6/13/13
to scala-l...@googlegroups.com


On Wednesday, June 12, 2013 5:33:53 PM UTC-7, Oliver Ruebenacker wrote:

     Hello,

On Wed, Jun 12, 2013 at 7:36 PM, Scott Carey <scott...@gmail.com> wrote:


On Wednesday, June 12, 2013 3:37:06 PM UTC-7, Oliver Ruebenacker wrote:

     Hello,

  I'm not sure I understand every one's position on this, so let's give an example. Would the following be legal or illegal?

  class Employee { def fire = println("bye") }
  class Missile { def fire = println("boom") }
  val e = new Employee
  val m = new Missile
  val bigGuy = if(util.Random.nextBoolean) e else m
  bigGuy.fire

  If this is legal, I think it is way too dangerous.

  If this is illegal, then the advantage of union over Either[A, B] seems marginal.


This has been answered.  There is no duck typing (aside from no discussion with respect to how unions of duck typing types behaves.  quack.).  Only methods on common parents qualify -- the common (perhaps virtual) super type of the two.

Here is the quote:
---------------------------------------

On Wednesday, June 12, 2013 9:47:00 AM UTC-7, Paul Phillips wrote:
On Wed, Jun 12, 2013 at 9:04 AM, Ka Ter <ter....@googlemail.com> wrote:
So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.

This is how it will be implemented; I didn't do it last night only because the relevant compiler code is a nightmare.
-----------------------------------------

  I didn't think it means that, but I'm glad it does.
 

That is how I interpreted it, but I can't speak for anyone with authority.
 

One huge non-syntactic benefit over Either or similar is that there is (potentially) no unnecessary boxing.  Creating objects on the heap purely so the runtime can do what the compiler could infer is a waste.

  Can't we teach the compiler to better optimize Either? Or wouldn't it do that already?

A simple approach won't work, because you can stuff a null into either side of Either, so unboxed you can't distinguish left from right if the value is null and A and B are both subclasses of AnyRef.  Using null sentinel objects can work, at the expense of more branching in various places.  

This is one reason why I would be excited to see some way to express a type that cannot be null.   Unboxed, you could cast (or implicitly convert) between types that extend from A|B (where each cannot be null) without runtime cost.  Boxed, or with specialized null sentinels per type, you must do more work at runtime.

 

It also seems like it would play well with the other discussion on implicit type widening -- List(1, 2.0) becoming type List[Int|Double] is quite interesting.

  How about List[Number]? I wonder why Scala doesn't have a Number supertype for Int, Long, Double, Float, etc, especially since their APIs overlap greatly? You could do Number.toDouble, but you can't do (Int|Long).toDouble.
 
Perhaps importing an implicit 
  def toDouble(num: Int|Long|Float|Short|Byte|Char) : Double

would be better than a supertype type that is problematic in many other contexts.

Scott Carey

unread,
Jun 13, 2013, 12:23:51 AM6/13/13
to scala-l...@googlegroups.com

A simple approach won't work, because you can stuff a null into either side of Either, so unboxed you can't distinguish left from right if the value is null and A and B are both subclasses of AnyRef.  Using null sentinel objects can work, at the expense of more branching in various places.  

This is one reason why I would be excited to see some way to express a type that cannot be null.   Unboxed, you could cast (or implicitly convert) between types that extend from A|B (where each cannot be null) without runtime cost.  Boxed, or with specialized null sentinels per type, you must do more work at runtime.

I should think through this more before posting.  In the case of Either, there is one other case that is problematic -- if A and B are the same type, there is no way to distinguish them without a witness or box. 

Is A|A the same type as A ?   I believe it is in this case -- which makes the current behavior of Either not completely identical to what you would build with union types.   Returning Either[A,B]  is not isomorphic with returning A|B when the types are the same or the value can be null.   There are strengths and weaknesses for each.

Haoyi Li

unread,
Jun 13, 2013, 12:27:51 AM6/13/13
to scala-l...@googlegroups.com
In general I think you won't be able to have an unboxed, fully generic A | B, because there'll always be the case where A =:= B and you'll need Left[A] and Right[A] to distinguish them. On the other hand, for 99% of use cases, you don't need a fully generic A | B, since (from my experience) usually A and B are disjoint, like Throwable and Seq, or None and Int, or String and Double. Having unboxed (both implementation-wise and syntactically) unions would be great for those.

-Haoyi


--

John Nilsson

unread,
Jun 13, 2013, 6:05:41 AM6/13/13
to scala-l...@googlegroups.com, scala-l...@googlegroups.com
Besides just using Either as is you could allways

type Either[A,B] = Left[A] | Right[B]

No?

BR
John

Skickat från min iPhone

Paul Phillips

unread,
Jun 13, 2013, 8:16:49 AM6/13/13
to scala-l...@googlegroups.com
[In case it requires saying: other people doing something doesn't make it a good idea; other people not doing something doesn't make it a bad idea; but I am not ashamed to draw upon the experiences of others. I would be ashamed if I didn't.]

On Wed, Jun 12, 2013 at 4:21 PM, Scott Carey <scott...@gmail.com> wrote:
My hunch is that tracking of union types gives the ability to track whether a reference can be null or not, as long as all of the ways that Null can be introduced and eliminated can be encoded as a union of a null type and types that cannot be null.

You might want to examine Ceylon, where they took exactly this approach, and since we both call null's type Null and they use | to represent union types, it has identical syntax. In fact for those who are having trouble discerning motivation for union types, you might find some ideas there. Their enthusiasm for them is such that their faq says "It turns out that support for first-class unions and intersections is perhaps the very coolest feature of Ceylon." And "it turns out" implies that this is something they discovered through usage as opposed to assuming it from the beginning, though I have no idea if that's actually the case.


Here is the bit on union types with Null from the spec::


"There is no primitive null in Ceylon. The null value is an instance of the class Null that is not assignable to user-defined class or interface types. An optional type is a union type like Null|String, which may be abbreviated to String?. An optional type is not assignable to a non-optional type except via use of the special-purpose if (exists ... ) construct. Thus, the Ceylon compiler is able to detect illegal use of a null value at compile time. Therefore, there is no equivalent to Java's NullPointerException in Ceylon."

Lex Spoon

unread,
Jun 13, 2013, 12:02:11 PM6/13/13
to scala-l...@googlegroups.com
Thanks for the pointer to the Ceylon FAQ. It elevates the conversation
for sure. I have now read that page, as well as two slide decks I
found motivating the language and its type system. Certainly it is
pleasant reading someone about else's efforts to make a small yet
general language that runs in the Java ecosystem.

Reading that FAQ makes me more worried, not less, about union types
for Scala. The bulk of the rationale is that unions provide for kinds
of expressiveness that, I will agree, you really do want in a
practical programming language. My concern is that, unlike with
Ceylon, Scala already includes support for all of those features.
Thus, union types will provide a second way to do things that you can
already do.

Specifically, the FAQ lists the following benefits of including union types:

1. "a sane approach to generic type argument inference". From a user's
point of view, rather than a compiler implementer's, Scala is more
than fine on this front. In fact, I think Scala already tries too
hard; at some point the user should say what they mean.

2. "optional values (things which can be null)". Scala has optional
values. Scala also has null.

3. "they help make overloading unnecessary". Scala has overloading.

4. "they make it easy to reason about algebraic/enumerated types".
Scala has had algebraic types since the beginning. In fact, Pizza had
algebraic types.


As an aside, I would dispute these choices even for a language from
scratch. I currently feel that static null checking is like static
exception checking: it increases the size of source code, yet the
advantage is ambiguous. I think overloading is fine, certainly better
than union types plus type case. For algebraic types, I believe it's
better to have a nominal type "List" than to make users write the
structural "Cons | Nil" all over the place.

In parting, let me share a quote from Ceylon's own Gavin King:

"We're trying to live with a lot less language features than Scala.
Leaving stuff out is, in and of itself, often a Good Thing." FROM:
http://in.relation.to/Bloggers/Ceylon#attachment1


He probably doesn't realize it, but he sounds like a Scalist!

Lex Spoon

Rex Kerr

unread,
Jun 13, 2013, 12:04:04 PM6/13/13
to scala-l...@googlegroups.com
Note that actually allowing code like
  def lengthOf[T](x: String | Array[T]) = x.length
comes with some not-insurmountable but nontrivial problems.  One could use structural typing on all called members, or one could create
  object String_Union_ArrayT {
    def length(x: AnyRef) = x match {
      case s: String => s.length
      case a: Array[_] => a.length
    }
  }
and have x.length be syntactic sugar for String_Union_ArrayT.length(x).  Or one could insert that pattern-match inline.  Or one could have some sort of hybrid strategy that pattern matched for small numbers of alternatives but reverted to reflection once the number of alternatives was so huge that reflection was cheaper (measured by some compromise between code size and speed).

Anyway, what needs to be proven about union types to know whether or not this is a good idea theoretically (on top of the pragmatic issues of maybe not wanting Int | Boolean | String | Unit | ... when you really just meant Any)?

  --Rex



On Wed, Jun 12, 2013 at 9:04 AM, Ka Ter <ter....@googlemail.com> wrote:
It would be better if the compiler creates a virtual supertype 'String|Int' that has the intersection of attributes and methods of both types String and Int (see also Curry-Howard-Isomorphism). So, on a 'A|B' you can call only methods that are within A *and* B. If both, A and B, share a supertype (like Any or AnyVal), 'A|B' also contains the supertypes' methods as 'A|B' is a subtype of that supertype.
Best Regards

Ka Ter

      Am 12.06.2013 14:53, schrieb Mark Hammons:

With def x(a: String|Int) you could probably do something like

a match {
case v: String =>v.length

case v: Int => v + v

}

 

if there's no cleanlier way of doing things.

 

On Wednesday, June 12, 2013 08:36:20 AM Oliver Ruebenacker wrote:


     Hello,

  OK, no total loss of type safety. But it looks like a step towards duck-typing. Or what methods can you call on A|B?

     Take care
     Oliver

On Wed, Jun 12, 2013 at 8:26 AM, Mark Hammons <markeh...@gmail.com> wrote:

How so? Currently, if I make a list like List(BigInt(5),"a",1.5f), type inference will infer the greatest common ancestor as the type for the List, while with union types it would be a List[BigInt|String|Float]. I think that's more descriptive of what the List contains than List[Any].

 

 

On Wednesday, June 12, 2013 08:10:45 AM Oliver Ruebenacker wrote:


     Hello,

  Automatic typing plus union types looks to me, at first glance, like entire loss of type safety. Maybe I'll change my mind after another cup of coffee, but for now I'm scared.

  If union types needed to be declared explicitly, then I guess it would be fine. I suppose then you could just use something like Either[A, B].

     Take care
     Oliver

On Wed, Jun 12, 2013 at 5:47 AM, Rich Oliver <rzi...@gmail.com> wrote:

This looks good! However what I most desire though is

1) to have proper enumerations

2) To be able to sub type Enumerations, Numerations and Strings

3) To be able to apply set operations on the basic types and their sub types.

Integer odd & (> 4 )

Double (> 2.0) | (< 4.0)

String (First character is 'd')  & (is > 4 characters long)



On Wednesday, June 12, 2013 9:47:07 AM UTC+1, Paul Phillips wrote:

https://gist.github.com/paulp/5763757


class Cell[T](value: T) {

var other: T = value

}

object Test {

def g[T, U](x: T, y: U) = if (util.Random.nextBoolean) x else y

def f1 = g(1f, "abc")

def f2 = g(1: Byte, "abc")

def f3 = g(f1, f2)

 

/*** Inferred return types:

def g[T, U](x: T, y: U): T|U

def f1: Float|String

def f2: Byte|String

def f3: String|Float|Byte

***/

 

def main(args: Array[String]): Unit = {

val x = new Cell(f3)

x.other = 1f // ok ( Float <:< Byte|Float|String)

x.other = (1: Byte) // ok ( Byte <:< Byte|Float|String)

x.other = "def" // ok (String <:< Byte|Float|String)

x.other = 1d // nope

// a.scala:15: error: type mismatch;

// found : Double(1.0)

// required: Byte|Float|String

// x.other = 1d

// ^

}

}

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 




--

Head of Systems Biology Task Force at PanGenX (http://www.pangenx.com)
Any sufficiently advanced technology is indistinguishable from magic.

--

You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

--
Head of Systems Biology Task Force at PanGenX (http://www.pangenx.com)
Any sufficiently advanced technology is indistinguishable from magic.

--

You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Paul Phillips

unread,
Jun 13, 2013, 12:38:45 PM6/13/13
to scala-l...@googlegroups.com
On Thu, Jun 13, 2013 at 12:02 PM, Lex Spoon <l...@lexspoon.org> wrote:
1. "a sane approach to generic type argument inference". From a user's
point of view, rather than a compiler implementer's, Scala is more
than fine on this front. In fact, I think Scala already tries too
hard; at some point the user should say what they mean.

I disagree. I would say if it appears this way, you have adapted extremely well to scala's type inference failings, such that they are no longer visible. That is a smart thing to do, but it can leave you seeing things a little differently. There is a long list of things you should and shouldn't do if you want to see your types inferred, and if you're involving implicit search in any way then god help you. Type constructor inference, multiple parameter lists, type parameters which depend on others, type parameters which depend on implicit arguments, none of these things do a decent job of generic type argument inference. Others have gone into far more detail on this.

None of this is to say union types are a good idea - especially for the reasons you mention (interaction with existing features which overlap) I am not at all sure that they are.

3. "they help make overloading unnecessary". Scala has overloading.

It does. It also causes a lot of problems.

Simon Ochsenreither

unread,
Jun 13, 2013, 2:01:16 PM6/13/13
to scala-l...@googlegroups.com
1. "a sane approach to generic type argument inference". From a user's
point of view, rather than a compiler implementer's, Scala is more
than fine on this front. In fact, I think Scala already tries too
hard; at some point the user should say what they mean.

Sometimes I really wonder if people just ignore what I write ...

Scott Carey

unread,
Jun 13, 2013, 6:46:35 PM6/13/13
to scala-l...@googlegroups.com

On Thursday, June 13, 2013 9:02:11 AM UTC-7, lexspoon wrote:
2. "optional values (things which can be null)". Scala has optional
values. Scala also has null.


No, Scala does not have this.   Option is not 'things which can be null', it is a Monad that represents either None or all possible values of a type A, where Null is a possible value in A.

In other words, the following exists:  Some(null)

A type that represents whether something is null or not is not Option, such a beast would not be able to contain null distinct from None.  You can use Option to simulate this manually, but the type system does not guarantee that the contents are not null, it wastes space on the heap, and severely slows down performance versus explicit null checks.

Haoyi Li

unread,
Jun 13, 2013, 6:55:05 PM6/13/13
to scala-l...@googlegroups.com
It also adds an annoying Some(X) to distinguish it from None, even if X and None are disjoint. Option is like Either that way: more general (can contain any X, even if X is not disjoint with None) but with added syntactic boilerplate and heap allocation v.s. Union types (or passing Any and casting)


Sassa Nf

unread,
Jun 14, 2013, 6:42:57 AM6/14/13
to scala-l...@googlegroups.com
A | B is isomorphic to Either[A, B]

The difference is that Either as class has some methods, like fold(left, right), which A | B as type doesn't.


Alex

On Wednesday, 12 June 2013 17:03:31 UTC+1, Oliver Ruebenacker wrote:

     Hello,

  When you override a method, you are supposed to fulfil the contract of the method overridden. Given a method call, you can find all possible method declarations that may be called.

  If two types just happen to have a method with the same signature, there is no common contract. And you never know whether two methods may be called from the same call.

     Take care
     Oliver

On Wed, Jun 12, 2013 at 11:55 AM, Mark Hammons <markeh...@gmail.com> wrote:

It's not any more a loss of type safety than overriding a method is. Further, var a: Int|String cannot be assigned any type other than Int or String. It looks more typesafe to me than just having the greatest common ancestor used.

 

On Wednesday, June 12, 2013 11:38:33 AM Oliver Ruebenacker wrote:


     Hello,

  Automatically deduced union types will allow you to substitute an object with one of an unrelated type, having a completely unrelated method called that just accidentally has the signature matching a call. If that is not loss of type safety, then I don't know what is.

     Take care
     Oliver

On Wed, Jun 12, 2013 at 11:35 AM, Paul Phillips <pa...@improving.org> wrote:


On Wed, Jun 12, 2013 at 11:29 AM, Oliver Ruebenacker <cur...@gmail.com> wrote:

  As suggested, it's duck typing and a loss of type safety.


It's funny how on twitter the people displaying the highest interest are all fervent type-safety lovers. I wonder how it happened.


--
You received this message because you are subscribed to the Google Groups "scala-language" group.

To unsubscribe from this group and stop receiving emails from it, send an email to scala-language+unsubscribe@googlegroups.com.


For more options, visit https://groups.google.com/groups/opt_out.
 
 

--
Head of Systems Biology Task Force at PanGenX (http://www.pangenx.com)
Any sufficiently advanced technology is indistinguishable from magic.

--

You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Simon Ochsenreither

unread,
Jun 14, 2013, 7:29:07 AM6/14/13
to scala-l...@googlegroups.com

A | B is isomorphic to Either[A, B]

The difference is that Either as class has some methods, like fold(left, right), which A | B as type doesn't.

I think this is the most important question from a user perspective, especially because we basically have it the other way around for T? v. Option[T].
In my opinion, having the shared members directly accessible makes union types more useful than nullable types (where the only useful operation is testing for null): therefore the need for having an additional API on top of it is not as strong as for T? v. Option[T].

Andrés Testi

unread,
Jun 14, 2013, 8:41:27 AM6/14/13
to scala-l...@googlegroups.com
Implicit classes could add extra methods

implicit class UnionOption[A, Op :< A | Null](option: Op)  {

   def getOrElse(a: => A): A = ...
   def foreach(f: a => Unit): Unit = ...
   def map[B](f: a => B): B | Null = ...
   ....

Simon Ochsenreither

unread,
Jun 14, 2013, 8:58:57 AM6/14/13
to scala-l...@googlegroups.com

Implicit classes could add extra methods

I think that would cause more issues than it would solve, considering that one can expect that union types will expose the full API of the lowest common supertype of the elements, unlike wrapper types.

Simon Ochsenreither

unread,
Jun 14, 2013, 9:19:20 AM6/14/13
to scala-l...@googlegroups.com
I built a “union type quiz”, freely inspired by the “programming language checklist”.¹

You can find it here: https://gist.github.com/soc/5776653

I think it might help to understand individual positions more clearly.


¹ http://colinm.org/language_checklist.html or http://mashedtaters.net/var/language_checklist.php

Oliver Ruebenacker

unread,
Jun 14, 2013, 9:25:34 AM6/14/13
to scala-l...@googlegroups.com

     Hello,

On Fri, Jun 14, 2013 at 9:19 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
I built a “union type quiz”, freely inspired by the “programming language checklist”.¹

You can find it here: https://gist.github.com/soc/5776653

I think it might help to understand individual positions more clearly.

  This is awesome!

     Take care
     Oliver

Vlad Patryshev

unread,
Jun 14, 2013, 10:13:19 AM6/14/13
to scala-l...@googlegroups.com
>A | B is isomorphic to Either[A, B]

I don't think so. | is associative and commutative; Either is neither.

Thanks,
-Vlad

Haoyi Li

unread,
Jun 14, 2013, 10:14:05 AM6/14/13
to scala-l...@googlegroups.com
A | B is isomorphic to Either[A, B]

I don't think that's true? You can use Either[Unit, Unit] to represent two distinct values, while Unit | Unit cannot. This is a contrived example, but in general, Either[A, B] is more powerful than A | B in that it can distinguish Left[A] and Right[B] even when A and B are not disjoint, while A | B cannot. This flexibility comes with additional syntactic (Left(a) and Right(b) vs a and b) and performance overhead (boxing).


--

Vlad Patryshev

unread,
Jun 14, 2013, 10:15:55 AM6/14/13
to scala-l...@googlegroups.com
Cool, and unusual. What are we supposed to do with it?

Thanks,
-Vlad


Sassa Nf

unread,
Jun 14, 2013, 11:06:27 AM6/14/13
to scala-l...@googlegroups.com
There probably is confusion about the meaning of "|" on my part. A coproduct A | A exists. But union type is not exactly a co-product.


2013/6/14 Haoyi Li <haoy...@gmail.com>

--
You received this message because you are subscribed to a topic in the Google Groups "scala-language" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/scala-language/J8LpYDmrOCg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to scala-languag...@googlegroups.com.

Rex Kerr

unread,
Jun 14, 2013, 3:00:54 PM6/14/13
to scala-l...@googlegroups.com
These are great questions!

I am a huge fan of union types, though I have not thought about them as much as I'd have liked.

Still, it seems to me that there is an issue where two concerns get conflated and it is the conflation that causes more confusion than the underlying structure.

(Also, note that you could never infer a union type unless explicitly annotated as such.  This would let you have all the safety you want if you ask for it, but otherwise not have to deal with a lot of complex issues that surround making it generally useful.  You'd have to explicitly pattern match out of it to do anything, but that's often worth it.)

First, when you ask about explosion of union types you must ask both
  (1) Can the _compiler_ handle the explosion?
  (2) Can the _programmer_ handle the explosion?
As is typical with explosions, in the general case compilers are much better than programmers (but if there is some complex structure, the programmer may do better than the compiler by working at a higher level of abstraction).

In any case, it seems to me that it is not necessary for both of these concerns to be answered in the same way.  Let's try to explode:

  def f(b: Boolean) = if (b) 2 else "salmon"
  def g[T](t: T, b: Boolean) = if (b) List(t, t) else Some(t)
  def h[T](t: T, b: Boolean) = if (b) Array(t) else Left(t)

Now we (by hand) infer the types:

  f returns Int | String
  g(f,) returns List[Int | String] | Some[Int | String]
  h(g(f,),) returns Array[List[Int | String] | Some[Int | String]] | Left[List[Int | String] | Some[Int | String]]

Ack!  Except, wait.  We know the pattern isn't that complicated.

  type fType = Int | String
  type gType = List[fType] | Some[fType]
  type hType = Array[gType] | Left[gType]

There's no need to expand the tree fully in the compiler.  Just leave it as a tree and do tree-tree tests for subtyping.  E.g. Array[List[Int | String] | Some[Int]] is a subtype because the (straightforward) recursive check matches.

Now, if you are trying to write this in a type-safe way, you might have impossible amounts of typing to do.  But not if you allow capture of types (probably don't want this syntax, but):

  val x: ?T = h(g(f(b1),b2),b3)

Now you can use T wherever you need to refer to that (nontrivial) type.

So I don't think explosion is an insurmountable problem.

There may be other problems; I haven't fully worked through how to get expressions in disjunctive normal form.  I think one does want DNF not CNF if stacking traits is common and unions are less so, as it avoids explosion (just imagine List | Vector in CNF!).  The basics are easy enough--it's just distributivity (pretending here that "with" is symmetric, which sometimes it is and sometimes it is not):

  (A | B) with (C | D)
  ((A | B) with C) | ((A | B) with D)
  ((A with C) | (B with C)) | ((A with D) | (B with D))
  (A with C) | (B with C) | (A with D) | (B with D)

But once you get into variance it gets trickier, mostly because you either get tripped up a lot on things like the difference between Option[Int | String] and Option[Int] | Option[String], or you try to find ways to paper over the difference and realize that it's not at all straightforward to do anything sane (even if you introduce a distributivity notation to augment co/contra/in-variance).

  --Rex

P.S. To answer some of the questions

9. What should be the inferred type of
if (true) Some(1) else None?
 
(X) Option[Int]
( ) Some[Int]|None
( ) Other: ______________________________

because Some[Int] | None has as LUB Option[Int] and that is exhausted by Some[Int] | None.
 
 
10. What should be the inferred type of
if (true) Nil else List(1).asInstanceOf[::[Int]]?
 
(X) List[Int]
( ) Nil|::[Int]
( ) Other: ______________________________

because, again, Nil | ::[Int] is exhaustive.

 
11. When should a nominal type be preferred to a more precise union type?
 
( ) Always
( ) Never
(X) Only if the element types enumerate all subclasses/subobjects of a sealed supertype
( ) Other: ______________________________

but there should be an easy way to downgrade to the LUB, and the compiler might
elect to only tell you about the LUB if the union type is messy, unless there is
a crucial distinction between the two in an error (for instance).

 
 
12. When should a more precise structural type be preferred to a union type?
 
( ) Always
( ) Never
(X) Only if the structural type is specified explicitly
( ) Other: ______________________________
because structural types are not more precise.  They are _less_ precise because they are unsealed.
  class K { def foo = "salmon" }
  class Q { def foo = "perch" }
  def takesFoo(x : { def foo: String }) = ???
  def takesKQ(x: K | Q) = ???

  class C { def foo = "cod" }
  def takesFoo(new C)   // works
  def takesKQ(new C)  // compile-time error 
 

13. Given the following declarations ...
 
trait T { def foo: Int }
class A extends T { def foo: Int = ???; def bar: Int = ??? }
class B extends T { def foo: Int = ???; def bar: Int = ???; def bippy: Int = ??? }
 
... which members are allowed to be called on an instance aOrB of type A|B?
 
[X] aOrB.toString
[X] aOrB.foo
[?] aOrB.bar
[ ] aOrB.bippy

You should never lose the capabilities of the LUB. Whether the compiler should fill
in a pattern match for you is arguable. I would tend towards "yes", as it's hardly
the only performance gotcha in town.

 
 
14. How will inference of union types interact with structural types?
_Unions of structural types will just be unions__
_and the LUB will contain the intersection of ___
_members of those types._________________________

so this will work (which it does not now):

scala> val x = new AnyRef { def foo: Int = 23; def bar = 24 }
x: AnyRef{def foo: Int; def bar: Int} = $anon$1@7aa3107c

scala> val y = new AnyRef { def foo: Int = 42; def baz = 41 }
y: AnyRef{def foo: Int; def baz: Int} = $anon$1@3d758b59

scala> (if (b) x else y).foo
<console>:12: error: value foo is not a member of Object
(if (b) x else y).foo

 
15. Given the following definitions ...
 
val x: AnyRef { def foo: Int } = null
val y: AnyRef { def foo: Int } = null
 
... should it be allowed to call xOrY.foo?
 
(X) Yes
( ) No
( ) Other: ______________________________
 
 
16. Given the following definitions ...
 
val x = new AnyRef { def foo: Int = 23}
val y = new AnyRef { def foo: Int = 42}
 
... should it be allowed to call xOrY.foo?
 
(X) Yes
( ) No
( ) Other: ______________________________
 
 
17. Will your choice from above break existing, valid code?
 
( ) Yes
( ) Yes, but it doesn't matter because: ______________________________
(X) No, because: _They work now and still will________________________
( ) Maybe?
 

Simon Ochsenreither

unread,
Jun 14, 2013, 3:02:06 PM6/14/13
to scala-l...@googlegroups.com

Cool, and unusual. What are we supposed to do with it?

Fill it out and link to it if you discuss some details, so that everyone knows where you are coming from.
You could also use the numbers in the quiz to refer to a specific topic.

Whatever, have fun! :-)

Jan Vanek

unread,
Jun 14, 2013, 3:42:47 PM6/14/13
to scala-l...@googlegroups.com
On 14.06.2013 21:00, Rex Kerr wrote:
These are great questions!

10. What should be the inferred type of
if (true) Nil else List(1).asInstanceOf[::[Int]]?
 
(X) List[Int]
( ) Nil|::[Int]
( ) Other: ______________________________
because, again, Nil | ::[Int] is exhaustive.

I thought that when DOT is implemented, the List and HList could sort-of be unified and the definition of :: could be

final case class ::[H, T](private var hd: T, private[scala] var tl: List[T]) extends List[H|T] { ... }

So the derived type of List(1, 2.3) would be ::[Int, ::[Double, Nil.type]] which is <: of List[Int | Double].

Regards,
Jan

Jan Vanek

unread,
Jun 14, 2013, 3:49:13 PM6/14/13
to scala-l...@googlegroups.com
On 14.06.2013 21:42, Jan Vanek wrote:
On 14.06.2013 21:00, Rex Kerr wrote:
These are great questions!

10. What should be the inferred type of
if (true) Nil else List(1).asInstanceOf[::[Int]]?
 
(X) List[Int]
( ) Nil|::[Int]
( ) Other: ______________________________
because, again, Nil | ::[Int] is exhaustive.

I thought that when DOT is implemented, the List and HList could sort-of be unified and the definition of :: could be

final case class ::[H, T](private var hd: T, private[scala] var tl: List[T]) extends List[H|T] { ... }

final case class ::[H, T](private var hd: H, private[scala] var tl: List[T]) extends List[H|T] { ... }

Sorry, typo.

Simon Ochsenreither

unread,
Jun 14, 2013, 4:23:58 PM6/14/13
to scala-l...@googlegroups.com
I think we have a lot of agreement: https://gist.github.com/soc/5784587

Mark Derricutt

unread,
Jun 14, 2013, 4:58:12 PM6/14/13
to scala-l...@googlegroups.com
Andr�s Testi wrote:
>
> implicit class UnionOption[A, Op :< A | Null](option: Op) {
>
> def getOrElse(a: => A): A = ...
> def foreach(f: a => Unit): Unit = ...
> def map[B](f: a => B): B | Null = ...
> ....
> }
>

This isn't the exact post I was wanting to reply to but I can't find it
off hand, but mentioning map here brings this back into my mind.

If you have:

val items: List[A]|List[B] = .....

what signature does map/flatMap have? Is it a: (A|B) => C

or is this mute - since the methods have different signatures and thus
wouldn't appear on the synthetic type, this would mean none of these
calls would be able to be used in for comprehensions.

Mark

Jan Vanek

unread,
Jun 14, 2013, 5:17:30 PM6/14/13
to scala-l...@googlegroups.com
On 14.06.2013 21:49, Jan Vanek wrote:
On 14.06.2013 21:42, Jan Vanek wrote:
On 14.06.2013 21:00, Rex Kerr wrote:
These are great questions!

10. What should be the inferred type of
if (true) Nil else List(1).asInstanceOf[::[Int]]?
 
(X) List[Int]
( ) Nil|::[Int]
( ) Other: ______________________________
because, again, Nil | ::[Int] is exhaustive.

I thought that when DOT is implemented, the List and HList could sort-of be unified and the definition of :: could be

final case class ::[H, T](private var hd: T, private[scala] var tl: List[T]) extends List[H|T] { ... }


Apologies, it would have to be something like:

final case class ::[H, U, T <: List[U]](private var hd: H, private[scala] var tl: T) extends List[H|U] { ... }

So the derived type of List(1, 2.3) would be ::[Int, Double, ::[Double, Nothing, Nil.type]] which is <: of List[Int | Double].

But List.apply is defined like:

  override def apply[A](xs: A*): List[A] = xs.toList

This means the derived type of List(1, 2.3) would be at most List[Int | Double].

Adding type parameters to :: class would be breaking, so it is not so good idea, after all.

Eric Tanter

unread,
Jun 14, 2013, 6:02:16 PM6/14/13
to scala-l...@googlegroups.com
+1 on this

-- Éric


On Jun 14, 2013, at 4:23 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:

> I think we have a lot of agreement: https://gist.github.com/soc/5784587
>

Jan Vanek

unread,
Jun 14, 2013, 8:20:53 PM6/14/13
to scala-l...@googlegroups.com
This is a very good question. According the DOT rules for union and
intersection with regard to method members the type of map would have to
be ((A=>C)&(B=>C))List[C], i.e. map(f: (A=>C)&(B=>C)): List[C]. Now the
(A|B)=>C is <: of (A=>C)&(B=>C), so it should be possible to call the
map with f: (A|B)=>C.

I vaguely remember a discussion from which uncertainty remains. Perhaps
it was about methods with multiple parameters, or methods from different
classes, I can't remember.

But List[A]|List[B] <: List[A|B], and in List[A|B] obviously the map is
with (f: (A|B) => C). So at least (items: List[A|B]).map(f) must work,
but I really don't believe the type ascription is necessary, it would be
ugly.

For the reference the derivation of the type. Say the X=>Y is Fn[-X,+Y].
The members of List[A]|List[B] is the intersection of members, and for
methods map: (Fn[A,C])List[C] and map: (Fn[B,C])List[C] the resulting
type is (Fn[A,C] & Fn[B,C])List[C]. Now A <: A|B, so Fn[A|B,C] <:
Fn[A,C], because of contravariant X in Fn. Similarly Fn[A|B,C] <:
Fn[B,C], so Fn[A|B,C] <: Fn[A,C]&Fn[B,C]. I'm uncertain about when/if
Fn[A,C]&Fn[B,C] can be reduced to Fn[A|B,C], but for the map to work it
doesn't seem necessary.

I find following mnemonics helpful:

L1 <: T <: U1
L2 <: T <: U2
-------------------
L1|L2 <: T <: U1&U2

Regards,
Jan

> Mark
>

P.S. This is in fact a wild speculation. I guess Scala team is busy
formulating/specifying this and for anything definitive we have to wait.

Lex Spoon

unread,
Jun 16, 2013, 12:06:07 PM6/16/13
to scala-l...@googlegroups.com
On Thu, Jun 13, 2013 at 12:38 PM, Paul Phillips <pa...@improving.org> wrote:
> I disagree. I would say if it appears this way, you have adapted extremely
> well to scala's type inference failings, such that they are no longer
> visible.

I've been pondering this statement, because there's a grain of truth
in it. It's true that I think about type inference when I write code.
However, it's not true that I contemplate the specifics of any
particular type inference algorithm. In particular for Scala, people
such as yourself are always making the inference algorithms better!

Here's a small example that I find jarring:

val output =
if (writable(settings.out))
settings.out
else
"/dev/null"

The problem is that this will always type check no matter what
settings.out is. This isn't an obscure issue with the type inferencer.
It's not a failing. It's a fundamental limitation of languages that
have subtyping.

In this case, you really ought to write "val output: String" so that
the compiler can do a useful type check for you. Even if settings.out
is a string today, real-world programmers are always changing the
types of things and then seeing what type errors result from it. Help
those future developers out, and write down the expected type.

The same goes for list literals:

val path = List(
"/etc/scpaths",
"/usr/local/sc/etc/paths",
settings.paths)

You should write List[String] here. Again, some future programmer is
going to change settings.paths to be a File instead of String. We want
that programmer to get a compile error.

Lex Spoon

John Nilsson

unread,
Jun 16, 2013, 12:22:51 PM6/16/13
to scala-l...@googlegroups.com
On Sun, Jun 16, 2013 at 6:06 PM, Lex Spoon <l...@lexspoon.org> wrote:
Here's a small example that I find jarring:

    val output =
        if (writable(settings.out))
            settings.out
        else
            "/dev/null"
Is the problem with the type of the expression, or is it with the contract of output?
I'm assuming that "output" is passed on to something which expects String?
I can see a number of ways to address this in the API as such.
You allready pointed out "val output: String"

Another way would be a trait
trait OutputSetting { def output: String }
val setting = new OutputSetting { def output = ... }

Or if you need to split the configuration, use a builder.
setting.Copy(output = ...)

 BR,
John

Paul Phillips

unread,
Jun 16, 2013, 8:47:53 PM6/16/13
to scala-l...@googlegroups.com

On Sun, Jun 16, 2013 at 9:06 AM, Lex Spoon <l...@lexspoon.org> wrote:
> I've been pondering this statement, because there's a grain of truth in it.

I was hoping for at least a teaspoon. A grain... you can barely see a grain. When "grain" appears in metaphor, it is most likely standing in for "the smallest imaginable thing which isn't nothing." How about a granule. It may be no bigger, but some people won't realize it.

Here's a small example that I find jarring:

    val output =
        if (writable(settings.out))
            settings.out
        else
            "/dev/null"

The problem is that this will always type check no matter what
settings.out is. This isn't an obscure issue with the type inferencer.
It's not a failing. It's a fundamental limitation of languages that
have subtyping.

It's not clear that it's not a failing. If we agree that it's undesirable, then we're on the road to it being a failing. There is definitely an alternative available, which gives us some momentum. So the question is whether the cure is worse than the disease.

scala> def ifThenElse[A](cond: Boolean, ifp: => A)(elsep: => A): A = if (cond) ifp else elsep
ifThenElse: [A](cond: Boolean, ifp: => A)(elsep: => A)A

scala> def settings = "abc"
settings: String

scala> val output = ifThenElse(util.Random.nextBoolean, settings)("/dev/null")
output: String = abc

scala> def settings = new scala.tools.nsc.Settings
settings: scala.tools.nsc.Settings

scala> val output = ifThenElse(util.Random.nextBoolean, settings)("/dev/null")
<console>:9: error: type mismatch;
 found   : String("/dev/null")
 required: scala.tools.nsc.Settings
       val output = ifThenElse(util.Random.nextBoolean, settings)("/dev/null")

My feeling that we shouldn't infer every type which can be inferred just because we can infer it seems to be my exclusive property. It is also fun to poke around at related situations, where we encounter inference behavior which might charitably be described as idiosyncratic.

scala> val output = Set(Nil) + 5
<console>:7: error: type mismatch;
 found   : Int(5)
 required: scala.collection.immutable.Nil.type
       val output = Set(Nil) + 5
                               ^

scala> val output = Set(Nil) + "abc"
output: String = Set(List())abc

scala> val output = Set(Nil) ++ Set(5)
output: scala.collection.immutable.Set[Any] = Set(List(), 5)

It is loading more messages.
0 new messages