How can I implement a similar Haskell function in Scala 3 and Cats?
ghci> (+) <$> (+1) <*> (+1) $ 10
22
There was a solution using mapN
. It is mentioned here, Using functions as applicative functors/cartesians, but I want a solution using <*>
instead of mapN
. I tried something similar but it won't compile
import cats._
import cats.implicits._
type Func1[Y] = Int => Y
val add1: Func1[Int] = (x: Int) => x + 1
val addXY: Func1[Func1[Int]] = (x: Int) => (y: Int) => x + y
val foo: Func1[Func1[Func1[Int]]] = Applicative[Func1].pure(addXY)
val bar: Func1[Func1[Int]] = Applicative[Func1].pure(add1)
val foobar = foo <*> bar
foobar(10) // should be 22 but it can't compile
Please advise. Thanks
Scala Cats admits functions as Applicative
instances, just like Haskell does. The Haskell code you wrote is
(+) <$> (+1) <*> (+1) $ 10
We can write these functions in Scala as
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
Now <*>
works like it does in Haskell, but <$>
doesn't exist as infix (not least of all because $
isn't a symbol character in JVM-based languages). Remember that f <$> x
is just equivalent to fmap f x
, or (by the Applicative
laws) pure f <*> x
.
So we can write
val foobar = addXY.pure[[X] =>> Function1[Int, X]] <*> add1 <*> add1
println(foobar(10))
Now, while Cats doesn't provide an infix <$>
that I know of, we can easily write one ourselves as an extension method. We can't name it <$>
because the dollar sign is funny on the JVM, but we can write <@>
instead.
extension[A, B](left: A => B)
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
Now we can say
val foobar = addXY <@> add1 <*> add1
println(foobar(10))
Complete example:
package example
import cats.*
import cats.implicits.*
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
extension[A, B](left: A => B)
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
@main def main(args: String*): Unit = {
val foobar = addXY <@> add1 <*> add1
println(foobar(10))
}
All of the above is written in Scala 3. If you're in Scala 2, then you have to do a bit of messy business to get Scala to treat the functions the way you want. As you've already seen, you have to write a type
alias to get the right type-level generic behavior (if you want to call pure
, for instance). The def
functions have to be explicitly converted to functions with a suffix _
. And the extension
method will need to be an old-school implicit class
.
So all in all, this is the same program written in Scala 2.
package example
import cats._
import cats.implicits._
import scala.language.higherKinds
object Main {
implicit class FmapExtension[A, B](
val left: A => B,
) extends AnyVal {
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
}
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
def main(args: Array[String]): Unit = {
val foobar = (addXY _) <@> (add1 _) <*> (add1 _)
println(foobar(10))
}
}