Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing type parameter inference for stable identifier patterns. #785

Open
scabug opened this issue Apr 21, 2008 · 9 comments
Open

Missing type parameter inference for stable identifier patterns. #785

scabug opened this issue Apr 21, 2008 · 9 comments

Comments

@scabug
Copy link

scabug commented Apr 21, 2008

This is both a report of a defect in the specification and a
minor enhancement request for the implementation.

GADT-style dependent case analysis works for objects:

scala> trait T[X]
defined trait T

scala> object O extends T[int]
defined module O

scala> def f[X] : T[X] => X = { case O => 42 }
f: [X](T[X]) => X

O is syntactically a stable identifier pattern (8.1.4), but
dependent case analysis isn't defined for them in section 8.3 of
the specification. Their treatment seems straightforwardly
analogous to the treatment of constructor patterns, though: {
case O => 42 } just gets translated to { case _ : O.type => 42 }.
This description is currently missing from the specification.

Moreover, it seems that with the current implementation, the
above rule only works for objects, not for other stable
identifiers.

scala> val V = new T[int] {}
V: java.lang.Object with T[int] = $$anon$$1@23194cf5

scala> def g[X] : T[X] => X = { case V => 42 }
<console>:6: error: type mismatch;
 found   : Int(42)
 required: X
       def g[X] : T[X] => X = { case V => 42 }

Still, manual translation to a type pattern works all right:

scala> def g[X] : T[X] => X = { case _ : V.type => 42 }
g: [X](T[X]) => X

scala> g(V)
res1: int = 42

This seems inconsistent. I very much think all stable identifiers
should be treated uniformly. My immediate motivation for this is
that it makes writing a typecase somewhat neater. Given:

val IntC = classOf[Int]
val StringC = classOf[String]

Currently one has to write:

def g[X] : Class[X] => X = { 
   case _ : IntC.type => 42
   case _ : StringC.type => "forty-two" 
}

Instead of the prettier:

def g[X] : Class[X] => X = { 
   case IntC => 42
   case StringC => "forty-two" 
}
@scabug
Copy link
Author

scabug commented Apr 21, 2008

Imported From: https://issues.scala-lang.org/browse/SI-785?orig=1
Reporter: Lauri Alanko (lealanko)
See #7456

@scabug
Copy link
Author

scabug commented Apr 23, 2008

Lauri Alanko (lealanko) said:
All right, this is a bigger mess than I first thought. A number of issues arise.

The specification states (8.1.4) that v matches a stable identifier
pattern r if r == v. In actuality, the test is v == r.

class C(name : String) { 
    override def equals(o : Any) = { 
        Console.println(name + " == ...")
        super.equals(o)
    }
}

object O extends C("O")

val V = new C("V")

scala> val ov = O match { case V => true; case _ => false }
O == ...
ov: Boolean = false

Either the specification or implementation is in error.

When matching against an object, even though it is a stable identifier
pattern, no == -based comparison is made.

scala> val ov = V match { case O => true; case _ => false }
ov: Boolean = false

Presumably direct reference equality is used, and this is indeed
necessary for an object pattern's type refinement properties to be safe.

Thus, an object pattern as currently implemented is not a stable
identifier pattern, despite the spec.

An object pattern provides stronger type refinement than a singleton type pattern.

object O

scala> def f[X] : X => X = { case O => O }
f: [X](X) => X

scala> def g[X] : X => X = { case _:O.type => O }
<console>:5: error: type mismatch;
 found   : O.type (with underlying type object O)
 required: X
       def g[X] : X => X = { case _:O.type => O }

Presumably matching to O gives us constraints X <: O.type <: X on the
right-hand side of the case clause, since the only possible subtypes of
a singleton type are Null and Nothing, and from the match we know that X
must be inhabited by a non-null value.

However, for an explicit type pattern, we only get the constraint X <:
O.type, since we're using the generic rule for types which cannot assume
that there couldn't be strict non-null subtypes.

Singleton type patterns should be given special treatment to make them
equivalent with the object patterns, and/or there should be a way to
match non-object stable identifier patterns with as strong type
refinements as object patterns get.

Summary:

Object patterns currently behave differently both from other stable
identifier patterns and from singleton type patterns. It is impossible
to get similar matching for other stable names. The specification is
greatly out of sync with the implementation.

