Lift into a Functor with pure

Alex Kelley
7 min readJul 1, 2018

--

This is Tutorial 15 in the series Make the leap from JavaScript to PureScript. Be sure to read the series introduction where I cover the goals & outline, and the installation, compilation, & running of PureScript. I will be publishing a new tutorial approximately once-per-month. So come back often, there is a lot more to come!

Index | << Introduction < Tutorial 14 | Tutorial 16 > Tutorial 27 >>

Welcome to Tutorial 15 in the series Make the leap from Javascript to PureScript. I hope you’re enjoying it thus far. If you’re new to this series, then be sure to read the Introduction to learn how to install and run PureScript.

In this tutorial, we’re going to examine the pure function applied to a few types we covered in the past. In some respect, you can think of the function pure as the equivalent of JavaScript's of function. In JavaScript, a pointed functor is a functor with an of method. However, you'll likely never hear someone within the PureScript community refer to a Pointed Functor because there is no type class for it. More on this later.

I borrowed (with permission) the outline and javascript code samples from the egghead.io course Professor Frisby Introduces Composable Functional JavaScript by Brian Lonsdorf — thank you, Brian! A fundamental assumption is that you have watched his video before tackling the equivalent PureScript abstraction featured in this tutorial. Brian covers the featured concepts exceptionally well, and it’s better you understand its implementation in the comfort of JavaScript.

The markdown and all code examples for this tutorial are on Github. If you read something that you feel could be explained better, or a code example that needs refactoring, then please let me know via a comment or send me a pull request. Finally, If you are enjoying this series, then please help me to tell others by recommending this article and favoring it on social media. My Twitter handle is @adkelley.

Lifting into a Functor

In past code examples, we often took a concrete value or expressiona and applied a type constructor f to get an f a. We did this using type constructors such as Box, Either, Maybe, and Task. What I didn't mention is the name of this process, which is called lifting. The intuition for lifting is that we are embedding the a in some broader context that is characterized by the type constructor.

For example, when we embed a value in the Either a b type constructor, we are putting it into a context characterized by a Left a or Right b type constructor. By convention, Right b is used to represent a value or expression that is correct. While Left a often means that something went wrong in our computation. This convention implies that we have a contract with the compiler. I like to think of this contract as a statement that says - "whenever you see a Right b, then continue to map or compose my functions in sequence (i.e., bind) on b until the final result OR until you encounter a Left a". "If you encounter a Left a, then stop any further mapping or binding and return Left a so that I can examine and deal with it."

In JavaScript, we lift a value into a Pointed Functor using the of function. For example, we know from the last tutorial that Arrays are functors because they have a map operation. Therefore, in JavaScript, of = x ⇒ [x] lifts x into the pointed functor Array. In PureScript, I can lift a value into a functor by merely applying the type constructor to the value. For example, let's have another look at the function moneyToFloat from Tutorial2. It has been slightly modified to check whether the number is NaN :

nanToMaybe :: Number → Maybe Number
nanToMaybe x =
if (isNaN x)
then Nothing
else Just x
moneyToFloat :: String -> Box (Maybe Number)
moneyToFloat str =
Box str
# map (replace (Pattern "$") (Replacement ""))
# map (\replaced -> nanToMaybe $ unsafeParseFloat replaced)

I can apply Box str directly because the function's type signature implies lifting str into Box. For a moment, imagine that we change the type signature to moneyToFloat :: String → Maybe Number. How can I ensure my code runs without having to replace Box str with Just str explicitly? And what about other type constructors in the future? For example, I may later decide to change it to moneyToFloat :: String → Either a Number so that I can report the error. I sure don't want to look for and change all the constructors explicitly. Instead, whenever I refactor my code, I would prefer something a little more future proof.

Fortunately, there is a function in PureScript that applies the correct context based on a functor’s characteristics, and it’s called pure. If you're coming from JavaScript, then feel free to think of pure as the equivalent to the function,of in JavaScript. Now let's rewrite moneyToFloat in the Maybe context using pure. This way, I use join and later fromMaybe to flatten the two Maybe constructors. Then, I console log either the actual number or, in case there is an error, 0.0.

