Lift into a Functor with pure
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 xmoneyToFloat :: 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)
# joinmain =
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!