In scalaz when we define a module, we additionally define implicit, helper functions. Here is an example of definition and how it could be used by a client:
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def fmap[F[_], A,B](as:F[A])(f:A=>B)
(implicit ff:Functor[F]):F[B] =
ff.map(as)(f)
implicit val listFunctor = new Functor[List] {
def map[A,B](as: List[A])(f: A => B): List[B] = as map f
}
}
...
import com.savdev.NewLibrary._
val r = fmap(List(1,2))(_.toString)
final class FunctorOps[F[_], A](self: F[A])(implicit ff:Functor[F]){
def qmap[B](f:A=>B):F[B] = ff.map(self)(f)
}
trait ToFunctorOps {
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
new FunctorOps[F,A](v)
}
object NewLibrary extends ToFunctorOps
...
import com.savdev.NewLibrary._
val r2 = List(1, 4) qmap (x=>x.toString)
The code is slightly changed. But the idea is that we define:
An abstraction and its API (algebra)
Define helper generic functions that use implicits and implicits themselves
Enrich existing types to be able to use our new abstraction. Implicit convertion is used for that. In scalaz we define a final class for a wrapper and implicit converters in traits
All above, the motivation of it and how it can be used by a client is clear. But in scalaz to each such module definition, there is also a related *Syntax class. I cannot understand the purpose of it. Can you please exlain, why it is needed and HOW it can be used in a client code.
In Scalaz it is defined as:
trait FunctorSyntax[F[_]] {
implicit def ToFunctorOps[A](v: F[A]): FunctorOps[F, A] =
new FunctorOps[F, A](v)(FunctorSyntax.this.F)
def F: Functor[F]
}
UPDATED:
Guys, it seems I am not clear enough, or a topic is more complicated for all of us.
What I need is to understand the difference between two traits:
trait ToFunctorOps {
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
new FunctorOps[F,A](v)
}
vs.
trait FunctorSyntax[F[_]] {
implicit def ToFunctorOps[A](v: F[A]): FunctorOps[F, A] =
new FunctorOps[F, A](v)(FunctorSyntax.this.F)
def F: Functor[F]
}
Both traits define a generic method that creates FunctorOps, both have the same visibility rules.
The first ToFunctorOps trait, it is not generic itself, it defines only generic method with [F[_],A]. As a result, I can compose a lot of such traits into one object and import all of them at once. I gave an example how such traits could be used by a client:
object NewLibrary extends ToFunctorOps
...
import com.savdev.NewLibrary._
val r2 = List(1, 4) qmap (x=>x.toString)
This trait already gives clients a possibility to inject methods implicitly. Why do we need FunctorSyntax? This FunctorSyntax trait is a generic itself on [F[_]]. When I extend it, I must provide a type in the definition. Because F[_] now is used in trait definition, a function has less generic parameters, only [A].
I am asking you guys, if you can help and undestand, give me a code example how this FunctorSyntax trait can be used by a client. Exactly this is not clear.
Right now I see tries to explain the other topics, but not the original:
How to create implicit classes, instead of implicit functions.
Difference between final *Ops class and a trait, including their visibility. Here we compare 2 traits with the same visibility.
Explaining in general method injection, how they help. This functionality is provided already with ToFunctorOps.
Guys, again, please show the community USE CASES via CODE of FunctorSyntax. Code itself is always the best documentation.
Best regards
From what I can see in the scalaz codebase, I think that FunctorSyntax is meant as an alternative way of enabling syntax. They define Functor like this (simplified):
trait Functor {
def map[A, B](fa: F[A])(f: A => B): F[B]
val functorSyntax = new FunctorSyntax[F] { def F = Functor.this }
}
This enables the following way of working:
def foo[F[_]](f: F[String])(implicit F: Functor[F]): F[Int] = {
import F.functorSyntax._
f.map(_.length)
}
Compare to how ToFunctorOps adds syntax:
package scalaz.syntax { // simplified version of the scalaz codebase
object functor extends ToFunctorOps
}
import scalaz.syntax.functor._
def foo[F[_]: Functor](f: F[String]): F[Int] = f.map(_.length)
Here's a use case where you would use functorSyntax:
import org.scalatest.{FreeSpec, Matchers}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scalaz._
import Scalaz._
import scala.language.postfixOps
class ScalazTest extends FreeSpec with Matchers {
"compose functors" in {
val composedFunctor = Functor[Future] compose Functor[List] compose Functor[Option]
import composedFunctor.functorSyntax._
val actual = Future.successful(List(Some(1), Some(2), None, Some(4))) fmap (x => x * 2)
Await.result(actual, 10 seconds) shouldBe List(Some(2), Some(4), None, Some(8))
}
}
The idea is that you can compose several functor instaces and import the final composed functor's instance in scope and work with it. Notice that fmap is resolved to composedFunctor.functorSyntax in this case and it works on 3 levels of nesting (Future[List[Option[Integer]]]) while still accepting a function that deals with primitive types.
Related
I have the following code:
import cats.effect.IO
import cats.data.State
import cats.data.StateT
import cats.implicits._
import cats.effect.LiftIO
abstract class Example {
object implicits {
implicit def myEffectLiftIO: LiftIO[IOGameplay] =
new LiftIO[IOGameplay] {
override def liftIO[A](ioa: IO[A]): IOGameplay[A] = {
StateT.liftF(ioa)
}
}
}
type Gameplay[A] = State[GameState, A]
type IOGameplay[A] = StateT[IO, GameState, A]
type EitherDirection[A] = Either[Throwable, A]
type Map = Array[Array[FieldType]]
sealed trait FieldType
case class GameState(map: Map, block: Block)
case class Block(f1: Field, f2: Field)
case class Field()
import implicits._
val L = implicitly[LiftIO[IOGameplay]]
sealed trait GameResult
sealed trait Direction
trait IOMonad {
def println(msg: String): IO[Unit]
def readln(): IO[String]
}
def play(io: IOMonad): StateT[IO, GameState, GameResult] = {
val L = implicitly[LiftIO[IOGameplay]]
for {
// print map to the console
_ <- L.liftIO(io.println("Next move: "))
directionOpt <- L.liftIO(readDirection(io))
direction <- StateT.liftF[IO, GameState, Direction](IO.fromEither(directionOpt))
nextBlock <- IO(nextBlock(direction))
gameResult <- calculate(nextBlock)
} yield {
gameResult
}
}
def readDirection(io: IOMonad): IO[EitherDirection[Direction]]
def nextBlock(direction: Direction): Gameplay[Block]
def calculate(block: Block): Gameplay[GameResult]
}
This is not completely accurate, but I posted the whole block to explain the problem.
Here, I have many transformations on values to produce IO and to transform it to StateT. Is there a more clever way to do this? Maybe I should somehow separate io tasks from the main algorithm, i.e. from this for-comprehension? Or should I do it like this?
One issue is that your Gameplay type is not compatible with IOGameplay, since Gameplay uses the Eval monad. I assume you want this:
type Gameplay[F[_], A] = StateT[F, GameState, A]
type IOGameplay[A] = Gameplay[IO, A]
These methods need to return IOGameplay instances (or you could lift them in your program later):
def nextBlock(direction: Direction): IOGameplay[Block]
def calculate(block: Block): IOGameplay[GameResult]
Then the for-comprehension compiles with minor adjustments:
for {
// print map to the console
_ <- L.liftIO(io.println("Next move: "))
directionOpt <- L.liftIO(readDirection(io))
direction <- StateT.liftF[IO, GameState, Direction](IO.fromEither(directionOpt))
nextBlock <- nextBlock(direction)
gameResult <- calculate(nextBlock)
} yield {
gameResult
}
BTW, what is the intended purpose of the IO effect in this program? User input?
If your goal is to avoid lifting stuff from one monad to the other, then you can make your methods and interfaces polymorphic so that they can work with different monads and not just IO. Here's how to do that for your IOMonad trait:
trait IOMonad[F[_]] {
def println(msg: String): F[Unit]
def readln(): F[String]
}
The idea is to not commit to any specific monad, but to make things work for any monad that provides the features that you need for a specific use case. In the IOMonad example, we need the ability to run synchronous side effects, so we express that by passing a parameter of type Sync[F]:
import cats.effect.Sync
object IOMonad {
def apply[F[_]](implicit F: Sync[F]) = new IOMonad[F] {
def println(msg: String): F[Unit] = F.delay(println(msg))
def readln(): F[String] = F.delay(scala.io.StdIn.readLine())
}
}
The other operations in your program need different capabilities. For instance readDirection needs to do console IO and raise errors of type Throwable. The ability to raise errors is expressed by the MonadError trait, so you get this signature:
def readDirection[F[_]](
io: IOMonad[F])(implicit monErr: MonadError[F, Throwable]
): F[Direction]
It's important to note that we're not passing a Sync[F] here, because we don't need it; the IOMonad[F] object is enough. This is important because it allows you to implement the IOMonad interface in some other way that doesn't necessarily involve side effects, notably for testing.
Another example are nextBlock and calculate. These need manipulate a state of type GameState, and the ability to manipulate state is expressed by the MonadState type:
def nextBlock[F[_]](
direction: Direction)(implicit F: MonadState[F, GameState]
): F[Block]
def calculate[F[_]](
block: Block)(implicit F: MonadState[F, GameState]
): F[GameResult]
MonadState is unfortunately not contained in cats or cats-effect, you need the cats-mtl library for that.
When you put all this together, you end up with a program like this:
import cats.MonadError
import cats.mtl.MonadState
import cats.implicits._
abstract class Example {
type Map = Array[Array[FieldType]]
sealed trait FieldType
case class GameState(map: Map, block: Block)
case class Block(f1: Field, f2: Field)
case class Field()
sealed trait GameResult
sealed trait Direction
trait IOMonad[F[_]] {
def println(msg: String): F[Unit]
def readln(): F[String]
}
def play[F[_]](
io: IOMonad[F])(
implicit merr: MonadError[F, Throwable],
mst: MonadState[F, GameState]
): F[GameResult] = {
for {
// print map to the console
_ <- io.println("Next move: ")
direction <- readDirection(io)
nextBlock <- nextBlock[F](direction)
gameResult <- calculate[F](nextBlock)
} yield gameResult
}
def readDirection[F[_]](
io: IOMonad[F])(
implicit merr: MonadError[F, Throwable]
): F[Direction]
def nextBlock[F[_]](
direction: Direction)(
implicit merr: MonadState[F, GameState]
): F[Block]
def calculate[F[_]](
block: Block)(
implicit mst: MonadState[F, GameState]
): F[GameResult]
}
Note that every single concrete Monad is gone – there is no IO, no State, no Either in the above program, and together with these, any necessity to convert or lift between different monads also went away.
Note however that this style of programming (known as MTL-Style) has its drawbacks.
type inference often doesn't work. In this example you need to pass the F parameter explicitly to nextBlock and calculate, because Scala can't infer it
as mentioned before, cats doesn't include all the necessary type classes like MonadState, so you need additional libraries like cats-mtl
it's somewhat hard to understand for newcomers
This is why parts of the Scala community (notably John de Goes and his ZIO effort) are no longer encouraging MTL-style. Others keep pushing it, because it allows code to be reused with different effect types.
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.
How can I create an instance of a custom type constructor in Cats?
I know that cats already comes with almost all Scala built-in type constructors and I can import them by e.g import cats.data._ import cats.implicits._ import cats.instances._ but I would like to know how can I create my own implicit instance to work with monads operators sintaxs like >>= with my own custom type constructor. Suppose I have MyType[A] that is monadic o at least has flatMap and a pure constructor, I want to do things like:
Monad[MyType].pure(x) >>= { _.fooBar }
I saw at the sources of instances package to look at some examples like Option but it seems to be a lot to do, I don't know if there is an easy way.
regards.
Just provide an implicit instance of Monad for your type, and implement the required methods:
implicit object MyMonad extends Monad[MyType] {
def pure[A](x: A): MyType[A] = ???
def flatMap[A, B](fa: MyType[A])(f: A => MyType[B]): MyType[B] = ???
def tailRecM[A, B](a: A)(f: A => MyType[Either[A, B]]): MyType[B] = ???
}
If you are confident that your Monad is stack-safe, you can skip implementing tailRecM and extend StackSafeMonad[MyType] instead.
I'm reading through and working my way through using type classes and I came across this way of defining type classes from the Shapeless guide:
So here goes the example:
object CsvEncoder {
// "Summoner" method
def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
enc
// "Constructor" method
def instance[A](func: A => List[String]): CsvEncoder[A] =
new CsvEncoder[A] {
def encode(value: A): List[String] =
func(value)
}
// Globally visible type class instances
}
What I do not understand is the need for the apply method? What is it doing in this context above?
Later on, the guide describes how I could create a type class instance:
implicit val booleanEncoder: CsvEncoder[Boolean] =
new CsvEncoder[Boolean] {
def encode(b: Boolean): List[String] =
if(b) List("yes") else List("no")
}
is actually shortened to:
implicit val booleanEncoder: CsvEncoder[Boolean] =
instance(b => if(b) List("yes") else List("no"))
So my question now is, how does this work? What I do not get is the need for the apply method?
EDIT: I came across a blog post that describes the steps in creating type classes as below:
Define typeclass contract trait Foo.
Define a companion object Foo with a helper method apply that acts like implicitly, and a way of defining Foo instances typically from a function.
Define FooOps class that defines unary or binary operators.
Define FooSyntax trait that implicitly provides FooOps from a Foo instance.
So what is the deal with point number 2, 3 and 4?
Most of those practices came from Haskell (basically an intention to mimic Haskell's type-classes is a reason for so much boilerplate), some of it is just for convenience. So,
2) As #Alexey Romanov mentioned, companion object with apply is just for convenience, so instead of implicitly[CsvEncoder[IceCream]] you could write just CsvEncoder[IceCream] (aka CsvEncoder.apply[IceCream]()), which will return you a required type-class instance.
3) FooOps provides convenience methods for DSLs. For instance you could have something like:
trait Semigroup[A] {
...
def append(a: A, b: A)
}
import implicits._ //you should import actual instances for `Semigroup[Int]` etc.
implicitly[Semigroup[Int]].append(2,2)
But sometimes it's inconvenient to call append(2,2) method, so it's a good practice to provide a symbolic alias:
trait Ops[A] {
def typeClassInstance: Semigroup[A]
def self: A
def |+|(y: A): A = typeClassInstance.append(self, y)
}
trait ToSemigroupOps {
implicit def toSemigroupOps[A](target: A)(implicit tc: Semigroup[A]): Ops[A] = new Ops[A] {
val self = target
val typeClassInstance = tc
}
}
object SemiSyntax extends ToSemigroupOps
4) You can use it as follows:
import SemiSyntax._
import implicits._ //you should also import actual instances for `Semigroup[Int]` etc.
2 |+| 2
If you wonder why so much boilerplate, and why scala's implicit class syntax doesn't provide this functionality from scratch - the answer is that implicit class actually provides a way to create DSL's - it's just less powerful - it's (subjectively) harder to provide operation aliases, deal with more complex dispatching (when required) etc.
However, there is a macro solution that generates boilerplate automatically for you: https://github.com/mpilquist/simulacrum.
One another important point about your CsvEncoder example is that instance is convenience method for creating type-class instances, but apply is a shortcut for "summoning" (requiring) those instances. So, first one is for library extender (a way to implement interface), another one is for a user (a way to call a particular operation provided for that interface).
Another thing to note is that in shapeless the apply method is not only for cuter syntax.
Take for instance this simplified version of shapeless' Generic and some case class Foo.
trait Generic[T] {
type Repr
}
object Generic {
def apply[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen
/* lots of macros to generate implicit instances omitted */
}
case class Foo(a: Int, b: String)
Now when I call Generic[Foo] I will get an instance that is typed as Generic[Foo] { type Repr = Int :: String :: HNil }. But if I call implicitly[Generic[Foo]] all the compiler knows about the result is that it's a Generic[Foo]. In other words: the concrete type of Repr is lost and I can't do anything useful with it. The reason is that implicitly is implemented as follows:
def implicitly[T](implicit e: T): T = e
That method declaration basically says: if you ask for a T I promise to give you a T, if I find one, and nothing more. So that means you'd have to ask implicitly[Generic[Foo] { type Repr = Int :: String :: HNil }] and that defeats the purpose of having automatic derivation.
Quoting the guide immediately after the object CsvEncoder definition:
The apply method ... allows us to summon a type class instance given a target type:
CsvEncoder[IceCream]
// res9: CsvEncoder[IceCream] = ...
In Haskell I got:
data Foo a where
Bar :: a -> Foo a
Map :: (a -> b) -> Foo a -> Foo b
instance Functor Foo where
fmap = Map
In Scala I came up with:
import cats.Functor
trait Foo[A]
case class Bar[A](t: A) extends Foo[A]
case class Map[A,B](fun: A => B,foo: Foo[A]) extends Foo[B]
implicit val fooFunctor: Functor[Foo] = new Functor[Foo] {
def map[A,B](fa: Foo[A])(f: A => B) = Map(f,fa)
}
But Bar(40).map(_+2) gives me:
error: value map is not a member of Bar[Int]
I fairly new to Scala and don't know the way of inheritance that well.
What am I missing?
You need to upcast Bar(40) to Foo explicitly, and import the syntax implicits:
import cats.syntax.functor._
(Bar(40): Foo[Int]).map(_+2)
You need the upcast because Scala will infer the type Bar[Int] for the expression Bar(40) and this then interferes with finding the appropriate implicit that adds the map method. For this reason you sometimes see helper methods in the companion object to do the upcast for you:
object Foo {
def bar[A](a: A): Foo[A] = Bar(a)
// etc.
}
I thought a little bit about how one could solve this problem. Basically, we want to provide the functor operations even if only a super type is a functor. It seems to be impossible to express, that some higher-kinded type is the super type of an other, e.g. something like this is not possible: def bla[F[_], R[_] <: F]
We can however provide a conversion from R[] to F[] implicitly together with the functor for B:
abstract class ProvidesFor[R,F[_],FF[_[_]],A](val value: FF[F]) {
def convert(r: R): F[A]
}
implicit def providesFunctorForBar[A] =
new ProvidesFor[Bar[A],Foo,Functor,A](Functor[Foo]) {
override def convert(r: Bar[A]): Foo[A] = r
}
So the implict val will provide us with a functor for Foo and a convertion from Bar to Foo.
Now we can provide an implicit converstion to Functor.Ops like this:
implicit class FunctorOps[R[_],F[_],A](target: R[A])(implicit tc: ProvidesFor[R[A],F,Functor,A])
extends Functor.Ops[F,A] {
override val self = tc.convert(target)
override val typeClassInstance = tc.value
override def map[B](f: A => B): F[B] = typeClassInstance.map(self)(f)
override def imap[B](f: A => B)(g: B => A): F[B] = map(f)
}
Now this works as expected:
Bar(1).imap(_+2)(_+5)
We can also do the same for Map:
implicit def providesFunctorForMap[A,B] =
new ProvidesFor[Map[A,B],Foo,Functor,B](Functor[Foo]) {
override def convert(r: Map[A,B]): Foo[B] = r
}
Map((_:Int) + 1,Bar(5)).map(_+2)
For some strange reason I had to implement map and imap when extending Functor.Ops even though these methods are not abstract. In fact it compiles fine when I do not implement them but it fails at runtime with an AbstractMethodError. So somehow the compiler thinks the implementations are there but they are not. I suspect they are using some kind of byte code optimization tool that removes those implementations.