Does Scala have syntax for projection of a nested singleton type?
Asked Answered
O

2

8
class C {
  object O
}

val x: C#O.type = (new C).O // error: ';' expected but '.' found
val y: C#(O.type) = (new C).O // error: identifier expected but '(' found

Is there a legal syntax for this type?

EDIT:

val x: c.O.type forSome { val c: C } = (new C).O

gives

ScalaFiddle.scala:4: error: type mismatch;
 found   : ScalaFiddle.this.C#O.type
 required: c.type#O.type forSome { type c.type <: ScalaFiddle.this.C with scala.this.Singleton{} }
  val x: c.O.type forSome { val c: C } = (new C).O
                                                 ^

So Scala compiler understands this type and shows it as C#O.type. It seems this case was just overlooked when creating the grammar.

Oren answered 6/4, 2018 at 7:29 Comment(7)
May I ask what the use case of this syntax? The object O is different for each instance of C, so why should it be object in the first place?Renie
It's used when you want exactly one O for each C.Oren
Can it be achieved the same way with class O and class C { final val o: O = new O }? There's exactly one o for each C, and final val will guarantee that derived classes can't modify this field.Renie
val x: c.O.type forSome { val c: C } = { val c = new C; c.O } works, because c is stable, whereas (new C) is not. Wouldn't this be at least a temporary workaround?Maxama
Found interesting sentence in the spec: "For other expressions e, e.x is typed as if it was { val y = e; y.x }, for some fresh name y." With this sentence, I don't understand why { val c = new C; c.O } and (new C).O are not equivalently. Added an edit to my answer with more details and links.Maxama
I wasn't aware of this, and it does seem like a divergence between spec and compiler. I'll ask on the Scala mailing list tomorrow.Oren
@AndreyTyukin users.scala-lang.org/t/…Oren
M
4

Overview

  1. A few attempts to write down the type
  2. A bash script that lists every attempt together with a somewhat cleaned up error message
  3. Edit 2018-04-11: Shouldn't the rhs be typed as { val y = new C; y.O }?

I can't prove that it is impossible, but at least I wanted to make sure that I didn't give up without trying. It's not really an answer, rather a list of failed attempts, maybe someone finds it amusing, maybe someone finds the script (in the second part) for generating the list of error messages useful for reporting their findings...


A few attempts to write down the type

Systematic rewriting of AlexeyRomanov's attempt that looked most promising:

val o: x.O.type forSome { val x: C } = (new C).O
val o: x.type#O.type forSome { val x : C } = (new C).O
val o: t#O.type forSome { type t <: C with Singleton } = (new C).O

Some more or less unsystematic attempts with type-lambda like constructs:

// boring, does nothing, identity, just to recall lambda-syntax
val o: ({ type R <: c.O.type forSome { val c: C } })#R = (new C).O
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[c.type forSome{val c: C}] = (new C).O
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[A forSome {type A <: C}] = (new C).O

Experiment with nested forSome:

val o: x.type forSome { val x: c.O.type forSome { val c: C }} =  (new C).O

Experiments with additional type members in C:

class C {
  object O
  type T = O.type
}

val o: C#T = (new C).O

This actually compiles, but modifies the right hand side, so I guess it doesn't count:

val o: c.O.type forSome { val c: C } = { val c = new C; c.O }

Conclusion: Looks impossible to me.


Bash script that generates all the error messages

To generate all the error messages (without file paths and feature warnings), save the part of the post above this line as saved_post.txt, and then run the following script in the same directory:

Disclaimer: this script actually modifies files in your file system. Please make sure that you really understand what it does, and really want to run it, before running it. In particular, it destroys the file 'newCdotO.scala'.

#!/bin/bash

tempFile=newCdotO.scala
inputFile=saved_post.txt
grep -oE "^ *val o:.*$" $inputFile | \
while read codeLine
do
  printf '=%.0s' {0..80}
  echo ""
  echo "" > $tempFile
  echo "import scala.language.existentials" >> $tempFile
  echo "import scala.language.higherKinds" >> $tempFile
  echo "class C { object O; type T = O.type }" >> $tempFile
  echo "$codeLine" | tee -a $tempFile
  printf -- '-%.0s' {0..80}
  echo ""
  scala $tempFile 2>&1 | sed 's|^.*error:|error:|g'
done |
awk '{print "    "$0}'

This generates the following wall of error messages (Hey, I've tried to clean it up!):

=================================================================================
val o: x.O.type forSome { val x: C } = (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: x.O.type forSome { val x: this.C }
val o: x.O.type forSome { val x: C } = (new C).O
                                               ^
one error found
=================================================================================
val o: x.type#O.type forSome { val x : C } = (new C).O
---------------------------------------------------------------------------------
error: ';' expected but '.' found.
val o: x.type#O.type forSome { val x : C } = (new C).O
               ^
one error found
=================================================================================
val o: t#O.type forSome { type t <: C with Singleton } = (new C).O
---------------------------------------------------------------------------------
error: ';' expected but '.' found.
val o: t#O.type forSome { type t <: C with Singleton } = (new C).O
          ^
one error found
=================================================================================
val o: ({ type R <: c.O.type forSome { val c: C } })#R = (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: AnyRef{type R <: c.type#O.type forSome { type c.type <: this.C }}#R
val o: ({ type R <: c.O.type forSome { val c: C } })#R = (new C).O
                                                                 ^
one error found
=================================================================================
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[c.type forSome{val c: C}] = (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: c.type(in type L)#O.type forSome { type c.type(in type L) <: c.type(in value o) forSome { type c.type(in value o) <: this.C with Singleton } with Singleton }
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[c.type forSome{val c: C}] = (new C).O
                                                                                                ^
one error found
=================================================================================
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[A forSome {type A <: C}] = (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: c.O.type forSome { val c: A forSome { type A <: this.C } }
val o: ({type L[A <: C] = c.O.type forSome { val c: A }})#L[A forSome {type A <: C}] = (new C).O
                                                                                               ^
one error found
=================================================================================
val o: x.type forSome { val x: c.O.type forSome { val c: C }} =  (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: x.type forSome { val x: c.type#O.type forSome { type c.type <: this.C } }
val o: x.type forSome { val x: c.O.type forSome { val c: C }} =  (new C).O
                                                                         ^
one error found
=================================================================================
val o: C#T = (new C).O
---------------------------------------------------------------------------------
error: type mismatch;
 found   : this.C#O.type
 required: _1.O.type forSome { val _1: this.C }
val o: C#T = (new C).O
                     ^
one error found
=================================================================================
val o: c.O.type forSome { val c: C } = { val c = new C; c.O }
---------------------------------------------------------------------------------

EDIT 2018-04-11:

Just stumbled upon this here in 6.4 of the specification:

For other expressions e, e.x is typed as if it was { val y = e; y.x }, for some fresh name y.

The parts before this sentence describing the "not-other" expressions seem to refer to ordinary names and stable identifiers. Given this clause in the specification, it is not entirely clear to me why (new C).O is not typed in exactly the same way as {val y = new C; y.O}, because after this rewriting, the problematic code

val o: c.O.type forSome { val c: C } = (new C).O

would look just like the only working proposal that I could come up with in the above series of attempts:

val o: c.O.type forSome { val c: C } = { val c = new C; c.O }

Could the inferred type C#O.type actually be a bug, and c.O.type forSome { val c: C } be the type mandated by the specification? I wouldn't go so far claiming that it is a bug, I'm not familiar enough with the formulations used in the spec.

Maxama answered 7/4, 2018 at 0:58 Comment(0)
R
1

The object O only exists with an instance of C, so its type will need to tied with an instance of C as well. Hence you can't do C#O.type or C#(O.type), but you need to have an instance of C first, e.g.:

scala> class C { object O }
defined class C

scala> val x = new C
x: C = C@59f95c5d

scala> val y: x.O.type = x.O
y: x.O.type = C$O$@5679c6c6

EDIT: Alexey Romanov has commented below that my conclusion is incorrect, which can be demonstrated by this example below:

scala> class B { class A {} }
defined class B

scala> val t = new B
t: B = B@63d4e2ba

scala> val u: B#A = new t.A
u: B#A = B$A@1cd072a9

So I actually don't know why B#A works but C#O.type doesn't work :(

Renie answered 6/4, 2018 at 8:25 Comment(2)
The logic of the first sentence wouldn't change for class O: "Every instance of class O only exists with an instance of C, so its type will need will need to tied with an instance of C as well". And it normally is: c.O. But the conclusion "Hence you can't do C#O" would be of course incorrect.Oren
Thanks for correcting my conclusion. I'll make an edit to my answer :)Renie

© 2022 - 2024 — McMap. All rights reserved.