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

Match type syntax does not allow writing type patterns for type members directly #13416

Open
neko-kai opened this issue Aug 29, 2021 · 11 comments

Comments

@neko-kai
Copy link
Contributor

neko-kai commented Aug 29, 2021

Minimized code

trait RDF {
  type Triple
}
object RDF {
  type Triple[R <: RDF] = R match {
    case (RDF { type Triple = t }) => t
  }
  type GetTriple[T] = RDF { type Triple = T }
}

Output

Not found: type t

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":

trait RDF {
  type Triple
}
object RDF {
  type Triple[R <: RDF] = R match {
    case GetTriple[t] => t
  }
  type GetTriple[T] = RDF { type Triple = T }
}

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

@neko-kai neko-kai changed the title Match type syntax does not allow writing type patterns forinside type members Match type syntax does not allow writing type patterns for type members directly Aug 29, 2021
@bblfish
Copy link

bblfish commented Aug 29, 2021

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.
This use of match types looks very promising. I am trying things out here src/test/scala/ideas/MatchTypes.scala

Previously I had tried dependent types in the same repo, but those were starting to look very bad.
I had been discussing this in dotty discussions thread 12527.

@OlivierBlanvillain
Copy link
Contributor

OlivierBlanvillain commented Sep 24, 2021

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

@bblfish
Copy link

bblfish commented Sep 24, 2021

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

@neko-kai
Copy link
Contributor Author

@OlivierBlanvillain

I think having to define a type alias here isn't the end of the world.

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.

Scala already has many oddities with types patterns, match types naturally inherits some of them:

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 ;)

@bblfish
Copy link

bblfish commented Oct 18, 2022

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.
eg

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?

@neko-kai
Copy link
Contributor Author

@bblfish
Maybe constraining GetURI parameter could help?

type URI[R <: RDF] <: Node = R match case GetURI[u] => u

private type GetURI[U <: Node]

@bblfish
Copy link

bblfish commented Oct 18, 2022

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 -explain flag works from sbt... Ah found it explained here and it works...

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

@bblfish
Copy link

bblfish commented Oct 18, 2022

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).

@bblfish
Copy link

bblfish commented Oct 18, 2022

Attached here is the full log of error messages when compiling.
I don't really know how to read those errors. So I can't tell if this is a bug on my part or in the compiler.

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.

@bblfish
Copy link

bblfish commented Oct 19, 2022

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 R is meant to come from the caller but u from the matching...

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 R type to the GetURI type function and hopefully the compiler could work out by looking at the RDF trait that a RDF.URI is always a subtype of RDF.Node...

@bishabosha
Copy link
Member

@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

neko-kai added a commit to 7mind/idealingua-v1 that referenced this issue Mar 16, 2023
…around to minimize changes and support Http4sContext type scala/scala3#13416
neko-kai added a commit to 7mind/idealingua-v1 that referenced this issue Mar 27, 2023
* 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>
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

4 participants