I'm trying to start using free monads in my project and I'm struggling to make it elegant.
Let's say I have two contexts (in reality I have more) - Receipt and User - both have operations on a database and I would like to keep their interpreters separate and compose them at the last moment.
For this I need to define different operations for each and combine them into one type using Coproduct.
This is what I have after days of googling and reading:
// Receipts
sealed trait ReceiptOp[A]
case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]]
class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) {
def getReceipt(id: String): Free[F, Either[Error, ReceiptEntity]] = Free.inject[ReceiptOp, F](GetReceipt(id))
}
object ReceiptOps {
implicit def receiptOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F]
}
// Users
sealed trait UserOp[A]
case class GetUser(id: String) extends UserOp[Either[Error, User]]
class UserOps[F[_]](implicit I: Inject[UserOp, F]) {
def getUser(id: String): Free[F, Either[Error, User]] = Free.inject[UserOp, F](GetUser(id))
}
object UserOps {
implicit def userOps[F[_]](implicit I: Inject[UserOp, F]): UserOps[F] = new UserOps[F]
}
When I want to write a program I can do this:
type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A]
type Program[A] = Free[ReceiptsApp, A]
def program(implicit RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[String] = {
import RO._, UO._
for {
// would like to have 'User' type here
user <- getUser("user_id")
receipt <- getReceipt("test " + user.isLeft) // user type is `Either[Error, User]`
} yield "some result"
}
The problem here is that for example user in for comprehension is of type Either[Error, User] which is understandable looking at the getUser signature.
What I would like to have is User type or stopped computation.
I know I need to somehow use an EitherT monad transformer or FreeT, but after hours of trying I don't know how to combine the types to make it work.
Can someone help?
Please let me know if more details are needed.
I've also created a minimal sbt project here, so anyone willing to help could run it: https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala
Cheers,
Leonti
After long battle with Cats:
// Receipts
sealed trait ReceiptOp[A]
case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]]
class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) {
private[this] def liftFE[A, B](f: ReceiptOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))
def getReceipt(id: String): EitherT[Free[F, ?], Error, ReceiptEntity] = liftFE(GetReceipt(id))
}
object ReceiptOps {
implicit def receiptOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F]
}
// Users
sealed trait UserOp[A]
case class GetUser(id: String) extends UserOp[Either[Error, User]]
class UserOps[F[_]](implicit I: Inject[UserOp, F]) {
private[this] def liftFE[A, B](f: UserOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))
def getUser(id: String): EitherT[Free[F, ?], Error, User] = Free.inject[UserOp, F](GetUser(id))
}
object UserOps {
implicit def userOps[F[_]](implicit I: Inject[UserOp, F]): UserOps[F] = new UserOps[F]
}
Then you write program as you want:
type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A]
type Program[A] = Free[ReceiptsApp, A]
def program(implicit RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[Either[Error, String]] = {
import RO._, UO._
(for {
// would like to have 'User' type here
user <- getUser("user_id")
receipt <- getReceipt("test " + user.isLeft) // user type is `User` now
} yield "some result").value // you have to get Free value from EitherT, or change return signature of program
}
A little explanation. Without Coproduct transformer, functions would return:
Free[F, A]
Once we add Coproduct of operations into picture, return type becomes:
Free[F[_], A]
, which works fine until we try to transform it to EitherT. If there would not be Coproduct, EitherT would look like:
EitherT[F, ERROR, A]
Where F, is Free[F, A]. But if F is Coproduct and Injection is used, intuition leads to:
EitherT[F[_], ERROR, A]
Which is wrong obviously, here we have to extract type of Coproduct. Which would lead us with kind-projector plugin to:
EitherT[Free[F, ?], ERROR, A]
Or with lambda expression:
EitherT[({type L[a] = Free[F, a]})#L, ERROR, A]
Now it is correct type to which we can lift with:
EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f)))
If needed, we can simplify return type to:
type ResultEitherT[F[_], A] = EitherT[Free[F, ?], Error, A]
And use it in functions like:
def getReceipt(id: String): ResultEitherT[F[_], ReceiptEntity] = liftFE(GetReceipt(id))
The Freek library implements all the machinery required to solve your problem:
type ReceiptsApp = ReceiptOp :|: UserOp :|: NilDSL
val PRG = DSL.Make[PRG]
def program: Program[String] =
for {
user <- getUser("user_id").freek[PRG]
receipt <- getReceipt("test " + user.isLeft).freek[PRG]
} yield "some result"
As you rediscovered yourself, Free monads and the likes are not extensible without going through the complexity of coproducts. If you are looking for an elegant solution, I would suggest you have a look at Tagless Final Interpreters.
Related
I'm trying to use an Either as the result of an algebra using Free monad, like the following code
///// Command
sealed trait Command[A]
type Result[A] = Either[Exception, A]
object Command {
case class Tell(line: String) extends Command[Result[Unit]]
case class Ask(line: String) extends Command[Result[String]]
}
type App[A] = EitherK[Command, Log, A]
def program(implicit L: LogOps[App],
C: CommandOps[App]): Free[App, Unit] =
for {
_ <- L.show("start <ask>")
name <- C.ask("What's your name?")
_ <- L.show("start <tell>")
_ <- C.tell(s"Hi <$name>, nice to meet you!")
_ <- L.show("done.")
} yield ()
...
the problem is that the name is an Either, so I got the following output
L--- start <ask>
What's your name?
George
L--- start <tell>
Hi <Right(George)>, nice to meet you!
L--- done.
Any idea?
Thanks
Solution found in this link
Scala Free Monads with Coproduct and monad transformer
I had to define a function like following
def liftFE[F[_], T[_], A, B](f: T[Either[A, B]])(implicit I: InjectK[T, F]): EitherT[Free[F, *], A, B] = EitherT[Free[F, *], A, B](Free.liftF(I.inj(f)))
and use it instead of Free.inject like the follow
class CommandOps[F[_]](implicit I: InjectK[Command, F]) {
import Command.{Ask, Tell}
def tell(line: String): EitherT[Free[F, *], Exception, Unit] =
liftFE(Tell(line))
def ask(line: String): EitherT[Free[F, *], Exception, String] =
liftFE(Ask(line))
}
and change also 'program' result type
def program(implicit L: LogOps[App],
C: CommandOps[App]): EitherT[Free[App, *], Exception, Either[Exception, Unit]] =
Suppose I have some function that should take a sequence of Ints or a sequence of Strings.
My attempt:
object Example extends App {
import scala.util.Random
val rand: Random.type = scala.util.Random
// raw data
val x = Seq(1, 2, 3, 4, 5).map(e => e + rand.nextDouble())
val y = Seq("chc", "asas")
def f1[T <: AnyVal](seq: Seq[T]) = {
println(seq(0))
}
// this works fine as expected
f1(x)
// how can i combine
f1(y)
}
How can I add this to also work with strings?
If I change the method signature to:
def f1[T <: AnyVal:String](seq: Seq[T])
But this won't work.
Is there a way to impose my required constraint on the types elegantly?
Note the difference between an upper bound
A <: C
and a context bound
A : C
so type parameter clause [T <: AnyVal : String] does not make much sense. Also types such as String are rarely (or never) used as context bounds.
Here is a typeclass approach
trait EitherStringOrAnyVal[T]
object EitherStringOrAnyVal {
implicit val str: EitherStringOrAnyVal[String] = new EitherStringOrAnyVal[String] {}
implicit def aval[T <: AnyVal]: EitherStringOrAnyVal[T] = new EitherStringOrAnyVal[T] {}
}
def f1[T: EitherStringOrAnyVal](seq: Seq[T]): Unit = {
println(seq(0))
}
f1(Seq(1)) // ok
f1(Seq("a")) // ok
f1(Seq(Seq(1))) // nok
or generelized type constraints approach
object Foo {
private def impl[T](seq: Seq[T]): Unit = {
println(seq(0))
}
def f1[T](seq: Seq[T])(implicit ev: T =:= String): Unit = impl(seq)
def f1[T <: AnyVal](seq: Seq[T]): Unit = impl(seq)
}
import Foo._
f1(Seq(1)) // ok
f1(Seq("a")) // ok
f1(Seq(Seq(1))) // nok
I feel like you should write a separate function for both, but there are other ways to do it:
In Dotty, you can just use union types, which is what I'd recommend here:
Edit: As per Alexey Romanov’s suggestion, replaced Seq[AnyVal | String] with Seq[AnyVal | String], which was wrong,
def foo(seq: Seq[AnyVal] | Seq[String]): Unit = {
println(seq)
}
Scastie
If you're not using Dotty, you can still do this in Scala 2 with a couple reusable implicit defs
class Or[A, B]
implicit def orA[A, B](implicit ev: A): Or[A, B] = new Or
implicit def orB[A, B](implicit ev: B): Or[A, B] = new Or
def foo[T](seq: Seq[T])(implicit ev: Or[T <:< AnyVal, T =:= String]): Unit =
println(seq)
foo(Seq("baz", "waldo"))
foo(Seq(23, 34))
foo(Seq(List(), List())) //This last one fails
Scastie
You can also do it with a typeclass, as Luis Miguel Mejía Suárez suggested, but I wouldn't recommend it for such a trivial task since you still have to define 2 functions for each type, along with a trait, 2 implicit objects, and one function that can use instances of that trait. You probably don't want this pattern to handle just 2 different types.
sealed trait DoSomethingToIntOrString[T] {
def doSomething(t: Seq[T]): Unit
}
implicit object DoSomethingToAnyVal extends DoSomethingToAnyValOrString[AnyVal] {
def doSomething(is: Seq[AnyVal]): Unit = println(is)
}
implicit object DoSomethingToString extends DoSomethingToIntOrString[String] {
def doSomething(ss: Seq[String]): Unit = println(ss)
}
def foo[T](t: Seq[T])(implicit dsis: DoSomethingToIntOrString[T]): Unit = {
dsis.doSomething(t)
}
foo(Seq("foo", "bar"))
foo(Seq(1, 2))
In Scastie
def f1( seq: Seq[Any] ): Unit =
println( seq( 0 ) )
This works, because the least common supertype of Int and String (the 'lowest' type that is a supertype of both) would be Any, since Int is a subtype of AnyVal (i.e. 'the primitives) and String is a subtype of AnyRef (i.e. `the heap objects). f1 does not depends in any way on the contents of the list and is not polymorphic in its return type (Unit), so we don't need a type parameter at all.
I'm using cats FreeMonad. Here's a simplified version of the algebra:
sealed trait Op[A]
object Op {
final case class Get[T](name: String) extends Op[T]
type OpF[A] = Free[Op, A]
def get[T](name: String): OpF[T] = liftF[Op, T](Get[T](name))
}
One of the interpreters will be a wrapper around a third-party library, called Client here which its get method's signature is similar to:
class Client {
def get[O <: Resource](name: String)
(implicit f: Format[O], d: Definition[O]): Future[O] = ???
}
My question is how can I encode that requirement in my implementation?
class FutureOp extends (Op ~> Future) {
val client = new Client()
def apply[A](fa: Op[A]): Future[A] =
fa match {
case Get(name: String) =>
client.get[A](name)
}
}
I tried things like introducing bounds to my apply (like apply[A <: Resource : Format : Definition]) which didn't work.
I understand that FunctionK is to transform values of first-order-kinded types, but is there anyway in which I can encode the requirements of the type parameter?
I intend to use it like:
def run[F[_]: Monad, A](intp: Op ~> F, op: OpF[A]): F[A] = op.foldMap(intp)
val p: Op.OpF[Foo] = Op.get[Foo]("foo")
val i = new FutureOp()
run(i, d)
(My original answer contained the same idea, but apparently it did not provide enough implementation details. This time, I wrote a more detailed step-by-step guide with a discussion of each intermediate step. Every section contains a separate compilable code snippet.)
TL;DR
Implicits are required for each type T that occurs in get[T], therefore they must be inserted and stored when the DSL-program is constructed, not when it is executed. This solves the problem with the implicits.
There is a general strategy for gluing a natural transformation ~> from several restricted natural transformations trait RNT[R, F[_ <: R], G[_]]{ def apply[A <: R](x: F[A]): G[A] } using pattern matching. This solves the problem with the A <: Resource type bound. Details below.
In your question, you have two separate problems:
implicit Format and Definition
<: Resource-type bound
I want to treat each of these two problems in isolation, and provide a reusable solution strategy for both. I will then apply both strategies to your problem.
My answer below is structured as follows:
First, I will summarize your question as I understand it.
Then I will explain what to do with the implicits, ignoring the type bound.
Then I will deal with the type bound, this time ignoring the implicits.
Finally, I apply both strategies to your particular problem.
Henceforth, I assume that you have scalaVersion 2.12.4, the dependencies
libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.1"
libraryDependencies += "org.typelevel" %% "cats-free" % "1.0.1"
and that you insert
import scala.language.higherKinds
where appropriate.
Note that the solution strategies are not specific to this particular scala version or the cats library.
The setup
The goal of this section is to make sure that I'm solving the right problem, and also to provide very simple mock-up definitions
of Resource, Format, Client etc., so that this answer is self-contained
and compilable.
I assume that you want to build a little domain specific language using the Free monad.
Ideally, you would like to have a DSL that looks approximately like this (I've used the names DslOp for the operations and Dsl for the generated free monad):
import cats.free.Free
import cats.free.Free.liftF
sealed trait DslOp[A]
case class Get[A](name: String) extends DslOp[A]
type Dsl[A] = Free[DslOp, A]
def get[A](name: String): Dsl[A] = liftF[DslOp, A](Get[A](name))
It defines a single command get that can get objects of type A given a string
name.
Later, you want to interpret this DSL using a get method provided by some Client
that you cannot modify:
import scala.concurrent.Future
trait Resource
trait Format[A <: Resource]
trait Definition[A <: Resource]
object Client {
def get[A <: Resource](name: String)
(implicit f: Format[A], d: Definition[A]): Future[A] = ???
}
Your problem is that the get method of the Client has a type bound, and that
it requires additional implicits.
Dealing with implicits when defining interpreter for the Free monad
Let's first pretend that the get-method in client requires implicits, but
ignore the type bound for now:
import scala.concurrent.Future
trait Format[A]
trait Definition[A]
object Client {
def get[A](name: String)(implicit f: Format[A], d: Definition[A])
: Future[A] = ???
}
Before we write down the solution, let's briefly discuss why you cannot supply all
the necessary implicits when you are calling the apply method in ~>.
When passed to foldMap, the apply of FunctionK is supposed
to be able to cope with arbitrarily long programs of type Dsl[X] to produce Future[X].
Arbitrarily long programs of type Dsl[X] can contain an unlimited number of
get[T1], ..., get[Tn] commands for different types T1, ..., Tn.
For each of those T1, ..., Tn, you have to get a Format[T_i] and Definition[T_i] somewhere.
These implicit arguments must be supplied by the compiler.
When you interpret the entire program of type Dsl[X], only the type X but not the types T1, ..., Tn are available,
so the compiler cannot insert all the necessary Definitions and Formats at the call site.
Therefore, all the Definitions and Formats must be supplied as implicit parameters to get[T_i]
when you are constructing the Dsl-program, not when you are interpreting it.
The solution is to add Format[A] and Definition[A] as members to the Get[A] case class,
and make the definition of get[A] with lift[DslOp, A] accept these two additional implicit
parameters:
import cats.free.Free
import cats.free.Free.liftF
import cats.~>
sealed trait DslOp[A]
case class Get[A](name: String, f: Format[A], d: Definition[A])
extends DslOp[A]
type Dsl[A] = Free[DslOp, A]
def get[A](name: String)(implicit f: Format[A], d: Definition[A])
: Dsl[A] = liftF[DslOp, A](Get[A](name, f, d))
Now, we can define the first approximation of the ~>-interpreter, which at least
can cope with the implicits:
val clientInterpreter_1: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case Get(name, f, d) => Client.get(name)(f, d)
}
}
Type bounds in case classes defining the DSL-operations
Now, let's deal with the type bound in isolation. Suppose that your Client
doesn't need any implicits, but imposes an additional bound on A:
import scala.concurrent.Future
trait Resource
object Client {
def get[A <: Resource](name: String): Future[A] = ???
}
If you tried to write down the clientInterpreter in the same way as in the
previous example, you would notice that the type A is too general, and that
you therefore cannot work with the contents of Get[A] in Client.get.
Instead, you have to find a scope where the additional type information A <: Resource
is not lost. One way to achieve it is to define an accept method on Get itself.
Instead of a completely general natural transformation ~>, this accept method will
be able to work with natural transformations with restricted domain.
Here is a trait to model that:
trait RestrictedNat[R, F[_ <: R], G[_]] {
def apply[A <: R](fa: F[A]): G[A]
}
It looks almost like ~>, but with an additional A <: R restriction. Now we
can define accept in Get:
import cats.free.Free
import cats.free.Free.liftF
import cats.~>
sealed trait DslOp[A]
case class Get[A <: Resource](name: String) extends DslOp[A] {
def accept[G[_]](f: RestrictedNat[Resource, Get, G]): G[A] = f(this)
}
type Dsl[A] = Free[DslOp, A]
def get[A <: Resource](name: String): Dsl[A] =
liftF[DslOp, A](Get[A](name))
and write down the second approximation of our interpreter, without any
nasty type-casts:
val clientInterpreter_2: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case g # Get(name) => {
val f = new RestrictedNat[Resource, Get, Future] {
def apply[X <: Resource](g: Get[X]): Future[X] = Client.get(g.name)
}
g.accept(f)
}
}
}
This idea can be generalized to an arbitrary number of type constructors Get_1, ...,
Get_N, with type restrictions R1, ..., RN. The general idea corresponds to
the construction of a piecewise defined natural transformation from smaller
pieces that work only on certain subtypes.
Applying both solution strategies to your problem
Now we can combine the two general strategies into one solution for
your concrete problem:
import scala.concurrent.Future
import cats.free.Free
import cats.free.Free.liftF
import cats.~>
// Client-definition with both obstacles: implicits + type bound
trait Resource
trait Format[A <: Resource]
trait Definition[A <: Resource]
object Client {
def get[A <: Resource](name: String)
(implicit fmt: Format[A], dfn: Definition[A])
: Future[A] = ???
}
// Solution:
trait RestrictedNat[R, F[_ <: R], G[_]] {
def apply[A <: R](fa: F[A]): G[A]
}
sealed trait DslOp[A]
case class Get[A <: Resource](
name: String,
fmt: Format[A],
dfn: Definition[A]
) extends DslOp[A] {
def accept[G[_]](f: RestrictedNat[Resource, Get, G]): G[A] = f(this)
}
type Dsl[A] = Free[DslOp, A]
def get[A <: Resource]
(name: String)
(implicit fmt: Format[A], dfn: Definition[A])
: Dsl[A] = liftF[DslOp, A](Get[A](name, fmt, dfn))
val clientInterpreter_3: (DslOp ~> Future) = new (DslOp ~> Future) {
def apply[A](op: DslOp[A]): Future[A] = op match {
case g: Get[A] => {
val f = new RestrictedNat[Resource, Get, Future] {
def apply[X <: Resource](g: Get[X]): Future[X] =
Client.get(g.name)(g.fmt, g.dfn)
}
g.accept(f)
}
}
}
Now, the clientInterpreter_3 can cope with both problems: the type-bound-problem is dealt with
by defining a RestrictedNat for each case class that imposes an upper bound on its type arguments,
and the implicits-problem is solved by adding an implicit parameter list to DSL's get-method.
I think I've found a way to solve your problem by combining a ReaderT monad transformer with intersection types:
import scala.concurrent.Future
import cats.~>
import cats.data.ReaderT
import cats.free.Free
object FreeMonads {
sealed trait Op[A]
object Op {
final case class Get[T](name: String) extends Op[T]
type OpF[A] = Free[Op, A]
def get[T](name: String): OpF[T] = Free.liftF[Op, T](Get[T](name))
}
trait Resource
trait Format[A]
trait Definition[A]
trait Client {
def get[O <: Resource](name: String)
(implicit f: Format[O], d: Definition[O]): Future[O]
}
type Result[A] = ReaderT[
Future,
(Format[A with Resource], Definition[A with Resource]),
A,
]
class FutureOp(client: Client) extends (Op ~> Result) {
def apply[A](fa: Op[A]): Result[A] =
fa match {
case Op.Get(name: String) =>
ReaderT {
case (format, definition) =>
// The `Future[A]` type ascription makes Intellij IDEA's type
// checker accept the code.
client.get(name)(format, definition): Future[A]
}
}
}
}
The basic idea behind it is that you produce a Reader from your Op and that Reader receives the values that you can use for the implicit params. This solves the problem of type O having instances for Format and Definition.
The other problem is for O be a subtype of Resource. To solve this, we're just saying that the Format and Definition instances are not just instances of any A, but any A that also happens to be a Resource.
Let me know if you bump into problems when using FutureOp.
I have posted this question in scala-user forum,
https://groups.google.com/forum/#!topic/scala-user/xlr7KmlWdWI
and I received an answer which I am happy with. However, at the same time I want to make sure if that is the only conclusion. Thanks
My question is, say, I have,
trait Combinable[A] {
def join[B](l: List[A]): B
}
When I implement this trait with A as String and B as Int, for example,
class CombineString extends Combinable[String] {
override def join[Int](strings: List[String]) = string.size
}
Obviously, Int, next to join method, is not the Scala integer class and
compiling this code will fail. Alternatively, I could rewrite my trait as
trait Combinable[A, B] {
def join(l: List[A]): B
}
or
trait Combinable[A] {
def join[B](l: List[A])(f: List[A] => B): B
}
My question is, how can I implement the trait as defined in the first example as it is? If the first example has no practical use because of the way it is defined, why does the compiler not complaints? Thanks again.
You can not expect for compiler to understand what type combinations make sense for you, but you can specify this sense as relation between types in term of multiparameter implicits.
Result is basically combination of two of your rejected approaches, but with desired syntax.
First of two rejected forms became implicit type:
trait Combine[A, B] {
def apply(l: List[A]): B
}
Next you can define appropriate type combinations and their meaning
implicit object CombineStrings extends Combine[String, String] {
def apply(l: List[String]) = l.mkString
}
implicit object CombineInts extends Combine[Int, Int] {
def apply(l: List[Int]) = l.sum
}
implicit object CombinableIntAsString extends Combine[Int, String] {
def apply(l: List[Int]) = l.mkString(",")
}
Finally we modify second rejected form that hides f argument for implicit resulution:
trait Combinable[A] {
def join[B](l: List[A])(implicit combine: Combine[A, B]): B = combine(l)
}
Now you can define
val a = new Combinable[String] {}
val b = new Combinable[Int] {}
And check that
a.join[String](List("a", "b", "c"))
b.join[Int](List(1, 2, 3))
b.join[String](List(1, 2, 3))
runs nice while
a.join[Int](List("a", "b", "c"))
makes compiler cry until you could provide evidence of practical use of relation between String and Int in form of implicit value
My question is, how can I implement the trait as defined in the first example as it is?
class CombineString extends Combinable[String] {
override def join[B](strings: List[String]) = null.asInstanceOf[B]
// or, for that matter, anything and then .asInstanceOf[B]
}
How could the compiler know that's not what you want?
I have a set of classes that looks something like this, which are (annoyingly) not in a single inheritance hierarchy:
trait Mxy[A,B] { def apply(a: A): B }
trait Mzz[ C ] { def apply(c: C): C }
trait Mix[ B] { def apply(i: Int): B }
trait Mxi[A ] { def apply(a: A): Int }
trait Mii { def apply(i: Int): Int }
The challenge is to map Function1 into these classes using converters (so you call .m to switch over); direct implicit conversion makes it too hard to figure out what the code is really doing.
As long as the most specific M is always okay, it's doable. You can write implicit conversions that look something like
abstract class MxyMaker[A, B] { def m: Mxy[A, B] }
abstract class MiiMaker { def m: Mii }
trait Priority2 {
implicit def f2mxy[A, B](f: Function1[A, B]) =
new MxyMaker[A, B] { def m = new Mxy[A, B] { def apply(a: A) = f(a) } }
}
// Bunch of missing priorities here in the full case
object Convert extends Priority2 {
implicit def f2mii[A](f: Function1[A, Int])(implicit evA: Int =:= A) =
new MiiMaker { def m = new Mii{
def apply(i: Int) = (f.asInstanceOf[Function1[Int,Int]]).apply(i)
} }
}
Now whenever you have an Int => Int you get a Mii, and for anything else you get a Mxy. (If you don't ask for evA then ((a: Any) => 5).m comes up as a Mii because of the contravariance of Function1 in its input argument parameter.)
So far so good. But what if you are in a context where you need not a Mii but a Mxy?
def muse[A, B](m: Mxy[A,B]) = ???
Now inferring the most specific type isn't what you want. But, alas,
scala> muse( ((i: Int) => 5).m )
<console>:18: error: type mismatch;
found : Mii
required: Mxy[?,?]
muse( ((i: Int) => 5).m )
^
Now what? If Mxy were a superclass of Mii it would be fine, but it's not and I can't change it. I could parameterize m with an output type and have a set of typeclass-like things that do the correct conversion depending on output type and then ask the user to type .m[Mxy] manually. But I want to skip the manual part and somehow pick up the type context from the call to muse without messing up things like val mippy = ((i: Int) = 5).m.
The astute may be able to think of a case like this in the wild, but it's easier to work with this "minimization" to get the principles right.