views:

369

answers:

3

I just recently encountered the term "Weak Conformance" (in retronym's answer to How to set up implicit conversion to allow arithmetic between numeric types?).

What is it?

+2  A: 

According to Scala lang spec 2.8:
http://www.scala-lang.org/archives/downloads/distrib/files/nightly/pdfs/ScalaReference.pdf

3.5.3 Weak Conformance
In some situations Scala uses a more genral conformance relation. A type S weakly conforms to a type T , written S <:w T , if S <: T or both S and T are primitive number types and S precedes T in the following ordering.
Byte <:w Short
Byte <:w Character
Short <:w Int
Int <:w Long
Long <:w Float
Float <:w Double
A weak least upper bound is a least upper bound with respect to weak conformance.

Sandor Murakozi
I'm pretty sure `Char` conforms to `Int` as well.
Daniel
+3  A: 

To complete Sandor's answer, that new feature in 2.8 is still being baked (and fixed).

In this thread, Esser uncovers a nasty side-effect:

scala> val a= 10 
a: Int = 10 

scala> val b= 3 
b: Int = 3 

scala> if (b!=0) a/b else Double.NaN 
res0: Double = 3.0 

scala> def div1(a: Int, b: Int) = if (b!=0) a/b else Double.NaN 
div1: (a: Int,b: Int)Double 

scala> def div2(a: Int, b: Int): Double = if (b!=0) a/b else Double.NaN 
div2: (a: Int,b: Int)Double 

scala> div1(10,3) 
res1: Double = 3.0 

scala> div2(10,3) 
res2: Double = 3.3333333333333335 

Seems interesting, because the implicitly found result type is Double and the result is 3.0.
If Double is explicitly given, the result is 3.33...

In this thread, Martin Odersky adds (June 21st):

you have uncovered a serious unintended side-effect of the weak conformance rules in overloading resolution.
The problem was that arguments of overloaded methods are required to conform weakly, whereas the result type was required to conform strongly.

This favored the Float => Float addition method on an Int over the Int => Int method if the result type was Float.
I was trying to be conservative in my change to weak conformance in that I required weak conformance only where it looked absolutely necessary.
But it seems now that being conservative caused the problem we are looking at!

And yet another Scala RC release ;)


Confirmed in this thread by Martin Odersky (June 22d):

So there will be a RC7 with so far three changes from RC6:

  1. val x: Double = 10/3 will give 3.0, not 3.3333333 - that was the regression I was mentioning
  2. [...]
  3. [...]

That's it. Our priorities now are to roll out 2.8 as quickly as we can, and at the same time avoid really bad regressions such as (1) above.

Timeline:

  • We will wait one more week to get feedback on RC6.
  • We will push out RC7 early next week.
    If no further problems show up RC7 would then turn into 2.8 final 10-14 days after it is released.

(so around July 12th, I believe, but this guess is mine alone ;) )

VonC
+15  A: 

3.5.3 Weak Conformance In some situations Scala uses a more general conformance relation. A type S weakly conforms to a type T , written S <:w T , if S <: T or both S and T are primitive number types and S precedes T in the following ordering.

  • Byte <:w Short
  • Byte <:w Character
  • Short <:w Int
  • Int <:w Long
  • Long <:w Float
  • Float <:w Double

A weak least upper bound is a least upper bound with respect to weak conformance.

Where is this used? For one thing, it determines the type of if expressions:

The type of the conditional expression is the weak least upper bound (§3.5.3) of the types of e2 and e3

In Scala 2.7.x, this would by of type AnyVal, the least uppper bound of Int and Double. In 2.8.x, it types as Double.

scala> if (true) 1 else 1d
res0: Double = 1.0

Similarly:

scala> try { 1 } catch { case _ => 1.0 }
res2: Double = 1.0

scala> (new {}: Any) match { case 1 => 1; case _ => 1.0 }
res6: Double = 1.0

scala> def pf[R](pf: PartialFunction[Any, R]): PartialFunction[Any, R] = pf
pf: [R](pf: PartialFunction[Any,R])PartialFunction[Any,R]

scala> pf { case 1 => 1; case _ => 1d }
res4: PartialFunction[Any,Double] = <function1>

Another place it is used is in type inference:

scala> def foo[A](a1: A, a2: A): A = a1
foo: [A](a1: A,a2: A)A

scala> foo(1, 1d)
res8: Double = 1.0

scala> def foos[A](as: A*): A = as.head
foos: [A](as: A*)A

scala> foos(1, 1d)
res9: Double = 1.0

And also for simple numeric widening:

Numeric Widening. If e has a primitive number type which weakly conforms (§3.5.3) to the expected type, it is widened to the expected type using one of the 6.26 Implicit Conversions 97 numeric conversion methods toShort, toChar, toInt, toLong, toFloat, toDouble defined in §12.2.1. expected type is a primitive numeric type Byte, Short or Char, and the expression e is an integer literal fitting in the range of that type, it is converted to the same literal in that type.

scala> 1: Double
res10: Double = 1.0

UPDATE

As pointed out by Daniel, the spec is wrong about which types have weak conformance. Let's ask the compiler itself:

scala> :power
** Power User mode enabled - BEEP BOOP      **
** scala.tools.nsc._ has been imported      **
** New vals! Try repl, global, power        **
** New cmds! :help to discover them         **
** New defs! Type power.<tab> to reveal     **

scala> settings.maxPrintString = 10000


scala> import global.definitions._
import global.definitions._

scala> (for{c1 <- ScalaValueClasses;
      c2 <- ScalaValueClasses
      isNSC = isNumericSubClass(c1, c2)
      if isNSC
  } yield ("isNumericSubClass (%s, %s) = %b" format (c1, c2, isNSC))).mkString("\n")


res5: String =
isNumericSubClass (class Byte, class Byte) = true
isNumericSubClass (class Byte, class Short) = true
isNumericSubClass (class Byte, class Int) = true
isNumericSubClass (class Byte, class Long) = true
isNumericSubClass (class Byte, class Float) = true
isNumericSubClass (class Byte, class Double) = true
isNumericSubClass (class Short, class Short) = true
isNumericSubClass (class Short, class Int) = true
isNumericSubClass (class Short, class Long) = true
isNumericSubClass (class Short, class Float) = true
isNumericSubClass (class Short, class Double) = true
isNumericSubClass (class Int, class Int) = true
isNumericSubClass (class Int, class Long) = true
isNumericSubClass (class Int, class Float) = true
isNumericSubClass (class Int, class Double) = true
isNumericSubClass (class Long, class Long) = true
isNumericSubClass (class Long, class Float) = true
isNumericSubClass (class Long, class Double) = true
isNumericSubClass (class Char, class Int) = true
isNumericSubClass (class Char, class Long) = true
isNumericSubClass (class Char, class Char) = true
isNumericSubClass (class Char, class Float) = true
isNumericSubClass (class Char, class Double) = true
isNumericSubClass (class Float, class Float) = true
isNumericSubClass (class Float, class Double) = true
isNumericSubClass (class Double, class Double) = true
retronym
nota bene: https://lampsvn.epfl.ch/trac/scala/ticket/3594
retronym