Scala 3 compiletime: value from type - scala

I want to initialize a val with the value of a type parameter:
import scala.compiletime.constValue
trait Foo[N <: Int]:
val m:Int=constValue[N]
val two = new Foo[2]{}.m
I get an error:
-- Error: ---------------------------------------------------------------
5 | val m:Int=constValue[N]
| ^^^^^^^^^^^^^
| not a constant type: Foo.this.N; cannot take constValue
How do I tell it to calculate m only after N is known?
(Well, in fact, I want something a bit more complex:
trait Foo[N <: Int]:
transparent inline def something[N]:Int = ??? // constValue[N] inside
val m:Int=something[N]
but it fails on constValue[N])

N is known (should) when you are creating this trait. So you could pass it in a constructor argument of a trait
trait Foo[N <: Int](using N: ValueOf[N]):
val m: Int = N.value
ValueOf[N] would be then provided when you instantiate the value
object Bar extends Foo[5]
println(Bar.m)
If you defer it further, you'd have to pass ValueOf[N] up till then place when it is known
def create[N <: Int : ValueOf]: Foo[N] =
new Foo[N] {}
println(create[4].m)
constValue[N] would work if N was known e.g. if it was defined as
type N = 5
or if it was used inside an inline method which known the type of N <: Int passed into it
inline def something[N <: Int]: Int = scala.compiletime.constValue[N]
println(something[3])
In your second example you have two N defined, it would partially work if you did
trait Foo[N <: Int]:
inline def something: Int = scala.compiletime.constValue[N]
println(new Foo[2]{}.something)
partially because something knows N when called from outside in the context where the exact type is known. Within Foo this is not true, so
val m: Int = something
will always fail.

Related

How to help the Scala 3 compiler infer a path-dependent-type?

