type level pattern matching in scala - scala

Ideally I'd like to write Haskell style pattern matching on the type level in Scala, something like this:
Can shapeless be used for something like this ?
object Test{
type F[Int] = String
type F[Boolean] = Int // but this line does not compile
implicitly[String =:= F[Int]]
implicitly[Int =:= F[Boolean]]
}
In this example if F takes Int then it returns String and if it takes Boolean then it returns Int.
Clarification (based on this answer)
Here is how I'd like to use these types in functions and typeclasses:
abstract class WrappedF[T] {
type F
type Unwrap = T
}
type F[X <: WrappedF[_]] = X#F
class IntF extends WrappedF[Int] { type F = StringF }
class BooleanF extends WrappedF[Boolean] { type F = IntF }
class StringF extends WrappedF[String] { type F = Nothing }
implicitly[String =:= F[IntF]#Unwrap]
implicitly[Int =:= F[BooleanF]#Unwrap]
implicitly[String =:= F[F[BooleanF]]#Unwrap]
// this is a type class definition where `V` is a member of the `Test` class
// `f`'s type should be defined by `V`, but it does not work :(
trait Test[V <: WrappedF[V]]{
def f(a: F[V]#Unwrap): F[V]#Unwrap // this does not compile
}
implicit object TestImpl extends Test[IntF]{
override def f(a: F[IntF]#Unwrap): F[IntF]#Unwrap = {
val z: F[IntF]#Unwrap = "fd"+a
z
}
}

Here two non-shapeless solutions.
A little type-level table of constants with funny names:
type F = {
type Int = java.lang.String
type Boolean = scala.Int
}
implicitly[String =:= F#Int]
implicitly[Int =:= F#Boolean]
Here, F is a type with two type members that have names Int and Boolean (could be I and B, those two constants aren't really connected with Int or Boolean in any way). This doesn't compose: you can't write down something like F#F#Int.
You can lift every type T for which you want to define F into a type that has both T and F[T] as type members:
abstract class WrappedF[T] {
type F
type Unwrap = T
}
type F[X <: WrappedF[_]] = X#F
class IntF extends WrappedF[Int] { type F = StringF }
class BooleanF extends WrappedF[Boolean] { type F = IntF }
class StringF extends WrappedF[String] { type F = Nothing }
implicitly[String =:= F[IntF]#Unwrap]
implicitly[Int =:= F[BooleanF]#Unwrap]
implicitly[String =:= F[F[BooleanF]]#Unwrap]
This adds more noise because of ...F and #Unwrap, but is purely a type-level computation, and it composes (as the last example shows).
Update (better suited for abstracting over V <: WrappedF)
In your code under "Clarification", you were missing F <: WrappedF bound on the type member F:
abstract class WrappedF {
type F <: WrappedF
type Unwrap
}
type F[X <: WrappedF] = X#F
class IntF extends WrappedF { type Unwrap = Int; type F = StringF }
class BooleanF extends WrappedF { type Unwrap = Boolean; type F = IntF }
class StringF extends WrappedF { type Unwrap = String; type F = Nothing }
implicitly[String =:= F[IntF]#Unwrap]
implicitly[Int =:= F[BooleanF]#Unwrap]
implicitly[String =:= F[F[BooleanF]]#Unwrap]
trait Test[V <: WrappedF]{
def f(a: F[V]#Unwrap): F[V]#Unwrap // this does not compile
}
implicit object TestImpl extends Test[IntF] {
override def f(a: String): String = {
val z: F[IntF]#Unwrap = "fd" + a
z
}
}

You use a typeclass style implicit structure with a type member.
// sealed for closed family
class F[I] { type O }
object F {
type Rel[I, O0] = F[I] { type O = O0 }
/* private */ def mkF[I, O0]: Rel[I, O0] = new F[I] { override type O = O0 }
implicit val fInt: Rel[Int, String] = mkF
implicit val fBoolean: Rel[Boolean, Int] = mkF
def apply[I](implicit f: F[I]): f.type = f // f.type survives the refinement (see what breaks on removal)
}
locally { val temp = F[Int]; implicitly[temp.O =:= String] }
locally { val temp = F[Boolean]; implicitly[temp.O =:= Int] }
locally { val temp0 = F[Boolean]; val temp1 = F[temp0.O]; implicitly[temp1.O =:= String] }

Related

Implementing a typeclass using type parameters versus abstract types

Following on from Witness that an abstract type implements a typeclass
I've tried to compare these two approaches side-by-side in the code snippet below:
// We want both ParamaterizedTC and WithAbstractTC (below) to check that
// their B parameter implements AddQuotes
abstract class AddQuotes[A] {
def inQuotes(self: A): String = s"${self.toString}"
}
implicit val intAddQuotes = new AddQuotes[Int] {}
abstract class ParamaterizedTC[A, _B](implicit ev: AddQuotes[_B]) {
type B = _B
def getB(self: A): B
def add1ToB(self: A): String = ev.inQuotes(getB(self)) // TC witness does not need to be at method level
}
abstract class WithAbstractTC[A] private {
// at this point the compiler has not established that type B implements AddQuotes, even if we have created
// this instance via the apply[A, _B] constructor below...
type B
def getB(self: A): B
def add1ToB(self: A)(implicit ev: AddQuotes[B]): String =
ev.inQuotes(getB(self)) // ... so here the typeclass witness has to occur on the method level
}
object WithAbstractTC {
// This constructor checks that B implements AddQuotes
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
// But we could also have a constructor that does not check, so the compiler can never be certain that
// for a given instance of WithAbstractTC, type B implements AddQuotes
def otherConstructor[A, _B](getB: A => _B): WithAbstractTC[A] { type B = _B } = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
}
case class Container[B: AddQuotes]( get: B )
// These are both fine
implicit def containerIsParamaterized[B: AddQuotes]: ParamaterizedTC[Container[B], B] =
new ParamaterizedTC[Container[B], B] { def getB(self: Container[B]): B = self.get }
implicit def containerIsWithAbstract[_B: AddQuotes]: WithAbstractTC[Container[_B]] =
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsParamaterized: ParamaterizedTC[Container[Int], Int] =
implicitly[ParamaterizedTC[Container[Int], Int]]
val contIsWithAbstract: WithAbstractTC[Container[Int]] =
implicitly[WithAbstractTC[Container[Int]]]
implicitly[AddQuotes[contIsParamaterized.B]]
implicitly[AddQuotes[contIsWithAbstract.B]] // This is not fine
My conclusion (please correct me if I'm wrong) is that if the typeclass witness exists in the public constructor (as in ParamaterizedTC below) then the compiler can always be certain that B implements AddQuotes. Whereas if this witness is put in a constructor in the typeclass companion object (like for WithAbstractTC) then it cannot. This somewhat changes the usage of a type-parameter-based approach versus the abstract-type-based approach.
The difference is rather: in ParametrizedTC you have the implicit in scope of the class, in WithAbstractTC you don't. But nothing stops you from adding it when you have an abstract type:
abstract class WithAbstractTC2[A] private {
type B
implicit val ev: AddQuotes[B]
def getB(self: A): B
def add1ToB(self: A): String =
ev.inQuotes(getB(self))
}
def apply[A, _B](getB: A => _B)(implicit _ev: AddQuotes[_B]): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = _ev
def getB(self: A): B = getB(self)
}
What unfortunately won't work is something like
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = implicitly[AddQuotes[_B]]
def getB(self: A): B = getB(self)
}
because it'll pick the implicit in closest scope: the one it's trying to define.
implicitly[AddQuotes[contIsWithAbstract.B]] refusing to compile is not connected with single/multiple constructors/apply methods or type parameter/type member difference. You just lost type refinements everywhere. Compiler can't check that you lost type refinements. You have the right to upcast a type discarding its refinement.
If you restore type refinements the code compiles
object WithAbstractTC {
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] {type B = _B} =
// ^^^^^^^^^^^^^
new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
...
}
implicit def containerIsWithAbstract[_B: AddQuotes]:
WithAbstractTC[Container[_B]] { type B = _B } =
// ^^^^^^^^^^^^^^^
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsWithAbstract: WithAbstractTC[Container[Int]] { type B = Int } =
// ^^^^^^^^^^^^^^^^
shapeless.the[WithAbstractTC[Container[Int]]]
//^^^^^^^^^^^^^
implicitly[AddQuotes[contIsWithAbstract.B]] // compiles
Please notice that implicitly looses type refinements, shapeless.the is safe version.
When implicitly isn't specific enough https://typelevel.org/blog/2014/01/18/implicitly_existential.html
How to use class-level implicit constraint for type-member type class via abstract implicit see #AlexeyRomanov's answer.

Abstract type and path dependent type in scala

I'd like to use abstract type and type refinement to encode something like a functional dependency between two types.
trait BaseA {
type M
type P <: BaseB.Aux[M]
def f(m: M): Unit
}
trait BaseB {
type M
def m(): M
}
object BaseB {
type Aux[M0] = BaseB { type M = M0 }
}
It means that a A only works with a B if they have the same type M inside.
With the following concret classes
class A extends BaseA {
type M = Int
type P = B
def f(m: Int): Unit = {
println("a")
}
}
class B extends BaseB {
type M = Int
def m(): M = 1
}
So now they have both Int type as M, but the following code does not compile
val a: BaseA = new A
val b: BaseB = new B
def f[T <: BaseA](a: T, b: T#P): Unit = {
a.f(b.m())
}
The compiler tells me that a.f here expect a path dependent type a.M but it got a b.M.
My question here is how can I express the fact that I only need the M type matched in the type level, not the instance level? Or how can I prevent the M in def f(m: M): Unit becoming a path-dependent type?
Thanks!
I think the issue comes from b being related to a type T, and it being possible for a to be a subclass of T that could override M to be something else, making the two objects incompatible. For instance:
class A2 extends BaseA {
type M = String
type P = B2
def f(s: String): Unit = {
println(s)
}
}
class B2 extends BaseB {
type M = String
def m(): M = "foo"
}
val a: BaseA = new A
val b: BaseB = new B2
f[BaseA](a, b)
It seems like if your f were to compile, then all of this should compile too.
You can either make b's type dependent on a.P:
def f(a: BaseA)(b: a.P): Unit
Or I think the whole thing is simplified by not having the compatible types restriction on your classes, but rather require that one is a subclass of the other at the point that they interact:
trait BaseA {
type M
def f(m: M): Unit
}
trait BaseB {
type M
def m(): M
}
class A extends BaseA {
type M = Int
def f(m: Int): Unit = {
println("a")
}
}
class B extends BaseB {
type M = Int
def m(): M = 1
}
val a: A = new A
val b: B = new B
def f(a: BaseA, b: BaseB)(implicit sub: b.M <:< a.M): Unit = {
a.f(sub(b.m()))
}
f(a, b)
Or lastly, consider whether you need these to be path-dependent types at all; could they be regular generic type parameters?
trait BaseA[-M] {
def f(m: M): Unit
}
trait BaseB[+M] {
def m(): M
}
class A extends BaseA[Int] {
def f(m: Int): Unit = {
println("a")
}
}
class B extends BaseB[Int] {
def m(): Int = 1
}
def f[T](a: BaseA[T], b: BaseB[T]): Unit = {
a.f(b.m())
}

Abstract type, variables and typeclasses in Scala

I'm trying to make a typeclass that depends on user input. Imagine we have some case objects:
sealed trait H
case object Ha extends H
case object Hb extends H
and the type class:
trait Foo[A] {
def bar: String
}
object Foo {
def bar[A : Foo] = implicitly[Foo[A]].bar
implicit object FooA extends Foo[Ha.type] {
override def bar: String = "A"
}
implicit object FooB extends Foo[Hb.type] {
override def bar: String = "B"
}
}
While I found a working solution using a match:
variableComingFromMainArgs match {
case "a" => Foo.bar[Ha.type] _
case "b" => Foo.bar[Hb.type] _
}
I remember that we have abstract types in Scala, so I could change my case class into:
sealed trait H {
type T <: H
}
case object Ha extends H {
type T = this.type
}
case object Hb extends H {
type T = this.type
}
Now, when depending on user input to the program, I could do something like
val variable = Ha
println(Foo.bar[variable.T])
However, for some reason this doesn't work the and the error is not very useful for me:
error: could not find implicit value for evidence parameter of type Foo[variable.T]
println(Foo.bar[variable.T])
Any ideas if this can be overcome, if not, why?
Thanks.
Implicits are compile time constructs so in principle they cannot depend on user input directly (programmer can wire it for example with pattern matching as you did).
Consider the following code. It compiles and works as intended:
trait H {
type A
}
case object Ha extends H {
override type A = Int
}
case object Hb extends H {
override type A = Long
}
trait Adder[T] {
def add(a: T, b: T): T
}
implicit object IntAdder extends Adder[Int] {
override def add(a: Int, b: Int): Int = a + b
}
implicit object LongAdder extends Adder[Long] {
override def add(a: Long, b: Long): Long = a + b
}
def addWithAdder(input: H)(a: input.A, b: input.A)(implicit ev: Adder[input.A]): input.A = ev.add(a, b)
val x: Int = addWithAdder(Ha)(3, 4)
val y: Long = addWithAdder(Hb)(3, 4)
Let's focus on addWithAdder method. Thanks to path dependent types compiler can choose correct implicit for this task. But still this method is basically the same as the following:
def add[T](a: T, b: T)(implicit ev: Adder[T]) = ev.add(a, b)
The only advantage first one can have is that you can provide all instances yourself and stop the user of your code to add own types (when H is sealed and all implementations are final).

Runtime type checks / pattern matching involving variable arity types

I am revisiting a problem of generic wrappers for grouping heterogeneous elementary types together. I am using type members, so now the structure looks like this:
trait Outer[S] {
type A1
def inner: Inner[S] { type A = A1 }
}
trait Inner[S] {
type A
def peer: A
}
The problem then of course is to test for specific objects, e.g.:
def test[S](o: Outer[S]): Option[Outer[S] { type A1 = String }] = o match {
case os: Outer[S] { type A1 = String } => Some(os)
case _ => None
}
This doesn't work because of type erasure. The problem is that I must abstract over type parameter arity for the the peer, that is, there are (most) peers which do have one type parameter [S] as well, but others don't. Therefore, using a type constructor parameter for Inner and/or Outer is not feasible.
The cheap solution is to require actual sub-classes:
trait StringOuter[S] extends Outer[S] { type A1 = String }
def test1[S](o: Outer[S]): Option[Outer[S] { type A1 = String }] = o match {
case os: StringOuter[S] => Some(os)
case _ => None
}
But I don't like this solution because I will have a lot of different peers, and I don't want to create dedicated wrapper classes for each of them. Also for example copying these objects becomes annoying if I have to write the copying method in each and every of these sub-classes.
So I'm left with class-tags perhaps? How would this be solved if I have the following two peer types with different type parameter arity:
trait Foo[S]
type Inner1[S] = Inner[S] { type A = Foo[S] }
type Inner2[S] = Inner[S] { type A = String }
?
If we forget about the pattern matching for a moment, one can use plain old reflection to some extent:
import reflect.ClassTag
trait Outer[S] {
type A1
def inner: Inner[S] { type A = A1 }
def as[A](implicit tag: ClassTag[A]): Option[Outer[S] { type A1 = A }] =
inner.peer match {
case _: A => Some(this.asInstanceOf[Outer[S] { type A1 = A }])
case _ => None
}
}
trait Inner[S] {
type A
def peer: A
}
Test:
trait Foo[S]
val x = new Outer[Unit] {
type A1 = String
val inner = new Inner[Unit] {
type A = String
val peer = "foo"
}
}
val y = new Outer[Unit] {
type A1 = Foo[Unit]
val inner = new Inner[Unit] {
type A = Foo[Unit]
val peer = new Foo[Unit] {}
}
}
val xs = x.as[String]
val xi = x.as[Foo[Unit]]
val ys = y.as[String]
val yi = y.as[Foo[Unit]]
The only problem now is that higher-kinded types are not checked:
y.as[Foo[Nothing]] // Some!
The other idea is to change my design to require the S parameter to be always present. Then
trait Sys[S <: Sys[S]]
trait Inner[S <: Sys[S], +Elem[~ <: Sys[~]]] {
def peer: Elem[S]
def as[A[~ <: Sys[~]]](implicit tag: ClassTag[A[S]]): Option[Inner[S, A]] =
if (tag.unapply(peer).isDefined)
Some(this.asInstanceOf[Inner[S, A]])
else
None
}
type In[S <: Sys[S]] = Inner[S, Any]
trait Foo[S <: Sys[S]] { def baz = 1234 }
trait Bar[S <: Sys[S]]
trait I extends Sys[I]
val i: In[I] = new Inner[I, Foo] { val peer = new Foo[I] {} }
val j: In[I] = new Inner[I, Bar] { val peer = new Bar[I] {} }
assert(i.as[Foo].isDefined)
assert(i.as[Bar].isEmpty )
assert(j.as[Foo].isEmpty )
assert(j.as[Bar].isDefined)
Or the last version changed back to using a type member:
trait Inner[S <: Sys[S]] {
type Elem
def peer: Elem
def as[A[~ <: Sys[~]]](implicit tag: ClassTag[A[S]]): Option[InnerT[S, A]] =
if (tag.unapply(peer).isDefined)
Some(this.asInstanceOf[InnerT[S, A]])
else
None
}
type InnerT[S <: Sys[S], A[~ <: Sys[~]]] = Inner[S] { type Elem = A[S] }
val i: Inner[I] = new Inner[I] { type Elem = Foo[I]; val peer = new Foo[I] {} }
val j: Inner[I] = new Inner[I] { type Elem = Bar[I]; val peer = new Bar[I] {} }
assert(i.as[Foo].isDefined)
assert(i.as[Bar].isEmpty )
assert(j.as[Foo].isEmpty )
assert(j.as[Bar].isDefined)
val ix: InnerT[I, Foo] = i.as[Foo].get
ix.peer.baz
...that may be advantageous for implicit resolution such as Serializer[Inner[S]] which can be easily broken when variant type parameters are involved.
Thanks to type erasure, after fiddling around with this for hours, this is the only viable solution:
trait StringOuter[S] extends Outer[S] { type A1 = String }
Trying anything else is a waste of energy. Just stick with flat classes and invariant types. Forget about the scenario of covariant types and pattern matching.

How to express type constraints for higher kinded type

I am trying to create a list of some trait, parameterised by a type using the CRTP, and cannot figure out how to express the type constraints. Here is some sample code that illustrates the problem:
trait A[X] {
def x: X
}
trait B[Y <: A[Y]] {
def y(i: Int): Y
}
case class C(i: Int) extends A[C] {
def x = C(i)
}
case class D(i: Int) extends A[D] {
def x = D(i)
}
case class E() extends B[C] {
def y(i: Int) = C(i)
}
case class F() extends B[D] {
def y(i: Int) = D(i)
}
object Program extends App {
def emptyList[X[_ <: Z forSome { type Z <: A[Z] } ]]() = collection.mutable.ListBuffer.empty[X[_]]
val myList = emptyList[B]()
myList += E()
myList += F()
println(myList.map(_.y(2).x))
}
So here I'm trying to create a list of objects that conform to the B trait. However this code will not compile, and gives the following error:
kinds of the type arguments (B) do not conform to the expected kinds of the type parameters (type X). B's type parameters do not match type X's expected parameters: type Y's bounds >: Nothing <: A[Y] are stricter than type _'s declared bounds >: Nothing <: Z forSome { type Z <: A[Z] } val myList = emptyList[B] ()
To me it seems like _ <: Z forSome { type Z <: A[Z] } is indeed at least as strict as Y <: A[Y] but maybe I'm missing something.
So the question is - what should the constraints be on the emptyList function to correctly handle B?
After some trial and error, I got it to work. Note: the compiler tells us that type parameters in A[+X] and B[+Y] must be covariant.
trait A[+X] {
def x: X
}
trait B[+Y <: A[Y]] {
def y(i: Int): Y
}
case class C(i: Int) extends A[C] {
def x = C(i)
}
case class D(i: Int) extends A[D] {
def x = D(i)
}
case class E() extends B[C] {
def y(i: Int) = C(i)
}
case class F() extends B[D] {
def y(i: Int) = D(i)
}
object Test extends App {
def emptyList[X[Y <: A[Y]]] = collection.mutable.ListBuffer.empty[X[Y forSome {type Y <: A[Y]} ]]
val myList = emptyList[B]
myList += E()
myList += F()
println(myList.map(_.y(2).x))
}