nanToMaybe :: Number → Maybe Number 
nanToMaybe x =
if (isNaN x)
then Nothing
else pure x
moneyToFloat :: String → Maybe Number
moneyToFloat str =
pure str
# map (replace (Pattern "$") (Replacement "")
# map (\replaced → nanToMaybe $ unsafeParseFloat replaced)
# join
main =
logShow $ fromMaybe 0.0 $ moneyToFloat "$1.55"
logShow $ fromMaybe 0.0 $ moneyToFloat "$NaN"

Now let’s look at some code examples that use pure to lift a value into the proper context based on a Functor's characteristics.

Hello Either

-- | either.of(x) == pure x 
-- | returns Right "hello"
eitherHello :: Either String String
eitherHello =
pure "hello"
main = logShow $ map (_ <> "!") eitherHello

We use the function pure to lift our value "hello" into Right "hello”. Then, in main, we map over the functor by lifting the expression (_ <> "!") into the Either context. Since eitherHello returns Right "hello", we can successfully map the expression (_ <> "!") on "hello"; logging Right "hello!" to the console.

Hello Task

The constructor TaskE is a little more interesting:

-- | task.of("hello") == taskOf "hello" == pure "hello"
-- | returns (TaskE a "hello") where a is a String
taskHello :: TaskE String String
taskHello = taskOf "hello"
-- | returns (TaskE "noHello" b), where b is a String
-- | taskRejected == throwError
taskNoHello :: TaskE String String
taskNoHello =
taskRejected "noHello for you"
showTask :: TaskE String String → Task Unit
showTask =
fork (\e → log $ "err " <> e) (\y → log $ "success " <> y)
main = do
void $ launchAff $ showTask taskHello
void $ launchAff $ showTask taskNoHello

If you squint hard enough, TaskE a b starts to look and behave like the Either a b constructor, with errors placed in the a slot, and successful computations placed in the b slot. In fact, underneath the covers, TaskE is ultimately transformed to the Either type constructor. Its type signature is: type TaskE x a = ExceptT x Aff a. Here, ExceptT is a monad transformer that allows us to combine multiple monads to form a new monad. Yes, I know we haven't covered monads, and we'll finally cover them in detail in the next tutorial. In the meantime, it is enough to know that the function runExceptT :: forall e m a. ExceptT e m a -> m (Either e a) is used to transform our TaskE down to a Task (Left e) or Task (Right a).

Where’s my Pointed Functor type class?

Throughout this tutorial, I’ve been careful not to say that the function pure lifts a value or an expression into a Pointed Functor. I did this because there is no Pointed Functor type class in PureScript. In fact, within the FP community, the Pointed Functor has lost its luster because it doesn't add any new laws to Functor. Moreover there is nothing to specify the meaning of Pointed or its relationship to map. I believe the author of PureScript recognized this and thereby avoided the mistake made in older FP languages, such as Haskell, by creating a type class for Pointed Functor and later regretting it.

At this point, you should be asking, “If there’s no Pointed Functor type class then where does pure appear?”. You’ll find it in the Control.Applicative module, which adds support for applicative functors. We haven’t covered applicative functors yet, but we will be soon. For now, it suffices to say that like monads (covered in the next tutorial) they are functors with extra laws and operations.

Summary

In this tutorial, I showed how to lift a value or expression a into a functor f by using the function pure. For example, Box a = pure a. While you can also apply a functor to a value directly (e.g., Box a), this approach isn't as future proof as using pure . For example, if I decide to change a setter's type signature from Box a to Maybe a, using pure ensures that my a is lifted into the Maybe functor automatically without explicitly applying Just a in my code.

That’s all for now. My next tutorial we finally explore the infamous topic of monads and we’ll look a little further into bind and see laws that ensure the monadic structure works correctly. If you are enjoying this series, then please help me to tell others by recommending this article and favoring it on social media. Thank you and till next time!

--

--

Alex Kelley

I work on technologies that simplify the creation and enjoyment of web applications.