Skip to content

Instantly share code, notes, and snippets.

@aappddeevv
Last active October 17, 2018 17:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aappddeevv/6588430 to your computer and use it in GitHub Desktop.
Save aappddeevv/6588430 to your computer and use it in GitHub Desktop.

As I was working through some content on the Reader monoid, I realized I wanted to lift a function to use it. If you read the Scala In Depth book, which as a really great book, you know that lifting a function to be monoidal could be useful.

The classic example is to lift the function so that the arguments as well as the return value are all Option types. Then, if any of the parameters to the new lifted function are None, the function returns None. Otherwise it would return Some.

The key thought is that you do not have to write your own, scalaz already supports lift.

It turns out that scalaz has support for lifting functions using your monoid of choice. Below is a transcript using scala REPL with a -cp set to the scalaz core library.

The easiest approach is to use optionInstance.lift(func2) where func2 takes 2 arguments.

Where does optionInstance come from? The scalaz.std package contains objects (modules really) that contain a variety of nice constructors and methods implicitly available. So there is an object scalaz.std.option that contains an object called optionInstance. So there is an object called scalaz.std.option which contains other vals or objects. If you are managing your import scope well, import scalaz.std.option.optionInstance. The std in scalaz refers to scala's standard library. For Either, there is a an object scalaz.std.either which has a scalaz.std.either.eitherInstance as well. The mnemonic is that by importing the optionInstance, you are bringing in constructive implicits which automatically wrap your type into scalaz objects when needed. optionInstance will also contain type classes for calling methods on type classes directly such as we did with Monad[Option] below.

$ scala -cp ./scalaz-core_2.10-7.1.0-M2.jar 
Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scalaz._
import scalaz._

scala> import std.option._
import std.option._

scala> def func2(i: Int, s: String) = s + "-" + i
func2: (i: Int, s: String)String

scala> func2(10, "base")
res0: String = base-10

// Messy with the type signature, you do not actually need it.
scala> def func2a = Apply[Option].apply2(_: Option[Int], _: Option[String])(func2)
func2a: (Option[Int], Option[String]) => Option[String]

scala> func2a(Some(10), Some("blah"))
res1: Option[String] = Some(blah-10)

scala> func2a(Some(10), Some("base"))
res2: Option[String] = Some(base-10)

scala> func2a(Some(10), None)
res3: Option[String] = None

scala> def func2b = Applicative[Option].lift2(func2)
func2c: (Option[Int], Option[String]) => Option[String]

scala> func2b(Some(10), Some("baseline"))
res12: Option[String] = Some(baseline-10)

scala> func2b(Some(10), None)
res13: Option[String] = None

scala> def func2d = Apply[Option].lift2(func2)
func2d: (Option[Int], Option[String]) => Option[String]

scala> func2d(Some(10), None)
res20: Option[String] = None

scala> func2d(Some(10), Some("blah"))
res21: Option[String] = Some(blah-10)

scala> def func2e = Monad[Option].lift2(func2)
func2e: (Option[Int], Option[String]) => Option[String]

scala> func2e(Some(10), Some("blah"))
res34: Option[String] = Some(blah-10)

// The easiest approach of them all!
scala> optionInstance.lift(func)
res2: Option[Int] => Option[String] = <function1>

Having describe how to generally lift a function, for example, the above code lifts func or func2 into an Option monad (we also call this lifting a function into an option environment), there are already builtin functions for lifting when using, for example, applicative syntax in scalaz. For example, the below code does the lifting on the function for us so we do not have to lift it ourselves. Note that we could use the applicative style here which is different than a for-comprehension. The applicative style is least powerful abstraction to use and that's a good design choice usually.

// Typing this off the top of my head

(AfuncReturingOptionInt |@| BfuncReturningOptionString) ( func2 )

Here, func2 is not lifted, it is lifted for us in the applicative implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment