-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Match type syntax does not allow writing type patterns for type members directly #13416
Comments
It looks very promising indeed. To port banana-rdf to Scala 3 I need to remove all the type projections in banana-rdf, which fundamentally dependent on them. Previously I had tried dependent types in the same repo, but those were starting to look very bad. |
I gave this a shot today, but it turns out to be trickier than it looks because of the way refinement types are handled in the compiler. I think having to define a type alias here isn't the end of the world. Scala already has many oddities with types patterns, match types naturally inherits some of them: // With 2.13
scala> new Object match { case _: Any { type T = t } => null }
^
error: not found: type t
scala> type Foo[T] = Any { type X = T }
defined type alias Foo
scala> new Object match { case _: Foo[t] => null }
res1: Null = null
scala> new Object match { case _: t => null }
^
error: not found: type t
scala> type Id[X] = X
defined type alias Id
scala> new Object match { case _: Id[t] => null }
res3: Null = null |
There was a long discussion on the contributors forum Algebras of opaque types and pattern matching. I have succeeded in getting this to work In quite a big project, as you can see in the scala-3 branch of banana-rdf. The only worry I have is that I get the following types of exceptions, even though I have exceptions such as the following even though there are unapply and TypeTests available. [warn] -- Unchecked Warning: /Volumes/Dev/Programming/Scala3/banana-rdf/scratch/shared/src/main/scala/org/w3/banana/test3/Scastie.scala:137:5
[warn] 137 | case t : banana.RDF.Literal[R] => "success matching "+t
[warn] | ^
[warn] |the type test for org.w3.banana.test3.banana.RDF.Literal[R] cannot be checked at runtime
[warn] -- Unchecked Warning: /Volumes/Dev/Programming/Scala3/banana-rdf/rdf-test-suite/shared/src/main/scala/org/w3/banana/TripleTest.scala:41:5
[warn] 41 | case t : RDF.Literal[R] =>
[warn] | ^
[warn] |the type test for org.w3.banana.RDF.Literal[R] cannot be checked at runtime
[warn] -- Unchecked Warning: /Volumes/Dev/Programming/Scala3/banana-rdf/rdf-test-suite/shared/src/main/scala/org/w3/banana/TripleTest.scala:149:18
[warn] 149 | case Triple(s, p, l: Literal[R]) =>
[warn] | ^
[warn] |the type test for org.w3.banana.RDF.Literal[R] cannot be checked at runtime |
As I see it, the bigger issue is that the lack of syntax makes it seem as if it's impossible to do this in the first place.
In case of value matches, extracting a type member into a variable is redundant, since you could instead refer to it with a selection new {} match { case t: { type T } => summon[t.T] } The equivalent ability for types has been removed in Scala 3 ;) |
I am using this pattern quite heavily. In banana-rdf for Scala3 I start by defining a set of types and specify an inheritance hierarchy for them. trait RDF:
...
type Node <: rNode
type BNode <: Node
type URI <: Node & rURI Then I put together an object RDF (below line 115) that is able to find those types given a subtype of RDF. So for example: type URI[R <: RDF] <: Matchable = R match
case GetURI[u] => u
private type GetURI[U <: Matchable] = RDF { type URI = U } But I'd like to capture that `URI[R <: RDF] is a subtype of Node[R] as in the initial RDF trait. I can't see how to do that.... Without that it seems like I need to write a lot of implicit conversion objects for every pair of inherited types. This is still doable here but perhaps there is a simpler solution? |
@bblfish type URI[R <: RDF] <: Node = R match case GetURI[u] => u
private type GetURI[U <: Node] |
The node type will depend on the RDF subclass. So the best I could do I think is: type Node[R <: RDF] = //URI[R] | BNode[R] | Literal[R]
R match
case GetNode[n] => n
type URI[R <: RDF] <: Node[R] = R match
case GetURI[u] => u
private type GetURI[U] = RDF { type URI = U }
private type GetNode[N] = RDF { type Node = N } I get the error [error] -- [E007] Type Mismatch Error: /Volumes/Dev/hjs/Programming/Scala3/CoSy/banana-rdf/scala3/rdf/shared/src/main/scala/org/w3/banana/RDF.scala:130:24
[error] 130 | case GetURI[u] => u
[error] | ^
[error] | Found: u
[error] | Required: org.w3.banana.RDF.Node[R]
[error] |
[error] | Note: a match type could not be fully reduced:
[error] |
[error] | trying to reduce org.w3.banana.RDF.Node[R]
[error] | failed since selector R
[error] | does not match case org.w3.banana.RDF{Node = n} => n
[error] | and cannot be shown to be disjoint from it either.
[error] |
[error] | longer explanation available when compiling with `-explain`
[error] one error found
[error] (rdfJVM / Compile / compileIncremental) Compilation failed
[error] Total time: 0 s, completed Oct 18, 2022, 12:19:54 PM I tried to see if I could give more information about URI in GetURI type URI[R <: RDF] <: Node[R] = R match
case GetURI[R, u] => u
private type GetURI[R <: RDF, U <: Node[R]] = RDF { type URI = U } Which gives me the error [error] -- [E007] Type Mismatch Error: /Volumes/Dev/hjs/Programming/Scala3/CoSy/banana-rdf/scala3/rdf/shared/src/main/scala/org/w3/banana/RDF.scala:130:27
[error] 130 | case GetURI[R, u] => u
[error] | ^
[error] |Found: u
[error] |Required: org.w3.banana.RDF.Node[R]
[error] |
[error] |where: u is a type in type URI with bounds <: org.w3.banana.RDF.Node[R]
[error] |
[error] |
[error] |Note: a match type could not be fully reduced:
[error] |
[error] | trying to reduce org.w3.banana.RDF.Node[R]
[error] | failed since selector R
[error] | does not match case org.w3.banana.RDF{Node = n} => n
[error] | and cannot be shown to be disjoint from it either.
[error] |
[error] | longer explanation available when compiling with `-explain`
[error] one error found Perhaps I should try to work out how this Btw. this pattern I think is extremely useful, and could be widely used across the scala ecosystem to bring many libraries written by different groups in different languages under a common API. I have used this to help me integrate Http4s and Akka for example here https://github.com/bblfish/httpSig/blob/trackIO/ietfSig/shared/src/main/scala/run/cosy/http/Http.scala |
Btw, I have just committed the code with an attempt to faithfully reproduce the type inheritance in the RDF class of the diesel2 branch. But that does not work. But it feels like that is what should work! Otherwise I am forced to create all kinds of implicit conversions for the DSL, as I was starting to write in the diesel syntax class. (some of those are needed, but clearly not all should be). |
Attached here is the full log of error messages when compiling. I also wrote up a detailed explanation of why this feature is so useful as well as a simplified version of the problem I am encountering as a scastie in the users.scala-lang.org discussion group. |
So now I am wondering if @neko-kai's original answer may not be what is needed to solve my inheritance problem @OlivierBlanvillain . After watching @mbovel's recent very helpful talk Type Level Programming in Scala I think I understand how the proposal that started this issue relates to refinement, and how that could help perhaps with the problem of inheritance. Instead of having the following extract which seems like what is needed to get inheritance to work, but also seems wrong syntactically, because somehow type URI[R <: RDF] <: Node[R] = R match
case GetURI[R, u] => u
private
type GetURI[R <: RDF, U <: Node[R]] = RDF { type URI = U } we could instead have the simpler object RDF:
...
type URI[R <: RDF] <: Node[R] = R match
case (RDF { type URI = u }) => u that would not require me to pass the |
@bblfish you can recover the sub typing with intersection types and recursion: type URI[R <: RDF] <: Node[R] = R match
case GetURI[u] => u & Node[R] see the example here https://scastie.scala-lang.org/lvxYeEJqT0e8t6QPZMbriA |
…around to minimize changes and support Http4sContext type scala/scala3#13416
* Initial build adaptation for Scala3. Replace ProjectAttributeMacro with izumi MacroParameters * wip * wip * wip * Port IRT Runtime to Scala 3; use Match Types as type projections workaround to minimize changes and support Http4sContext type scala/scala3#13416 * Add scala-java-time dependency for Scala.js test on Scala 3 * Add project/build.properties to generated SBT projects Add sbt workaround for circe-derivation/circe-core old versions into generated SBT projects Use semiauto encoder for circe when sbt.scalaVersion > 3 FIXME: disable AnyVals on Scala 3 because circe deriver on 3 doesn't support AnyVals: `class Struct is not a generic product because it is a value class` Re-enable AnyVals on Scala 3 - generate manual codec for AnyVals on Scala 3 Add sbt version to Scala build manifest Add Scala 3 keywords to keywords list Update scalameta, print source with Scala 3 Dialect to fix escaping of `export` and other keywords Fix Scala 3 warning: final case object -> case object --------- Co-authored-by: Pavel Shirshov <pshirshov@eml.cc>
Minimized code
Output
Expectation
Expected to be able to extract a variable type from inside a type refinement.
This syntax limit can be worked around by introducing a "pattern type":
Why write something like that? Apparently this pattern of extracting a field using a match type can substitute for some usages of generalized type projections and is enough to port some of the libraries using type projections, e.g. https://github.com/bblfish/banana-play
The text was updated successfully, but these errors were encountered: