ProjectSeptemberInc / freek

Freek, a freaky simple Free to combine your DSL seamlessly
Other
198 stars 19 forks source link

Something wrong with combining multiple interpreter #17

Open pandaforme opened 7 years ago

pandaforme commented 7 years ago

DSL:

sealed trait KVS[A]
object KVS {
 case class Get(key: String) extends KVS[String]
 case class Put(key: String, value: String) extends KVS[Unit]
}

sealed trait FileIO[A]
object FileIO {
 case class Get(name: String) extends FileIO[File]
 case class Delete(name: String) extends FileIO[Unit]
}

Interpreter:

val FileInterpreter = new (FileIO ~> Future) {
  def apply[A](a: FileIO[A]): Future[A] = a match {
    case FileIO.Get(name) =>
      Future {
        new File(name)
      }

    case FileIO.Delete(name) =>
      Future {
        new File(name).delete()
        ()
      }
  }
}

val KVSInterpreter = new (KVS ~> Future) {
  def apply[A](a: KVS[A]): Future[A] = a match {
    case KVS.Get(id) =>
      Future {
        "123"
      }
    case KVS.Put(id, value) =>
      Future {
        ()
      }
  }
}

val interpreter = KVSInterpreter :&: FileInterpreter

It could compile successfully.

When I modify interpreters to


val FileInterpreter = new (FileIO ~> Lambda[A => Future[Xor[Exception, A]]]) {
  override def apply[A](fa: FileIO[A]): Future[Xor[Exception, A]] = fa match {
    case FileIO.Get(name) =>
      Future {
        Xor.right(new File(name))
      }

    case FileIO.Delete(name) =>
      Future {
        new File(name).delete()
        Xor.right(())
      }
  }
}

val KVSInterpreter = new (KVS ~> Lambda[A => Future[Xor[Exception, A]]]) {
 def apply[A](a: KVS[A]): Future[Xor[Exception, A]] = a match {
  case KVS.Get(id) =>
   Future {
     Xor.right("123")
   }
  case KVS.Put(id, value) =>
   Future {
     Xor.right(())
   }
 }
}

I can't compile it ..., and it tells me

value :&: is not a member of cats.~>[A$A48.this.FileIO,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
lazy val interpreter: Interpreter[({type λ[γ] = In2[KVS, FileIO, γ]})#λ, ({type Λ$[A] = Future[Xor[Exception, A]]})#Λ$] = KVSInterpreter :&: FileInterpreter;}

Does someone know what's going on?

mandubian commented 7 years ago

If you try val interpreter = toInterpreter(KVSInterpreter) :&: FileInterpreter, what is the error?

Sorry for not answering, github never notified me on those messages :)

pandaforme commented 7 years ago

According to your suggestion, I got a lot of errors:

Error:(53, 20) no type parameters for method toInterpreter: (nat: cats.~>[F,R])freek.Interpreter[[β]freek.In1[F,β],R] exist so that it can be applied to arguments (cats.~>[A$A47.this.KVS,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]])
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : cats.~>[A$A47.this.KVS,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
    (which expands to)  cats.arrow.FunctionK[A$A47.this.KVS,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
 required: cats.~>[?F,?R]
    (which expands to)  cats.arrow.FunctionK[?F,?R]
val interpreter = toInterpreter(KVSInterpreter) :&: FileInterpreter;}
                  ^
Error:(53, 34) type mismatch;
 found   : cats.~>[A$A47.this.KVS,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
    (which expands to)  cats.arrow.FunctionK[A$A47.this.KVS,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
 required: cats.~>[F,R]
    (which expands to)  cats.arrow.FunctionK[F,R]
val interpreter = toInterpreter(KVSInterpreter) :&: FileInterpreter;}
                                ^
Error:(53, 50) value :&: is not a member of cats.~>[A$A47.this.FileIO,[A]scala.concurrent.Future[cats.data.Xor[Exception,A]]]
val interpreter = toInterpreter(KVSInterpreter) :&: FileInterpreter;}
                                                ^

But I change FileInterpreter implementation:

type QQ[A] = Future[Xor[Exception, A]]
val FileInterpreter = new (FileIO ~> QQ) {
  override def apply[A](fa: FileIO[A]): QQ[A] = fa match {
    case FileIO.Get(name) =>
      Future {
        Xor.right(new File(name))
      }

    case FileIO.Delete(name) =>
      Future {
        new File(name).delete()
        Xor.right(())
      }
  }
}

val KVSInterpreter = new (KVS ~> Lambda[A => Future[Xor[Exception, A]]]) {
  def apply[A](a: KVS[A]): Future[Xor[Exception, A]] = a match {
    case KVS.Get(id) =>
      Future {
        Xor.right("123")
      }
    case KVS.Put(id, value) =>
      Future {
        Xor.right(())
      }
  }
}

val interpreter = KVSInterpreter :&: FileInterpreter

It works, no more errors !!! :)

Is it a Freek or kind projector bug?

mandubian commented 7 years ago

Actually it's not really a bug, more a limitation of Freek & SI2712 patch...

implicit def toInterpreter[F[_], R[_]](nat: F ~> R): Interpreter[In1[F, ?], R] = Interpreter(nat)

That expects a F ~> R and R should be a F[_] and in FileIO ~> Lambda[A => Future[Xor[Exception, A]]], SI2712 is not able to unify Lambda[A => Future[Xor[Exception, A]] with F[_]

Shame, isn't it? :) This is a case we had remarked with Miles Sabin when he was finishing his SI2712 patch and we couldn't solve it without introducing weird undecidable edge-cases... So it was decided to keep less powerful but more robust... I'll re-think about it but I fear that there is no really good solution in this case other than using a type alias as you've done...

pandaforme commented 7 years ago

It seems the limitation of SI2712, although I don't understand the implementation of SI2712 patch very clearly. I'll look at the implementation :)

Thank you for your thorough explanation.