Suppose that, whenever type A can represent a less-than-perfectly-precise measurement of a physical value, I'll have an instance of Imprecise[A].
trait Imprecise[A]:
type Precision
val ord: Ordering[Precision]
def precision(a: A): Precision
For example, this Bounds type has a given instance in its companion object:
case class Bounds[N: Numeric](min: N, max: N):
if summon[Ordering[N]].compare(min, max) > 1 then
throw new IllegalArgumentException
object Bounds:
import scala.math.Numeric.Implicits.infixNumericOps
import scala.math.Ordering.Implicits.infixOrderingOps
given [N: Numeric]: Imprecise[Bounds[N]] with
type Precision = N
val ord = summon[Ordering[N]]
def precision(rng: Bounds[N]) = rng.max - rng.min
Now my program can start dealing with the physical features that need to be observed:
trait Feature[A: Imprecise]:
type Observation = A
val imprecise = summon[Imprecise[A]]
type Precision = imprecise.Precision
def within(bound: Precision) = new RequiredFeature(this, bound)
class RequiredFeature(val feature: Feature[_], val min_precision: feature.Precision)
case class Temperature(chamber_id: Int) extends Feature[Bounds[Double]]
case class Pressure(chamber_id: Int) extends Feature[Bounds[Float]]
But when, at last, I try to make a required feature:
val rf = Temperature(3).within(7.0)
the compiler complains
Found: (7.0d : Double)
Required: ?1.Precision
where: ?1 is an unknown value of type exp.Temperature
val rf = Temperature(3).within(7.0)
The code written so far can be made to compile if I forego any path-dependent types and pass the precision around everywhere as a regular generic type parameter. But what if I don't want to do that? Is there some strategy like the Aux pattern that will help the compiler see that this is a valid call to within?
This question is similar to Cannot prove equivalence with a path dependent type
The thing is not in implicits, the thing is in path-dependent types. The simplest example is
trait A:
type T
val a0 = new A:
type T = Int
summon[a0.T =:= Int]
class B(val a: A) // a has type A, not A { type T = ... }
val b = B(a0)
// summon[b.a.T =:= Int] // doesn't compile
To make this compile we could use singleton types class B(val a: a0.type) or add a type parameter class B[_T](val a: A { type T = _T }).
trait Feature[A: Imprecise] is desugared into trait Feature[A](using ev: Imprecise[A]) and here you're loosing type refinement, upcasting Imprecise[A] { type Precision = ... } to just Imprecise[A]. Difference in behavior of path-dependent types in variable vs. method parameter is discussed in Same type ascription loses type member information on variable declaration but not on method parameter declaration and using ev: Imprecise[A] in trait Feature[A](using ev: Imprecise[A]) declares a variable.
So you should restore type refinement of Imprecise and add a type parameter to Feature
trait Imprecise[A]:
type Precision
val ord: Ordering[Precision]
def precision(a: A): Precision
object Imprecise:
type Aux[A, P] = Imprecise[A] with
type Precision = P
case class Bounds[N: Numeric](min: N, max: N):
if summon[Ordering[N]].compare(min, max) > 0 then
throw new IllegalArgumentException
object Bounds:
import Numeric.Implicits._
given [N: Numeric]: Imprecise[Bounds[N]] with
type Precision = N
val ord = summon[Ordering[N]]
def precision(rng: Bounds[N]) = rng.max - rng.min
trait Feature[A, P](using Imprecise.Aux[A, P]):
type Observation = A
val imprecise = summon[Imprecise[A]]
type Precision = imprecise.Precision
def within(bound: Precision) = new RequiredFeature(this, bound)
class RequiredFeature(val feature: Feature[_,_], val min_precision: feature.Precision)
case class Temperature(chamber_id: Int) extends Feature[Bounds[Double], Double]
case class Pressure(chamber_id: Int) extends Feature[Bounds[Float], Float]
val rf = Temperature(3).within(7.0)
or add a type member to Feature
trait Imprecise[A]:
type Precision
val ord: Ordering[Precision]
def precision(a: A): Precision
object Imprecise:
type Aux[A, P] = Imprecise[A] with
type Precision = P
case class Bounds[N: Numeric](min: N, max: N):
if summon[Ordering[N]].compare(min, max) > 0 then
throw new IllegalArgumentException
object Bounds:
import Numeric.Implicits._
given [N: Numeric]: Imprecise[Bounds[N]] with
type Precision = N
val ord = summon[Ordering[N]]
def precision(rng: Bounds[N]) = rng.max - rng.min
trait Feature[A]:
type P
val ev: Imprecise.Aux[A, P]
given Imprecise.Aux[A, P] = ev
type Observation = A
val imprecise = summon[Imprecise[A]] // ev
type Precision = imprecise.Precision // P
def within(bound: Precision) = new RequiredFeature(this, bound)
class RequiredFeature(val feature: Feature[_], val min_precision: feature.Precision)
case class Temperature[_P](chamber_id: Int)(using _ev: Imprecise.Aux[Bounds[Double], _P]) extends Feature[Bounds[Double]]:
type P = _P
val ev = _ev
case class Pressure[_P](chamber_id: Int)(using _ev: Imprecise.Aux[Bounds[Float], _P]) extends Feature[Bounds[Float]]:
type P = _P
val ev = _ev
val rf = Temperature(3).within(7.0)
Adding to #DmytroMitin excellent answer, here is a step by step analysis for those with slow minds like mine :)
The explanation is based on this example code:
class Outer {
class Inner
def doSomething(a: Inner): Unit = ()
}
val outerRef = new Outer
The first thing we have to note is that the type of the reference outerRef is not Outer but outerRef.type, which is assignable to Outer but not the opposite. That happens even if the type of outerRef were specified explicitly to be Outer (val outerRef: Outer = new Outer).
summon[outerRef.type <:< Outer]
The second thing we have to note is that a copy of a reference does not have the same type than the original reference.
val copyOfOuterRef = outerRef
Here the type of the reference copyOfOuterRef is not outerRef.type but copyOfOuterRef.type, which is assignable to Outer but not to outerRef.type.
summon[copyOfOuterRef.type <:< Outer] // compiles
summon[copyOfOuterRef.type =:= outerRef.type] // does not compile
summon[copyOfOuterRef.type <:< outerRef.type] // does not compile
summon[outerRef.type <:< copyOfOuterRef.type] // does not compile
What does all this have to do with path-dependent-types?
The path-dependent-type assignability rules is based on the singleton type of the references involved in the path.
The following line defines a reference whose type is assignable to the path-dependent-type outerRef.Inner.
val innerRef = new outerRef.Inner
Therefore, it is suitable to be the argument of the outerRef.doSomething method.
outerRef.doSomething(innerRef) // compiles
summon[innerRef.type <:< outerRef.Inner] // compiles
But not suitable to be the argument of the copyOfOuterRef.doSomething method.
copyOfOuterRef.doSomething(innerRef) // does not compile
summon[innerRef.type <:< copyOfOuterRef.Inner] // does not compile
because the singleton type of the references involved in the paths is not the same.
summon[copyOfOuterRef.type =:= outerRef.type] // does not compile
To solve that, we have to make the copy of the reference have the same singleton-type than the original reference.
val copyOfOuterRefWithSameType: outerRef.type = outerRef
Now the singleton type of the references that conform both paths, outerRef.X and copyOfOuterRefWithSameType.X, are the same. Therefore
copyOfOuterRefWithSameType.doSomething(innerRef) // compiles
summon[outerRef.type =:= copyOfOuterRefWithSameType.type] // compiles
summon[outerRef.Inner =:= copyOfOuterRefWithSameType.Inner] // compiles
A more realistic case
Usually, we can't change the type of the second reference (the copy of the first) because the first reference is out of the scope. For example, when the second reference (the copy) is a member a previously defined class.
class User(val outer: Outer) // defined in some place where the `outerRef` reference is not accesible.
val outerRef = new Outer
val user = new User(outerRef)
Here both user.outer and outerRef reference the same instance of Outer but because the path-dependent-type is based on the singleton type of the references involved, the assignability fails.
val outerRef = new Outer
val innerRef = new outerRef.Inner
user.outer.doSomething(innerRef) // does not compile
summon[innerRef.type <:< user.outer.Inner] // does not compile
because
summon[user.outer.Inner =:= outerRef.Inner] // does not compile
To solve that we have to make the singleton type of the outer member be equivalent to the type of outerRef, which is the singleton type outerRef.type. We can achieve that parameterizing the member type.
class UserBis[O <: Outer](val outer: O)
val outerRef = new Outer
val innerRef = new outerRef.Inner
val userBis = new UserBis[outerRef.type](outerRef)
userBis.outer.doSomething(innerRef) // compiles
summon[userBis.outer.type =:= outerRef.type] // compiles
summon[userBis.outer.Inner =:= outerRef.Inner] // compiles