Here's a sketch of the situation and my view of how it should be:

pattern               matched with            type refinement
                   spec  impl     ideal     spec    impl    ideal
object             ==    eq       eq        none?   strong  strong
other stable id    ==    ==(rev)  eq        none?   none    strong
singleton type     eq    eq       eq        weak    weak    weak or strong

The point of stable identifiers is that they have these wonderful
singleton types. It seems silly to have a pattern that is constrained to
stable identifiers, yet compares them with a user-definable equals() and
provides no type refinements. An equals()-tested value could just as
well be provided by an arbitrary expression, so you could just as well
add a pattern { expr } which would be evaluated during the match, and
the result compared with == against the matched value.

Yet, on the other hand, it would also be at times useful to have

def f[X] : Class[X] => X = { case { classOf[int] } => 42 }

provide the proper type refinements, which of course requires that the
match is done with eq. It's silly that currently one has to write:

def f[X] : Class[X] => X = { 
    val IC = classOf[int]
    { case _ : IC.type => 42 } 
}

As a general rule in programming language design, I think that if a
variable is used only once, it should be inlinable away. Currently this
is not possible in Scala.

So, both eq-compared type-refinable matches and ==-compared
non-refinable matches have their place, and I think both need better
support from the language than they currently have.

@scabug
Copy link
Author

scabug commented Aug 19, 2008

Lauri Alanko (lealanko) said:
One more quirk that I discovered: a type refinement in a pattern match clause doesn't seem to work properly when the refined type is used in a, uh, "type refinement" in the different sense of explicitly providing type constraints. With 2.7.1.r15804-b20080816212059, the following fails:

object T3 {
  class A { type T }
  case class B[X]
  object C extends B[Int]
  def f[X](b : B[X]) : A { type T = X } = b match { 
    case C => new A { type T = Int }
  }
}

T3.scala:6: error: type mismatch;
 found   : T3.A{ ... }
 required: T3.A{type T = X}
    case C => new A { type T = Int }

However, this can be worked around by using a simple wrapper class around the refined type. With the following change it works:

  def f[X](b : B[X]) : A { type T = X } = {
    case class W[Y](a : A { type T = Y })
    val w : W[X] = b match { 
      case C => W[Int](new A { type T = Int })
    }
    w.a
  }

@scabug
Copy link
Author

scabug commented Jan 14, 2009

@odersky said:
Milestone next_feature deleted

@scabug
Copy link
Author

scabug commented Oct 1, 2010

@harrah said:
The patch I added for #3887 causes 1) and 2) in the first comment to behave as specified ( == called on the pattern). The dependent case analysis presented in this bug appears to no longer work. That is, the first example gives the same compile error as the second.

@scabug
Copy link
Author

scabug commented Oct 2, 2010

@paulp said:
Replying to [comment:8 harrah]:

The patch I added for #3887 causes 1) and 2) in the first comment to behave as specified ( == called on the pattern). The dependent case analysis presented in this bug appears to no longer work. That is, the first example gives the same compile error as the second.

Can you clarify what is no longer working? I can't tell what you are referring to. I find about the same behavior for everything I tried.

scala> trait T[X]
defined trait T

scala> object O extends T[Int]
defined module O

scala> def f[X] : T[X] => X = { case O => 42 }
f: [X](T[X]) => X

scala> val V = new T[Int] { }
V: java.lang.Object with T[Int] = $$anon$$1@50ec2522

scala> def g[X] : T[X] => X = { case V => 42 }
<console>:7: error: type mismatch;
 found   : Int(42)
 required: X
       def g[X] : T[X] => X = { case V => 42 }
                                          ^

scala> def g[X] : T[X] => X = { case _ : V.type => 42 }
g: [X](T[X]) => X

scala> g(V)
res0: Int = 42

@scabug
Copy link
Author

scabug commented Oct 2, 2010

@harrah said:
Yeah, you're right, sorry. I don't know if I was using a bad build or failed to notice that the error was about 'int'.

@scabug
Copy link
Author

scabug commented May 2, 2013

@paulp said:
For the record, I implemented much of this ticket a while ago and it is available here: https://github.com/paulp/scala/tree/issue/785

@scabug
Copy link
Author

scabug commented Jun 6, 2013

@retronym said:
See also #7456

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants