scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

Ignoring a type constructor requires rocket science #8039

Open scabug opened 10 years ago

scabug commented 10 years ago

I think it's pretty reasonable to expect to be able to write a method like this:

def f[A](xs: Thing[_, A]): A = xs.bippy

If Thing's first type argument is kind *, you can. If it isn't, then god help you. There's no way most people will ever come up with this. The usual surrender probably involves adding an otherwise pointless type parameter to method f. This is not always an option and definitely shouldn't be mandated.

Here are a few things one might try before hitting upon the syntax which actually works.

scala> trait Foo[CC[+X], +A]
defined trait Foo

scala> trait Foo[CC[+X], +A] ; def f[A](x: Foo[_, A]): A = ???
<console>:8: error: _$1 takes no type parameters, expected: one
       trait Foo[CC[+X], +A] ; def f[A](x: Foo[_, A]): A = ???
                                               ^

// That's not what you said about _$1 a minute ago, you sadist.
scala> trait Foo[CC[+X], +A] ; def f[A](x: Foo[_[_], A]): A = ???
<console>:8: error: _$1 does not take type parameters
       trait Foo[CC[+X], +A] ; def f[A](x: Foo[_[_], A]): A = ???
                                               ^
// If you're lucky enough to come up with the existential syntax for
// higher order type parameters, you will still be burned if you get the
// variance wrong. Remember we're doing all this just to IGNORE IT.
scala> trait Foo[CC[+X], +A] ; def f[A](x: Foo[CC forSome { type CC[X] }, A]): A = ???
<console>:8: error: kinds of the type arguments (CC forSome { type CC[X] <: Any },A) \
do not conform to the expected kinds of the type parameters \
(type CC,type A) in trait Foo.
CC forSome { type CC[X] <: Any }'s type parameters do not match \
type CC's expected parameters:
type X is invariant, but type X (in trait Foo) is declared covariant
       trait Foo[CC[+X], +A] ; def f[A](x: Foo[CC forSome { type CC[X] }, A]): A = ???
                                           ^

Colonel Mustard in the conservatory:

// Nice, three feature warnings for the only way to write it.
scala> trait Foo[CC[+X], +A] ; def f[A](x: Foo[CC forSome { type CC[+X] }, A]): A = ???
warning: there were 3 feature warning(s); re-run with -feature for details
defined trait Foo
f: [A](x: Foo[CC forSome { type CC[+X] <: Any },A])A
scabug commented 10 years ago

Imported From: https://issues.scala-lang.org/browse/SI-8039?orig=1 Reporter: @paulp

scabug commented 10 years ago

@adriaanm said: The Plum lady in the wine cellar.

scala> trait Foo[CC[+X], +A]
warning: there were 1 feature warning(s); re-run with -feature for details
defined trait Foo

scala> trait FooChill { type CC[+X]; type A; type Apply = Foo[CC, A] }
warning: there were 1 feature warning(s); re-run with -feature for details
defined trait FooChill

scala> def x: (FooChill{type A = Int})#Apply = ???
x: Foo[_1.CC,Int] forSome { val _1: FooChill{type A = Int} }
scabug commented 10 years ago

@adriaanm said: Or, of course:


scala> def x[B]: (FooChill{type A = B})#Apply = ???
x: [B]=> Foo[_3.CC,B] forSome { val _3: FooChill{type A = B} }
scabug commented 10 years ago

@paulp said: Okay, that's cool and all, but do you have any good reason why should not be kind-polymorphic? Isn't it blazingly obvious what means in a ' -> expected' context? There's always going to be a manual translation available, and obviously you can't refer to things without names, so it's not like soundness is going to come into play. (Not that I think soundness is on anyone's mind.)

scabug commented 10 years ago

@adriaanm said: I'm not against simplifying this in 2.12, maybe overload _ in one more way. Given the workaround, type members are the better design when type applications are expected to be partial once in a while.

scabug commented 10 years ago

@retronym said: The proposal does seem to fit nicely with the new bound inference for _.

https://github.com/retronym/scala/tree/ticket/8039

scabug commented 10 years ago

@paulp said: What? "When type applications are expected to be partial once in a while", have you thought this through? How are you supposed to know what the rest of the world is going to do with your types?

Not to mention it's disingenuous to suggest one is ever likely to have such freedom that one can hop between type members and type parameters. There are a billion things which constrain this choice - most of them are implementation bugs or inadequacies by design - never once will anyone be so free of those constraints that they'll be able to say "I'll make this a type member because I expect it to be partial once in a while."

scabug commented 10 years ago

@paulp said: Oh yes, and last but not least, this isn't even about partial application. It's about NO application. Partial application is a whole separate thing.

scabug commented 9 years ago

@adriaanm said: As with many of your outstanding tickets, it's not that I don't want to comment and fix, it's that I don't know how.

scabug commented 9 years ago

@paulp said: You know I can't resist such flattery. Okay, let's close this thing.

Hey wait a minute, "outstanding tickets" could mean "excellent tickets" or it could mean "the tickets which aren't closed yet." I'm going to infer the first meaning, no need for corrections.

scabug commented 9 years ago

Alexander Abdugafarov (frozenspider) said: Just ran into this issue recently, was very surprised. Workaround is nice and all, but I really think this should work with type parameters as well.

jeffmay commented 3 years ago

Any chance this will be fixed for Scala 2 or possibly Scala 3?

SethTisue commented 3 years ago

Scala 2, doubtful. Scala 3, https://dotty.epfl.ch/docs/reference/other-new-features/kind-polymorphism.html

Jasper-M commented 3 years ago

Not sure if AnyKind is related, but Paul's example does seem to work:

scala> trait Foo[CC[+X], +A]
// defined trait Foo

scala> def f[A](x: Foo[_, A]): A = ???
def f[A](x: Foo[?[+X], A]): A

scala> f(new Foo[List, Int]{})
scala.NotImplementedError: an implementation is missing
SethTisue commented 3 years ago

even more fixed-in-Dotty than I realized, then!

jeffmay commented 3 years ago

Not sure if AnyKind is related, but Paul's example does seem to work:

scala> trait Foo[CC[+X], +A]
// defined trait Foo

scala> def f[A](x: Foo[_, A]): A = ???
def f[A](x: Foo[?[+X], A]): A

scala> f(new Foo[List, Int]{})
scala.NotImplementedError: an implementation is missing

I tried to make that work with my own code but without much luck. This is ideally what I want:

sealed trait Expr[F[_], V, R, P] {
  def visit[G[_]](v: Expr.Visitor[F, V, P, G]): G[R]
}

object Expr {
  trait Visitor[F[_], V, P, G[_]] extends (Expr[F, V, *, P] ~> G) {
    override final def apply[R](fa: Expr[F, V, R, P]): G[R] = fa.visit(this)
    def visitNode[R](node: Expr.Node[F, V, R, P]): G[R]
    // other visit Expr subclass methods...
  }
  // Expr subclasses....
}

import io.circe.Json

object ConvertExprToJson {
  type Out[R] = Json
}

// Is it possible to remove these class type parameters too?
class ConvertExprToJson[F[_], V, P](
  encodeSubNodes: Boolean = true
) extends Expr.Visitor[F, V, P, ConvertExprToJson.Out] {

  // This signature doesn't compile.
  private def encodeSubNode(name: String, subNodes: List[Expr[_, _, _, _]]): Json = {
    if (encodeSubNodes) {
      val subNodesJson = subNodes.map(_.visit(new ConvertExprToJson[_, _, _, _])).asJson
      Json.obj(name -> subNodeJson)
    }
    else Json.obj()
  }

  // I currently work around this issue by using G[_] here
  private def encodeNode[G[_]](node: Expr[G, _, _, _]): Json = {
    Json.obj(
      "className" -> node.getClass.getSimpleName.asJson
      // encode common Expr fields...
    )
  }

  override def visitNode[R](node: Expr.Node[F, V, R, P]): Json = {
    encodeSubNode("subNodes" -> List(expr.subNode))).deepMerge(encodeNode(expr))
  }
}