Return most specific type given a method parameter

I am trying to create a specific and unique type given some variable parameter.
Listed below you find the prediined types representing the first four natural numbers (with an HList like approach of Digit0..Digit9, DimensionCons and EndDimension (like HNil)).
object Defined {
type D1 = DimensionCons[Digit1, EndDimension.type]
val d1 = DimensionCons(Digit1(), EndDimension)
type D2 = DimensionCons[Digit2, EndDimension.type]
val d2 = DimensionCons(Digit2(), EndDimension)
type D3 = DimensionCons[Digit3, EndDimension.type]
val d3 = DimensionCons(Digit3(), EndDimension)
type D4 = DimensionCons[Digit4, EndDimension.type]
val d4 = DimensionCons(Digit4(), EndDimension)
}
I am seeking for a method with an indicated signature like
def getDimensionTpe[D <: Dimension](dim: Int) : D
that e.g. returns DimensionCons[Digit2, EndDimension.type] for getDimensionTpe(2).
Questions:
Is this possible without a whitebox macro (that generates the types?) If so, how and what to take care of?
Are there alternative techniques possible or applicable?
Can path dependent types help?
Thanks, Martin
I'm not sure I understand what exactly you're doing with DimensionCons and the DigitN business, but it is possible to use Shapeless to write a method that will take a integer (although only a literal constant—it must be known at compile-time) and have that integer determine the static type of the return value.
To simplify your code for the sake of having a complete working example, suppose we want to write a method like the following:
trait Dimension
case class Dim1() extends Dimension
case class Dim2() extends Dimension
case class Dim3() extends Dimension
// And so on...
def getDimensionTpe[D <: Dimension](dim: Int) : D
…where getDimensionTpe(1) would return Dim1() (statically typed as Dim1), getDimensionTpe(2) would return Dim2(), etc. To do this we can introduce a type class mapping between Shapeless natural numbers and dimensions:
import shapeless.{ DepFn0, Nat }
trait DimMap[N <: Nat] extends DepFn0 {
type Out <: Dimension
}
object DimMap {
type Aux[N <: Nat, D <: Dimension] = DimMap[N] { type Out = D }
implicit val dimMap1: Aux[Nat._1, Dim1] = new DimMap[Nat._1] {
type Out = Dim1
def apply(): Dim1 = Dim1()
}
implicit val dimMap2: Aux[Nat._2, Dim2] = new DimMap[Nat._2] {
type Out = Dim2
def apply(): Dim2 = Dim2()
}
implicit val dimMap3: Aux[Nat._3, Dim3] = new DimMap[Nat._3] {
type Out = Dim3
def apply(): Dim3 = Dim3()
}
// And so on as needed.
}
If you have more structure for your dimension type, it might be possible to avoid the boilerplate here, but it's not clear to me how your DigitN stuff is working. That seems orthogonal to the main point of the question, though, about how to define getDimensionTpe.
You could define it as something with a signature very much like the following, if you didn't mind writing a white-box macro:
def getDimensionTpe[D <: Dimension](dim: Int) : D
That's a huge pain in the ass, though, and Shapeless lets you write something that's essentially the same without custom macros. Given the DimMap type class above, you can write the following:
def getDimensionTpe(dim: Nat)(implicit m: DimMap[dim.N]) : m.Out = m()
And then:
scala> val d1: Dim1 = getDimensionTpe(1)
d1: Dim1 = Dim1()
scala> val d2: Dim2 = getDimensionTpe(2)
d2: Dim2 = Dim2()
scala> val d3: Dim3 = getDimensionTpe(3)
d3: Dim3 = Dim3()
If you get the static type wrong, the compiler will tell you:
scala> val d3: Dim1 = getDimensionTpe(3)
<console>:15: error: type mismatch;
found : DimMap.dimMap3.Out
(which expands to) Dim3
required: Dim1
val d3: Dim1 = getDimensionTpe(3)
^
If you provide an integer literal without a mapping, that's also a compile-time error:
scala> getDimensionTpe(0)
<console>:14: error: could not find implicit value for parameter m: DimMap[shapeless._0]
getDimensionTpe(0)
^
And finally if you provide an Int argument that isn't an integer literal, you'll also get a compiler error:
scala> val x = 1
x: Int = 1
scala> getDimensionTpe(x)
<console>:16: error: Expression x does not evaluate to a non-negative Int literal
getDimensionTpe(x)
^
Behind the scenes Shapeless is using a macro to make this possible, and it's not even that scary—you can read the NatMacros definition for details.

