thlim
thlim

Reputation: 2982

Using an Applicative Functor Functions with Cats and Scala

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

Upvotes: 1

Views: 182

Answers (1)

Silvio Mayolo
Silvio Mayolo

Reputation: 70287

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))
}

Scala 2

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))
  }

}

Upvotes: 4

Related Questions