First off, I don't know how to properly label my problem. This might also be the reason why I didn't find helpful resources. Any hints are highly appreciated.
trait Context[T]
{
self =>
trait Rule
{
def apply( value: T ): Boolean
}
implicit class RichRule[A <: Rule]( a: A )
{
def and[B <: Rule]( b: B ): and[A, B] = self.and( a, b )
def or[B <: Rule]( b: B ): or[A, B] = self.or( a, b )
}
sealed trait Group[A <: Rule, B <: Rule] extends Rule
{
def a: A
def b: B
override def apply( value: T ) = ???
}
case class and[A <: Rule, B <: Rule]( a: A, b: B ) extends Group[A, B]
case class or[A <: Rule, B <: Rule]( a: A, b: B ) extends Group[A, B]
}
Given the above code, I can now define and chain Ruless in this fashion:
new Context[String]
{
class MyRule extends Rule
{
override def apply( value: String ) = true
}
case class A() extends MyRule
case class B() extends MyRule
val x1: A and B or A = A() and B() or A()
}
This works as I intended but now comes the tricky part. I want to introduce a Type Class Combination that explains how to join two rules.
trait Combination[-A <: Rule, -B <: Rule]
{
type Result <: Rule
def combine( a: A, b: B ): Result
}
trait AndCombination[-A <: Rule, -B <: Rule] extends Combination[A, B]
trait OrCombination[-A <: Rule, -B <: Rule] extends Combination[A, B]
This Type Class should now be passed with the operators.
implicit class RichRule[A <: Rule]( a: A )
{
def and[B <: Rule]( b: B )( implicit c: AndCombination[A, B] ): and[A, B] = ???
def or[B <: Rule]( b: B )( implicit c: OrCombination[A, B] ): or[A, B] = self.or( a, b )
}
Which is still working after some tweaks.
implicit val c1 = new Combination[MyRule, MyRule]
{
type Result = MyRule
def combine( a: A, b: B ): MyRule = a
}
val x: A and B = A() and B()
But if it gets more complicated, things are falling apart.
A() and B() and A()
Will raise an implicit missing error: Combination[and[A, B], A] is missing. But I want it to use the result of the implicit combination of and[A, B] (type Result = MyRule) which it already knows how to handle (Combination[and[A, B]#Result, A]).
It is important for me to keep the type information of combined rules val x: A and B or A, folding them together to a final result type is easy, but not what I want.
This is as close as I could get, it fails compilation, though.
trait Context[T]
{
self =>
trait Rule
trait Value extends Rule
trait Group[A <: Rule, B <: Rule] extends Rule
{
def a: A
def b: B
implicit val resolver: Resolver[_ <: Group[A, B]]
}
case class and[A <: Rule, B <: Rule]( a: A, b: B )( implicit val resolver: Resolver[and[A, B]] ) extends Group[A, B]
implicit class RichRule[A <: Rule]( a: A )
{
def and[B <: Rule]( b: B )( implicit resolver: Resolver[and[A, B]] ) = self.and[A, B]( a, b )
}
trait Resolver[-A <: Rule]
{
type R <: Value
def resolve( a: A ): R
}
}
object O extends Context[String]
{
implicit val c1 = new Resolver[A and A]
{
override type R = A
override def resolve( a: O.and[A, A] ) = ???
}
implicit def c2[A <: Value, B <: Value, C <: Value]( implicit r1: Resolver[A and B] ) = new Resolver[A and B and C]
{
override type R = C
override def resolve( a: A and B and C ): C =
{
val x: r1.R = r1.resolve( a.a )
new c2( x )
???
}
}
class c2[A <: Value, B <: Value]( val a: A )( implicit r2: Resolver[A and B] ) extends Resolver[A and B]
{
override type R = B
override def resolve( a: O.and[A, B] ) = a.b
}
case class A() extends Value
val x: A and A and A = A() and A() and A()
}
The reason while your code can't compile is that at the instruction
new c2( x )
The compiler need to resolve a implicit r2: Resolver[A and B] from x and the only type information available is the type of x, which is r1.R.
This sort of problems requires making more type information available to the compiler and adding some implicit parameter. When you require a Resolver[A and B], you can't use its R type to resolve another Resolver[r1.R and C].
type ResolverAux[-A<:Rule,B] = Resolver[A] { type R = B }
With this available, you can rewrite the signature of your c2
implicit def c2[A <: Value, B <: Value, C <: Value,D<:Value]( implicit r1: ResolverAux[A and B,D], r2:Resolver[D and C] ):Resolver[A and B and C] = new Resolver[A and B and C]
{
override type R = C
override def resolve( a: A and B and C ): C =
{
val x: r1.R = r1.resolve( a.a )
new c2[r1.R,C]( x )
???
}
}
Notice that by using the type alias and introducing an additional generic parameter, I can express the relation r1.R1 = D which is then used to resolve the second implicit r2
Related
I'm writing an implementation for Curry-Howard isomorphism in scala, a family of proofs can be defined as an object implementing the following trait:
(the full code repo is uploaded to https://github.com/tribbloid/shapesafe/blob/djl/prove-debug/macro/src/main/scala/org/shapesafe/core/shape/unary/WithNames.scala)
trait ProofSystem[OUB] { // TODO: no IUB?
trait Proposition extends Serializable {
type Codomain <: OUB
def value: Codomain
}
case object Proposition {
type Aux[O <: OUB] = Proposition {
type Codomain = O
}
type Lt[+O <: OUB] = Proposition {
type Codomain <: O
}
// Can't use Aux, syntax not supported by scala
trait Of[O <: OUB] extends Proposition {
final type Codomain = O
}
case class ToBe[O <: OUB](override val value: O) extends Of[O]
}
// doesn't extend T => R intentionally
// each ProofSystem use a different one to alleviate search burden of compiler (or is it redundant?)
trait CanProve[-I, +P <: Proposition] {
def apply(v: I): P
final def valueOf(v: I): P#Codomain = apply(v).value // TODO: this should be real apply? The above should be 'prove'
}
object CanProve {}
/**
* representing 2 morphism:
*
* - value v --> value apply(v)
*
* - type I --> type O
*
* which is why it uses =>>
* #tparam I src type
* #tparam P tgt type
*/
class =>>^^[-I, P <: Proposition](
val toProof: I => P
) extends CanProve[I, P] {
override def apply(v: I): P = toProof(v)
}
type -->[-I, O <: OUB] = CanProve[I, Proposition.Aux[O]]
type ~~>[-I, +O <: OUB] = CanProve[I, Proposition.Lt[O]]
class =>>[-I, O <: OUB](
val toOut: I => O
) extends =>>^^[I, Proposition.ToBe[O]](v => Proposition.ToBe(toOut(v)))
def from[I]: Factory[I] = new Factory[I]()
class Factory[I]() {
def =>>^^[P <: Proposition](fn: I => P) = new (I =>>^^ P)(fn)
def =>>[O <: OUB](fn: I => O) = new (I =>> O)(fn)
}
def at[I](v: I) = new Summoner[I](v)
class Summoner[I](v: I) extends Factory[I] {
implicit def summon[P <: Proposition](
implicit
ev: CanProve[I, P]
): P = ev.apply(v)
implicit def summonValue[P <: Proposition](
implicit
ev: CanProve[I, P]
): P#Codomain = summon(ev).value
}
}
In one particular case, the compiler got into a dead loop when 2 implicits are defined in a scope:
case class WithNames[
S1 <: Shape,
N <: Names
](
s1: S1,
newNames: N
) extends Shape {}
object WithNames {
// TODO: DEAD LOOP!
implicit def axiom[
P1 <: LeafShape,
N <: Names,
HO <: HList,
O <: LeafShape
](
implicit
zip: ZipWithKeys.Aux[N#Keys, P1#_Dimensions#Static, HO],
toShape: LeafShape.FromRecord.==>[HO, O]
): WithNames[P1, N] =>> O = {
from[WithNames[P1, N]].=>> { src =>
val keys: N#Keys = src.newNames.keys
val p1: P1 = src.s1
val values: P1#_Dimensions#Static = p1.dimensions.static
val zipped: HO = values.zipWithKeys(keys)
LeafShape.FromRecord(zipped)
}
}
implicit def theorem[
S1 <: Shape,
N <: Names,
P1 <: LeafShape,
O <: LeafShape
](
implicit
lemma1: S1 ~~> P1,
lemma2: WithNames[P1, N] --> O
): WithNames[S1, N] =>> O = {
from[WithNames[S1, N]].=>> { src =>
val p1: P1 = lemma1.valueOf(src.s1)
lemma2.valueOf(
src.copy(p1)
)
}
}
}
The compiler seems to be stuck on a cyclic proof resolution: it tries to fulfil lemma2 of theorem with itself. If I merge 2 implicits into 1, the problem disappeared:
implicit def asLeaf[
S1 <: Shape,
P1 <: LeafShape,
N <: Names,
HO <: HList,
O <: LeafShape
](
implicit
lemma: S1 ~~> P1,
zip: ZipWithKeys.Aux[N#Keys, P1#_Dimensions#Static, HO],
toShape: LeafShape.FromRecord.==>[HO, O]
): WithNames[S1, N] =>> O = {
from[WithNames[S1, N]].=>> { src =>
val keys: N#Keys = src.newNames.keys
val p1: P1 = lemma.valueOf(src.s1)
val values: P1#_Dimensions#Static = p1.dimensions.static
val zipped: HO = values.zipWithKeys(keys)
LeafShape.FromRecord(zipped)
}
}
So my questions are:
Why this could happen and what change of the scala compiler algorithm could eliminate any cyclic search for proves?
How to manually circumvent this error?
I also realised that the canonical implementation of homotopy type theory (https://github.com/siddhartha-gadgil/ProvingGround) actually does most of the inference in runtime, instead of compile-time, which seems to be defeating the purpose. Perhaps this bug is part of the reason?
I'm trying to deal with an ambiguous implicits problem, and (relatedly) figure out what best practise should be for parameterizing typeclasses. I have a situation where I am using a typeclass to implement a polymorphic method. I initially tried the approach below:
abstract class IsValidTypeForContainer[A]
object IsValidTypeForContainer {
implicit val IntIsValid = new IsValidTypeForContainer[Int] {}
implicit val DoubleIsValid = new IsValidTypeForContainer[Double] {}
}
abstract class IsContainer[A, B: IsValidTypeForContainer] {
def getElement(self: A, i: Int): B
def get[R](self: A, ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = gets.get(self, ref)
def fromList(self: A, other: List[B]): A = ???
}
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
) {
def getElement(i: Int) = isCont.getElement(self, i)
def get[R](ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = isCont.get(self, ref)
def fromList(other: List[B]): A = isCont.fromList(self, other)
}
abstract class GetsFromContainerMax[A, R, B: IsValidTypeForContainer] {
type Out
def get(self: A, ref: R): Out
}
object GetsFromContainerMax {
type Aux[A, R, B, O] = GetsFromContainerMax[A, R, B] { type Out = O }
def instance[A, R, B: IsValidTypeForContainer, O](func: (A, R) => O): Aux[A, R, B, O] = new GetsFromContainerMax[A, R, B] {
type Out = O
def get(self: A, ref: R): Out = func(self, ref)
}
implicit def getsForListInt[A, B: IsValidTypeForContainer](implicit
isCons: IsContainer[A, B],
): Aux[A, List[Int], B, A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst)
}
)
}
Where I have given the GetsContainerMax typeclass three parameters - one for the IsContainer object, one for the reference and one for the data type of the IsContainer object.
When I then try to use this, I get a compile error:
case class List1[B: IsValidTypeForContainer] (
data: List[B]
)
implicit def list1IsContainer[B: IsValidTypeForContainer] = new IsContainer[List1[B], B] {
def getElement(self: List1[B], i: Int): B = self.data(i)
def fromList(self: List1[B], other: List[B]): List1[B] = ???
}
val l1 = List1[Int](List(1,2,3))
implicitly[IsContainer[List1[Int], Int]].get(l1, List(1,2)) // Works
implicitly[List1[Int] => IsContainerOps[List1[Int], Int]] // Works
l1.get(List(1,2)) // Does not work
If I use the -Xlog-implicits build parameter, it tells me that
ambiguous implicit values: both value IntIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Int] and value DoubleIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Double] match expected type example.Test.IsValidTypeForContainer[B]
Which seems to make sense; presumably I am bringing both of these implicits into scope by parameterizing the typeclass with the generic B.
My next thought was therefore to try to reduce the number of generic parameters for IsValidTypeForContainer to the minimum, in order to have only one typeclass in scope per type of R, likeso:
abstract class GetsFromContainerMin[R] {
type Out
def get[A](self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out
}
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
def instance[A, R, O](func: (A, R) => O): Aux[R, O] = new GetsFromContainerMin[R] {
type Out = O
def get(self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out = func(self, ref)
}
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst) // type mismatch - found: List[Any], required: List[_$3]
}
)
}
But this seems to not only not solve the problem, but to generate an additional error in that the compiler can no longer type-check that type B implements IsValidTypeForContainer.
Any help gratefully received.
So I've messed around with this a bit and seem to have found the solution. The typeclass-with-three-parameters approach works, if I use
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
)
instead of
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
)
I am not exactly sure why this is and if anyone would care to respond I'd be interested to know. Are multiple typeclasses created if you use a context bound like in the original example, one for each implementation of IsValidTypeForContainer?
Regarding GetsFromContainerMin, only type classes without polymorphic methods can have constructor method instance (in companion object) because of the lack of polymorphic functions in Scala 2. In Scala 3 you'll be able to write
def instance[R, O](func: [A] => (A, R) => O): Aux[R, O] = ...
So far you have to write
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = new GetsFromContainerMin[List[Int]] {
override type Out = A
override def get[A1](self: A1, ref: List[Int])(implicit isCont: IsContainer[A1, _]): Out = {
val lst = ref.map(isCons.getElement(self, _)).toList
// ^^^^
isCons.fromList(self, lst)
// ^^^^
}
}
}
I guess compile errors are pretty clear
type mismatch;
found : self.type (with underlying type A1)
required: A
Regarding your first question,
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B]
)
is desugared to
implicit class IsContainerOps[A, B](self: A)(implicit
ev: IsValidTypeForContainer[B],
isCont: IsContainer[A, B]
)
but order of implicits is significant. If you modify IsContainerOps to
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
ev: IsValidTypeForContainer[B],
)
then l1.get(List(1,2)) will compile.
Implicits are resolved from left to right. You want firstly A to be inferred (from self), then IsContainer[A, B] to be resolved, so B to be inferred and IsValidTypeForContainer[B] to be resolved and not vice versa firstly A to be inferred, then IsValidTypeForContainer[B] to be resolved, and at this step B is not restricted, there is no connection between A and B, so possibly B can be not inferred or inferred to be Nothing, so IsContainer[A, B] is not resolved. I'm a little simplifying (not every time when you swap implicits you break resolution) but general strategy of implicit resolution and type inference is as I described.
Here is the code:
trait Service[T<: HList] {
def doStuff(): Unit
}
class A
class B
class C
class ServiceAB extends Service[A :: B :: HNil] {
override def doStuff(): Unit = println("handling a b")
}
class ServiceC extends Service[C :: HNil] {
override def doStuff(): Unit = println("handling c")
}
implicit val serviceAB = new ServiceAB
implicit val serviceC = new ServiceC
def operate[T, W <: HList](x: T)(implicit service: Service[W]) = {
service.doStuff()
}
operate(new C)
I just wonder is it possible or what should I code in the type level to inject implicit serviceC when operate(new C) is executed, since class C is the element of HList of type class of ServiceC ?
Many thanks in advance
I realy don't know why you need this :)
So your code works, but if you pass type parameter explicitly:
operate[C, C :: HNil](new C)
If you want same, but implicitly, you can define your class type:
trait Service[L <: HList, U] { def doStuff(): Unit }
trait lowPriority {
implicit def otherwise[L <: HList, U] =
new Service[L, U] {
def doStuff(): Unit = println("handling otherwise")
}
}
object Service extends lowPriority {
implicit def ab[L <: HList, U]
(implicit e: L =:= (A :: B :: HNil),
s: Selector[L, U]) =
new Service[L, U] {
def doStuff(): Unit = println("handling a b")
}
implicit def c[L <: HList, U]
(implicit e: L =:= (C :: HNil),
s: Selector[L, U]) =
new Service[L, U] {
def doStuff(): Unit = println("handling c")
}
}
}
def operate[T, W <: HList](x: T)(implicit service: Service[W, T]) = {
service.doStuff()
}
So this works as expected:
operate(new C) //> handling c
operate(new A) //> handling a b
operate(new B) //> handling a b
It is possible to make it more general (so it will check is type you need is in a HList, ohterwise if it doesn't) (using Curry-Howard isomorphism, great article with explanations by Miles Sabin: http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/):
import reflect.runtime.universe._
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[¬[T] with ¬[U]]
type ¬¬[A] = ¬[¬[A]]
class A
class B
class C
class D //> additional class for example
trait Service[L <: HList, U] { def doStuff(): Unit }
trait lowPriority {
implicit def otherwise[L <: HList, U] =
new Service[L, U] {
def doStuff(): Unit = println("handling otherwise")
}
}
object Service extends lowPriority {
implicit def ab[L <: HList, U]
(implicit e: (¬¬[U] <:< (A ∨ B)),
s: Selector[L, TypeTag[U]]) =
new Service[L, U] {
def doStuff(): Unit = println("handling a b")
}
implicit def c[L <: HList, U](implicit e: U =:= C, s: Selector[L, TypeTag[U]]) =
new Service[L, U] {
def doStuff(): Unit = println("handling c")
}
}
}
def operateBi[T, W <: HList](x: T, w: W)(implicit service: Service[W, T]) = {
service.doStuff()
}
Defining HLists of types:
val hl1 = implicitly[TypeTag[A]] :: implicitly[TypeTag[B]] :: HNil
val hl2 = implicitly[TypeTag[C]] :: HNil
operateBi(new C, hl1)
operateBi(new A, hl2)
operateBi(new B, hl1)
operateBi(new D, hl1)
Works as expected.
I want to achieve this:
implicit def aConvertable(obj: A): B = new B(obj)
implicit def aConvertableWithoutB[T <: A without B](obj: T): C = new C(obj)
What is the correct way to say: "A without B"?
I'm assuming that B is a subtype of A. The trick is to introduce an implicit "AllowedConversion" trait that works for all A's, and then two implicit instances for B that have the same type, so that they're ambiguous for B:
object Foo {
trait A
class B(a: A) extends A
class C(a: A)
class AllowedConversion[T]
implicit def allowedForAs[T<:A] = new AllowedConversion[T]
implicit def contradictoryForBs[T <: B] = new AllowedConversion[T]
implicit def contradictoryForBs2[T <: B] = new AllowedConversion[T]
implicit def aConvertable(obj: A): B = new B(obj)
implicit def aConvertableWithoutB[T <: A](obj: T)(implicit allowed: AllowedConversion[T]): C = new C(obj)
// error!
// new B(new A{}):C
// ok!
new A{}:C
}
You can do this to get implicit conversions to chain:
package language
object chainedImplicits {
implicit def chainImplicits[A, B, C](a: A)(implicit conv1: A => B, conv2: B => C): C = conv2(conv1(a))
}
but this is obviously not safe.
I can't see anything wrong with this version, though:
package language
package chainedImplicits {
final class Chained[A, B] private[chainedImplicits] (val f: A => B)
trait Low { this: `package`.type =>
implicit def startChaining(implicit conv: A => B): Chained[A, B] = new Chained[A, B](conv)
implicit def chainImplicits[A, B, C](implicit conv1: Chained[A, B], conv2: B => C): Chained[B, C] = new Chained(conv1.f andThen conv2)
}
}
package object chainedImplicits extends Low {
implicit def endChain[A, B](a: A)(implicit conv: Chained[A, B]): B = conv.f(a)
}
Is there a catch here?
First, I can't get your first "obviously not safe" example to do anything:
import language.implicitConversions
object Test {
implicit def chain[A, B, C](a: A)(implicit ab: A => B, bc: B => C): C = bc(ab(a))
case class X()
case class Y()
case class Z()
implicit def xy(x: X) = Y()
implicit def yz(y: Y) = Z()
val z: Z = X()
}
...
type mismatch;
found : Test.X
required: Test.Z
val z: Z = X()
^
Next, the obvious "catch" in your second example is that it doesn't compile. Did you actually try it? Regardless, after "fixing" it, it still doesn't do what you want:
import language.implicitConversions
class Convert[A, B](val convert: A => B) extends AnyVal
trait Convert0 {
implicit def convert[A, B](implicit ab: A => B): Convert[A, B] = new Convert(ab)
implicit def convert[A, B, C](implicit ab: Convert[A, B], bc: B => C): Convert[A, C] = new Convert(ab.convert andThen bc)
}
object Convert extends Convert0 {
implicit def convert[A, B](a: A)(implicit convert: Convert[A, B]): B = convert.convert(a)
}
object Test {
case class X()
case class Y()
case class Z()
implicit def xy(x: X) = Y()
implicit def yz(y: Y) = Z()
val z: Z = X()
}
This gives the same type mismatch error. To my knowledge, if you want implicit conversions to chain, you have to be explicit about it:
import language.implicitConversions
object test {
case class X()
case class Y()
case class Z()
// anything convertible to X is convertible to Y
implicit def xy[A <% X](x: A) = Y()
// anything convertible to Y is convertible to Z
implicit def yz[B <% Y](y: B) = Z()
val z: Z = X()
}