Get from `Some[A]` to `A`

Is there any way, to get to the type of A from Some[A]?
type X = Some[Int]
type Y = ??? // what do I have to write here to get `Int`
I can define my own Option-type that allows this:
sealed trait Option[+A]
case object None extends Option[Nothing]
case class Some[+A](a: A) {
type Inner = A
}
and then use
type X = Some[Int]
type Y = X#Inner
Is this also possible somehow with the normal Scala Option type?
here is a solution that uses path dependent type to recover type from value:
trait IsOption[F]{
type T
def apply(f: F): Option[T]
}
object IsOption{
def apply[F](implicit isf: IsOption[F]) = isf
implicit def mk[A] = new IsOption[Option[A]]{
type T = A
def apply(f: Option[A]): Option[A] = f
}
}
def getInner[A](in:A)(implicit inner: IsOption[A]): Option[inner.T] = inner(in)
The answer is heavily inspired from this slide of a brilliant presentation: http://wheaties.github.io/Presentations/Scala-Dep-Types/dependent-types.html#/2/1
You have a function that receives an opaque A, but you recover the fact that it is an option and the inner type via the IsOption[A] implicit.
I appreciate that this is not exactly what you asked for, but when you use such type-dependent types. you need to have a concrete value from which you recover a type.
You can write a simple type function as follows:
scala> type X = Some[Int]
defined type alias X
scala> type F[H <: Option[A], A] = A
defined type alias F
scala> type Y = F[X, Int]
defined type alias Y
scala> implicitly[Y =:= Int]
res3: =:=[Y,Int] = <function1>
Without partial type parameter application / inference it's not very usefull, but it works...

