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.
Related
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.
Currently I have a piece of program that implements peano arithmetic:
sealed trait NaturalNumber
and a function called getResource that picks up natural number type-value in its arguments which have the interface:
sealed trait VersionNumber {
type Nat <: NaturalNumber
}
and checks the values against a reference type-value version numbers: MAJ and MIN, provided in this interface:
trait ResourceManifest {
def getResource: Int
type Major <: NaturalNumber
type Minor <: NaturalNumber
}
depending on which, the function does or does not compile. The function has this form:
def getResource(manifest: ResourceManifest)(maj: VersionNumber, min: VersionNumber)
(implicit
maj_check: manifest.Major IsEqual maj.Nat,
min_check: manifest.Minor IsLessOrEqual min.Nat
) = manifest.getResource
Here is the full code. (Here is an alternative implementation if you like type-recursion.)
As it is, this is powered by overriding type values which the average Scala user might not be too comfortable with. Also, getResource gets separate arguments for the major and minor versions.
Ideally I would like to user to provide values instead of types say in a wrapper class:
case class VersionInfo(major: VersionNumber, minor: VersionNumber)
so that my manifest is of this form:
trait ResourceManifestRefactored {
def getResource: Int
val versionInfo: VersionInfo
}
and similarly have:
def getResourceRefactored(manifest: ResourceManifestRefactored)(versionInfo: VersionInfo)
and do my type level constraints by picking up the version-types from the wrapper version value class: VersionInfo. However I am struggling to get it to work despite me doing it in many different ways. for instance I tried using doing my type checks directly with path-dependent types but that failed. I also tried defining MAJ and MIN based on the types in inside VersionInfo but the type constraints are no longer working the way they are expected to. I understand we are potentially facing the similar problems that are solved by the likes of aux-pattern but I am struggling to fit a similar solution to my problem.
Essentially, I want to have pre-defined objects wrapping types and I want to do type constraints through these objects instead of the types directly.
Is there a fundumental reason why I can't and if not, how can I do it?
Suppose that __1 and __2 are two objects that extend VersionNumber, with two different types _1, _2 that extend Nat inside them. Is there any reason why the compiler would reject to compile
val foo: VersionInfo = VersionInfo( if (math.random < 0.5) __1 else __2, __2)
? In your current code, there is no reason why the compiler would reject this. This means that your VersionInfo breaks the constant paths between the outer constants __1, __2 and the inner values major and minor, which are stored inside of your VersionInfo. For example, as soon as you pass __1 as major to VersionInfo foo, the information that __1.Nat is the same type as foo.major.Nat is lost forever.
This can be easily solved by simply not throwing this type information away, but instead attaching it as type parameters to VersionInfo.
Assuming that your natural numbers looks somewhat like this:
sealed trait NaturalNumber
class _3 extends NaturalNumber
class _2 extends _3
class _1 extends _2
class _0 extends _1
class VersionNumber {
type Nat <: NaturalNumber
}
val __0 = new VersionNumber { type Nat = _0 }
val __1 = new VersionNumber { type Nat = _1 }
val __2 = new VersionNumber { type Nat = _2 }
val __3 = new VersionNumber { type Nat = _3 }
type IsEqual[A, B] = A =:= B
type IsLessOrEqual[A, B] = A <:< B
you can define VersionInfo and ResourceManifest as follows:
case class VersionInfo[Major, Minor](
major: VersionNumber { type Nat = Major },
minor: VersionNumber { type Nat = Minor }
)
trait ResourceManifest {
def getResource: Int
type Major <: NaturalNumber
type Minor <: NaturalNumber
}
and then use them as argument types of getResource:
def getResource[A, B]
(manifest: ResourceManifest)
(versionInfo: VersionInfo[A, B])
(implicit
maj_check: manifest.Major IsEqual A,
min_check: manifest.Minor IsLessOrEqual B
)
: Unit = println("it compiles, ship it")
A little test:
val manifest21 = new ResourceManifest {
def getResource = 21
type Major = _2
type Minor = _1
}
val manifest22 = new ResourceManifest {
def getResource = 22
type Major = _2
type Minor = _2
}
getResource(manifest21)(VersionInfo(__2, __1))
getResource(manifest21)(VersionInfo(__2, __2))
// getResource(manifest22)(VersionInfo(__2, __1)) // won't compile, good
getResource(manifest22)(VersionInfo(__2, __2))
In the code above, I've tried to use the same names as in this answer of yours from few months ago.
Similar, but slightly different alternative solution that does not use any type parameters on VersionInfo or ResourceManifest, and instead relies on Aux-typedefs.
Allows to instantiate both VersionInfos and ResourceManifests from VersionNumber objects, without using any explicit type parameters anywhere.
Here's the whole thing as a single wall of code:
sealed trait NaturalNumber
class _3 extends NaturalNumber
class _2 extends _3
class _1 extends _2
class _0 extends _1
abstract class VersionNumber {
type Nat <: NaturalNumber
def toInt: Int
}
val __0 = new VersionNumber { type Nat = _0 ; def toInt = 0 }
val __1 = new VersionNumber { type Nat = _1 ; def toInt = 1 }
val __2 = new VersionNumber { type Nat = _2 ; def toInt = 2 }
val __3 = new VersionNumber { type Nat = _3 ; def toInt = 3 }
object VersionNumber {
type Aux[N <: NaturalNumber] = VersionNumber {
type Nat = N
}
}
type IsEqual[A, B] = A =:= B
type IsLessOrEqual[A, B] = A <:< B
abstract class VersionInfo {
type Major <: NaturalNumber
type Minor <: NaturalNumber
val major: VersionNumber.Aux[Major]
val minor: VersionNumber.Aux[Minor]
}
object VersionInfo {
type Aux[A <: NaturalNumber, B <: NaturalNumber] = VersionInfo {
type Major = A
type Minor = B
}
def apply[A <: NaturalNumber, B <: NaturalNumber](
a: VersionNumber { type Nat = A },
b: VersionNumber { type Nat = B }
): VersionInfo.Aux[A, B] = new VersionInfo {
type Major = A
type Minor = B
val major = a
val minor = b
}
}
abstract class ResourceManifest {
type Major <: NaturalNumber
type Minor <: NaturalNumber
val major: VersionNumber.Aux[Major]
val minor: VersionNumber.Aux[Minor]
def getResource: Int = major.toInt * 10 + minor.toInt
}
object ResourceManifest {
type Aux[A <: NaturalNumber, B <: NaturalNumber] = ResourceManifest {
type Major = A
type Minor = B
}
def apply[A <: NaturalNumber, B <: NaturalNumber](
a: VersionNumber { type Nat = A },
b: VersionNumber { type Nat = B }
): ResourceManifest.Aux[A, B] = new ResourceManifest {
type Major = A
type Minor = B
val major = a
val minor = b
}
}
def getResource[
MnfMaj <: NaturalNumber,
MnfMin <: NaturalNumber,
VrsMaj <: NaturalNumber,
VrsMin <: NaturalNumber
]
(manifest: ResourceManifest.Aux[MnfMaj, MnfMin])
(versionInfo: VersionInfo.Aux[VrsMaj, VrsMin])
(implicit
maj_check: MnfMaj IsEqual VrsMaj,
min_check: MnfMin IsLessOrEqual VrsMin
)
: Unit = println("it compiles, ship it")
val manifest21 = ResourceManifest(__2, __1)
val manifest22 = ResourceManifest(__2, __2)
getResource(manifest21)(VersionInfo(__2, __1))
getResource(manifest21)(VersionInfo(__2, __2))
// getResource(manifest22)(VersionInfo(__2, __1)) // still won't compile, good
getResource(manifest22)(VersionInfo(__2, __2))
I'm trying to print peano numbers, something like this:
sealed trait Nat
trait _0 extends Nat
trait Succ[N <: Nat] extends Nat
type _1 = Succ[_0]
type _2 = Succ[_1]
class RepNat[T <: Nat](val value: Int)
def rep[T <: Nat](implicit r: RepNat[T]) = r.value
implicit val repZero = new RepNat[_0](0)
implicit def repSucc[A <: Succ[B], B <: Nat](implicit r: RepNat[B]): RepNat[A] = new RepNat[A](r.value + 1)
println(rep[_0])
println(rep[_1])
// does not work, implicits do not resolve recursively:
// implicitly[RepNat[_2]]
// println(rep[_2])
// but explicit instantiation works:
println(rep[_2](repSucc(implicitly[RepNat[_1]])))
Recursive implicits do work. The following definition works fine for _2:
implicit def repSucc[A <: Nat, B <: Nat](implicit
ev: A <:< Succ[B],
r: RepNat[B]
): RepNat[A] =
new RepNat[A](r.value + 1)
The reason, I believe, is that
repSucc gets a single actual type argument A, and needs to calculate B from that. With your definition it tries to assign A and B at the same time, and thus B gets assigned effectively to Nothing.
This a common problem with type inference in Scala, and the usual solution is to move the type bound A <: M[B] to a generalised type constraint A <:< M[B].
Also, note that the order of implicit parameters matters: first the compiler calculates B from A with ev: A <:< Succ[B], and then finds the RepNat implementation for B, possibly recursively.
Thanks to #MilesSabin's answer I can write a type level Fibonacci sequence:
sealed trait Digit
case object Zero extends Digit
case object One extends Digit
sealed trait Dense { type N <: Dense }
sealed trait DNil extends Dense { type N = DNil }
case object DNil extends DNil
final case class ::[+H <: Digit, +T <: Dense](digit: H, tail: T) extends Dense {
type N = digit.type :: tail.N
}
/* The `A`th Fibonacci number is `B` */
trait Fib[A <: Dense, B <: Dense]
object Fib {
implicit val f0 = new Fib[_0, _0] {}
implicit val f1 = new Fib[_1, _1] {}
implicit def f2[A <: Dense, P <: Dense, P2 <: Dense, F <: Dense, F2 <: Dense]
(implicit p: Pred.Aux[A, P],
p2: Pred.Aux[P, P2],
f: Fib[P, F],
f2: Fib[P2, F2],
sum: Sum[F, F2]): Fib[A, sum.Out] = new Fib[A, sum.Out] {}
}
implicitly[Fib[_7, _13]]
What I'd really like to be able to do is get a Witness for Dense and use it like:
def apply[Out <: Dense](n: Dense)(implicit f:Fib[n.N, Out], w:Witness.Aux[Out]): Out
= w.value
Scala tells me that it can't summon a Witness instance. I'm guessing this is because my type-level encoding of natural numbers is a linked list of bits and that's not a singleton type. I can't understand why Witness won't work since there is only a single inhabitant for a class like _7.
What I'm trying to do is materialize a value for a type that only has one possible value. That way I can get an Out directly from apply.
I think a possible solution might leverage implicit macros.
Any and all ideas are welcome. I encourage you to leave a note if the question isn't clear.
Type macros are off.
However I have two important use cases that would have required them. The result is an important lost of extensibility in my application.
Both are dynamic compile time generation of a type given other types.
Basically i want to do something like (obviously not scala code but i think you'll get the idea) :
type T[U] = macro usecase1[U]
def usecase1[U]= U match {
case t if (t <:< Int) => String
case ... => ...
}
Second use case is :
type Remaining[A, B >: A] = macro ...
where for example
class C
trait T1 extends C
trait T2 extends C
type Remaining[C with T1 with T2, T2] is assigned to "C with T1" at compile time
(so the macro would have generated the subclass list, and generated a new type from the list without T2)
I didn't do it with macros so that are assumptions. I planned to do it now.. till i saw that type macro were dead.
Anyway, did anyone knows a trick to obtain such functionalities?
Thanks
The first use case is indeed implementable with implicits to some degree, as far as I understand it. Here's an example of how this might look like:
scala> trait Bound[A] {
| type Type
| }
defined trait Bound
scala> implicit val bound1 = new Bound[Int] { type Type = String }
bound1: Bound[Int]{type Type = String}
scala> implicit val bound2 = new Bound[String] { type Type = Double }
bound2: Bound[String]{type Type = Double}
scala> val tpe = implicitly[Bound[Int]]
tpe: Bound[Int] = $anon$1#2a6b3a99
scala> type U = tpe.Type
defined type alias U
But then:
scala> implicitly[U =:= String]
<console>:19: error: Cannot prove that U =:= String.
implicitly[U =:= String]
^
On the other hand:
scala> implicitly[bound1.Type =:= String]
res0: =:=[bound1.Type,String] = <function1>
Implicit resolution seems to be losing some types on the way. Not sure why, and how to work around that.
For the second use case, HLists immediately come to mind. Something like:
scala> trait Remaining[A <: HList, B] { type Result = Remove[A, B] }
defined trait Remaining
scala> new Remaining[C :: T1 :: T2 :: HNil, T2] {}
res5: Remaining[shapeless.::[C,shapeless.::[T1,shapeless.::[T2,shapeless.HNil]]],T2] = $anon$1#3072e54b
Not sure how to combine the resulting HList into a compound type though. Something like (pseudo-code):
trait Remaining[A <: HList, B] {
def produceType(
implicit ev0 : Remove.Aux[A, B, C],
ev1 : IsCons.Aux[C, H, T],
ev2 : LeftFolder[T, H, (T1, T2) => T1 with T2]) = ev2
// ^ Not real syntax, type-level function to combine/mix types
val tpe = produceType
type Result = tpe.Out
}