scalascala-cats

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


Solution

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