Two-way conversion between integers and Peano naturals

I am using a standard type-encoding of natural numbers in Scala. For the purpose of this question, the following definition will do:
sealed trait Nat
sealed trait _0 extends Nat
sealed trait Succ[N <: Nat] extends Nat
I can use the compiler to convert these Nat types into actual numbers, for instance by defining
class NatConverter[N <: Nat](val n: Int)
implicit val zeroConv: NatConv[_0] = new NatConv(0)
implicit def succConv[N <: Nat](implicit ev: NatConv[N]): NatConv[Succ[N]] =
new NatConv(ev.n + 1)
def nat2value[N <: Nat](implicit ev: NatConv[N]) = ev.n
This works:
type _1 = Succ[_0]
type _2 = Succ[_1]
nat2value[_2] // gives 2
I am trying to invert this correspondence, if possible at all, by exploiting dependent method return types. So, the first thing a need is a container for an Int and a Nat
trait Pair {
type N <: Nat
def n: Int
}
Now, I would like to be able to implicitly convert an Int to an instance of Pair, with the right value for N. Here it is
implicit def int2pair(a: Int): Pair =
if (a == 0) new Pair {
type N = _0
val n = 0
}
else {
val previous = int2pair(a - 1)
new Pair {
type N = Succ[previous.N]
val n = a
}
}
This does compile. Unfortunately
val two = int2pair(2)
implicitly[two.N =:= _2]
fails, as well as
val two = int2pair(2)
implicitly[two.N <:< _2]
Any idea why?
Because the return type of int2pair is just Pair, not Pair { type N = _2 }. The if/else happens at runtime, the compiler can't know which branch will be taken.
AFAIK the only way to go from value to type is with a macro. You might want to look at shapeless' singleton support.

How to infer the right type parameter from a projection type?

I have some troubles having Scala to infer the right type from a type projection.
Consider the following:
trait Foo {
type X
}
trait Bar extends Foo {
type X = String
}
def baz[F <: Foo](x: F#X): Unit = ???
Then the following compiles fine:
val x: Foo#X = ???
baz(x)
But the following won't compile:
val x: Bar#X = ???
baz(x)
Scala sees the "underlying type String" for x, but has lost the information that x is a Bar#X. It works fine if I annotate the type:
baz[Bar](x)
Is there a way to make Scala infer the right type parameter for baz?
If not, what is the general answer that makes it impossible?
The program compiles by adding this implicit conversion in the context:
implicit def f(x: Bar#X): Foo#X = x
As this implicit conversion is correct for any F <: Foo, I wonder why the compiler does not do that by itself.
You can also:
trait Foo {
type X
}
trait Bar extends Foo {
type X = String
}
class BarImpl extends Bar{
def getX:X="hi"
}
def baz[F <: Foo, T <: F#X](clz:F, x: T): Unit = { println("baz worked!")}
val bi = new BarImpl
val x: Bar#X = bi.getX
baz(bi,x)
but:
def baz2[F <: Foo, T <: F#X](x: T): Unit = { println("baz2 failed!")}
baz2(x)
fails with:
test.scala:22: error: inferred type arguments [Nothing,java.lang.String] do not conform to method baz2's type parameter bounds [F <: this.Foo,T <: F#X]
baz2(x)
^
one error found
I think basically, F <: Foo tells the compiler that F has to be a subtype of Foo, but when it gets an X it doesn't know what class your particular X comes from. Your X is just a string, and doesn't maintain information pointing back to Bar.
Note that:
def baz3[F<: Foo](x : F#X) = {println("baz3 worked!")}
baz3[Bar]("hi")
Also works. The fact that you defined a val x:Bar#X=??? just means that ??? is restricted to whatever Bar#X might happen to be at compile time... the compiler knows Bar#X is String, so the type of x is just a String no different from any other String.