Note: This is Tutorial 4 - Part 2 in the series Make the leap from JavaScript to PureScript . Be sure to read the series introduction where we cover the goals & outline, and the installation, compilation, & running of PureScript. I will be publishing a new tutorial approximately once-per-week. So come back often, there is a lot more to come!
Welcome to Tutorial 5 in the series Make the leap from Javascript to PureScript, and I hope you've enjoyed the learnings thus far. Be sure to read the series Introduction to learn how to install and run PureScript. I borrowed (with permission) this series 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. And for this particular tutorial, you should also review his transcript, which has the imperative and FP code examples in Javascript. Brian covers the featured concepts extremely well, and it's better that you understand its implementation in the comfort of JavaScript. Finally, 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 on Github.
Each of the examples below shows the FP example in JavaScript, followed by its port to PureScript.
In my Github repository, you will find each code snippet in a separate PureScript file; importing ExampleX.purs and calling it from Main.purs
. All my utility functions, including chain
and fromNullable
, and their corresponding FFI are in the Data
folder. It is worth noting that I refactored chain
from the [previous tutorial](Github](https://github.com/adkelley/javascript-to-purescript/tree/master/tut05) to point-free style (tacit programming). So if you're interested in tacit programming, then have a look at chain
and read the discussion on point-free style in Example 1
below. Finally, Example 5 requires reading a JSON file, so I have created example.json
and put it in the folder ./src/resources
.
A few of the examples simulate receiving JSON objects and values over the wire. If I were doing this in production, I would likely use purescript-argonaut to parse the JSON and transform the objects and values into PureScript Records and basic types respectively. But I want to keep it simple, so I decided to access the JavaScript objects and their name/value pairs using the FFI. Furthermore, I typically treat them as Foreign
types until I print them to the console.
When handling Foreign
types returned from a Javascript function or from JSON coming over the wire, there is always the possibility that they may be null
or undefined
. But in PureScript there are no null
or undefined
values, so we typically represent this concept by the value Nothing
from the Maybe
type. Think of Nothing
as something like a type-safe null
and, for now, we'll save further details on the Maybe
type for a later tutorial.
To match Brian's code examples, I created fromNullable
to check whether a Foreign
type is null
or undefined
, returning Either Error Foreign
. Similarly, fromString
returns Either Error String
, depending on whether a string is empty. I am always looking for opportunities to DRY (Don't Repeat Yourself) out my code and found fromNullable
and fromString
to be very similar. So I abstracted the repetition into a separate function, toEither
. This method is another example of the benefits of PureScript's support for polymorphism. Notice how I was able to make Right x
a polymorphic type so that it works for both Foreign
, String
and other types.
toEither :: forall a. Boolean -> String -> a -> Either Error a
toEither predicate errorMsg value =
if predicate
then Left $ error errorMsg
else Right value
fromEmptyString :: String -> Either Error String
fromEmptyString value =
toEither (value == "") "empty string" value
fromNullable :: Foreign -> Either Error Foreign
fromNullable value =
toEither (isNull value || isUndefined value) "null or undefined" value
In PureScript and other FP languages, you'll frequently find that, while a type declaration of a function states that it accepts arguments (or points), the actual arguments are missing in the implementation. We call this paradigm point-free or tacit style programming, and it helps 'sometimes' to give a precise definition of the function. I said 'sometimes' because point-free can also obscure the meaning of a function; particularly when an argument name helps in understanding a function's implementation. The PureScript example below has been written in a point-free style.
const openSite = () =>
fromNullable(current_user)
.fold(showLogin, renderPage)
openSite :: Foreign -> String
openSite =
fromNullable >>>
either (\_ -> "showLogin") \_ -> "renderPage"
I chose the point-free style to illustrate this paradigm and to take advantage of function composition. That is, openSite = fromNullable >>> . . .
vs. openSite currentUser = (fromNullable currentUser) # . . .
But it is debatable whether the fact that it takes a current user remains clear. So, given that your mileage will vary, always proceed with caution when deciding whether to use point-free style.
const getPrefs = user =>
(user.premium ? Right(user) : Left('not premium'))
.map(u => u.preferences)
.fold(() => defaultPrefs, prefs => loadPrefs(prefs))
getPrefs :: Foreign -> String
getPrefs user =
toEither (getPremium user) "not premium" user #
map getPreferences >>>
either (\_ -> defaultPrefs) \prefs -> "loadPrefs " <> prefs
const streetName = user =>
fromNullable(user.address)
.chain(a => fromNullable(a.street))
.map(s => s.name)
.fold(e => 'no street', n => n)
streetName :: Foreign -> String
streetName user =
(fromNullable $ getAddress user) >>=
(\address -> fromNullable $ getStreet address) >>=
(\street -> fromNullable $ getStreetName street) >>>
map (\name -> unsafeFromForeign name :: String) #
either (\_ -> "no street") id
Whoah! What happened to chain
? Well, I have a secret that I've been keeping since the last tutorial - you can replace chain
with bind
(operator alias >>=
)! I know, shocking isn't it. Perhaps I should have come clean earlier, but I felt it was better to stick with Brian's abstraction in the last tutorial, namely chain
. So why and when can we replace chain
with bind
you ask? Let's look at their type declarations to see if that helps to illuminate things:
chain :: forall a b e. (a -> Either e b) -> Either e a -> Either e b
bind :: forall a b. m a -> (a -> m b) -> m b
Nope, not really. Hold on a minute - let's try some substitution. First we insert Left
and Right
in chain
:
chain :: forall a b e. (a -> Right b) -> Left e -> Left e
chain :: forall a b e. (a -> Right b) -> Right a -> Right b
Then, substitute m a
for Left a
and Right a
in bind
:
bind :: forall a b. Left a -> (a -> Right b) -> Left a
bind :: forall a b. Right a -> (a -> Right b) -> Right b
Interesting, so bind
is quite similar to chain
but with the first two arguments flipped! Why does this work? Well, if bind
sees that the first argument is Left a
then, like chain
, it ignores the function application, represented by the second argument. It only passes and returns the first argument, Left a
, just like Left e
in chain
. But if the first argument is Right a
then the second function argument (a -> Right b) is applied and Right b
will be returned.
The next question is 'when can I substitute chain
with bind
? Well, you can see that these functions are interchangeable. And, because they are roughly synonymous, you won't find chain
in the standard PureScript library called the Prelude
. Take a quick look at Example 6, where I've provided two versions of parseDbUrl
- one using chain
and the other using the operator alias for bind
. That example should help you to go back and refactor any functions using chain
to bind
. So say goodbye to chain
from here on and long live bind
!
const concatUniq = (x, ys) =>
fromNullable(ys.filter(y => y === x)[0])
.fold(() => ys.concat(x), y => ys)
concatUniq :: String -> String -> String
concatUniq x ys =
filter (_ == x) ys #
fromEmptyString #
either (\_ -> ys <> x) \_ -> ys
This tip is straightforward and very useful in practice. In the PureScript example above, notice the expression filter (_ == x) ys
. You may be wondering why I didn't write it as filter (\y -> y == x) ys
. Well, because I'm using an anonymous function argument _
, which represents an anonymous argument in the predicate portion of the filter function. Think of _
as a little syntax sugar that helps to shorten your code. You'll be pleased to know that it works for Records and other types of expressions as well. You can learn everything you need to know about anonymous function arguments this blog post, which is part of a series '24-days-of-purescript-2016'.
Before discussing the let
and where
keywords, let me make mention that this code snippet makes good use of native side effects. That topic was well covered in Part 2 of Tutorial 4, so if you're still shaky on File IO and exception handling then go back and have a look. Now onto the use of where
vs. let
keywords in the duel wrapExample
snippets.
const readFile = x => tryCatch(() => fs.readFileSync(x))
const wrapExample = example =>
fromNullable(example.previewPath)
.chain(readFile)
.fold(() => example,
preview => Object.assign({}, example, preview))
wrapExample :: forall eff. Foreign -> Eff (fs :: FS, exception :: EXCEPTION | eff) Foreign
wrapExample example =
fromNullable (getPreviewPath example) #
map (\path -> unsafeFromForeign path :: String) >>>
either (\_ -> pure example) wrapExample'
where
wrapExample' pathToFile =
(try $ readTextFile UTF8 pathToFile) >>=
either Left parseValue >>>
either (\_ -> example) (assignObject2 example) >>>
pure
wrapExample_ :: forall eff. Foreign -> Eff (fs :: FS, exception :: EXCEPTION | eff) Foreign
wrapExample_ example =
fromNullable (getPreviewPath example) #
map (\path -> unsafeFromForeign path :: String) >>>
let
wrapExample' pathToFile =
(try $ readTextFile UTF8 pathToFile) >>=
either Left parseValue >>>
either (\_ -> example) (assignObject2 example) >>>
pure
in
either (\_ -> pure example) wrapExample'
We have seen the where
keyword many times before. It is usually at the top of a module for the purpose of delineating the block of code represented by the module name. But, so far, I haven't used it inside a function. The purpose is the same - introduce a new block of code, indenting that code so that the compiler understands that where
is bound to the syntactic construct (i.e., the new block of code). In the example, you can see that where
is forever linked to the code that defines wrapExample'
.
Now let's take a look at let
(no pun intended). At first blush, the purpose of where
and let
appear to be identical, and this is roughly correct. But there is a subtle difference! let . . . in . . .
is also an expression, and therefore can be written wherever expressions are allowed. The code snippet best explains this difference. I used let . . . in . . .
as an expression by inserting it between the map
and either
functions. I could have gone even further, with the following:
map (\path -> unsafeFromForeign path :: String) >>>
either (\_ -> pure example)
let wrapExample' pathFile = . . .
in wrapExample'
but then it becomes a matter of readability.
For more information on let
vs. where
, check out Let vs. Where from wiki.haskell.org. Oh, and you are going to find that the syntax of Haskell is surprisingly similar to PureScript! I've heard a few PureScripters commenting that they were able to pick up Haskell much more quickly, thanks to having learned PureScript first. Likewise, I learned Haskell first and found I was able to pick up PureScript more rapidly.
FFirst, before discussing regular expressions in PureScript, my reason for creating the dual code snippets parseDbUrl
and parseDbUrl_
is to demonstrate how to port a function that uses chain
to use bind
instead. This code snippet will be the last time you will see chain
in my tutorials. And to learn why then please review the discussion in Example 2.
const parseDbUrl = cfg =>
tryCatch(() => JSON.parse(cfg))
.chain(c => fromNullable(c.url))
.fold(e => null,
u => u.match(/postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/))
dBUrlRegex :: Partial => Regex
dBUrlRegex =
unsafePartial
case regex "^postgres:\\/\\/([a-z]+):([a-z]+)@([a-z]+)\\/([a-z]+)$" noFlags of
Right r -> r
matchUrl :: Regex -> String -> Either Error (Array (Maybe String))
matchUrl r url =
case match r url of
Nothing -> Left $ error "unmatched url"
Just x -> Right x
parseDbUrl_ :: Partial => String -> Array (Maybe String)
parseDbUrl_ =
parseValue >>>
chain (\config -> fromNullable $ getDbUrl config) >>>
map (\url -> unsafeFromForeign url :: String) >>>
chain (matchUrl dBUrlRegex) >>>
either (\_ -> singleton Nothing) id
parseDbUrl :: Partial => String -> Array (Maybe String)
parseDbUrl s =
(parseValue s) >>=
(\config -> fromNullable $ getDbUrl config) >>>
map (\url -> unsafeFromForeign url :: String) >>=
(\r -> matchUrl dBUrlRegex r) #
either (\_ -> singleton Nothing) id
Like JavaScript, PureScript supports regular expressions well - by wrapping JavaScript's very own RegExp
object! The types and functions are part of the purescript-strings
library, located in the module Data.String.Regex
. First up is the Regex
object.
There's some new pieces of syntax in DbUrlRegex
function, namely Partial
and unsafePartial
. In this instance, they allow us to treat a non-exhaustive case expression as a regular case expression (unsafely). So why did I decide unsafePartial
? Because I tested the regular expression "^postgres:\\/\\/([a-z]+):([a-z]+)@([a-z]+)\\/([a-z]+)$"
and I know it works! So no need to bother returning and dealing with an Either Error Regex
. You can also take advantage of unsafePartial
to return partial functions; again unsafely. And, as a consequence of DbUrlRegex
, that is exactly what I am doing in parseDbUrl
.
'Let the types be your guide' as we say, so I call out in the type declaration the fact that DbUrlRegex
is a partial function, and therefore it belongs to the Partial
class (i.e., dbUrlRegex :: Partial => Regex
). This fact propagates all the way back to main
. I declare that parseDbUrl
returns a partial function and, consequently, in the main
code snippet below, I use the unsafeFromPartial
function to log the result to the console.
One final item - parseDbUrl
returns Array (Maybe String)
, and so you're probably wondering about the Maybe
constructor. Again, as I mentioned in the fromNullable
and fromEmptyString
section, we'll cover that abstraction in a future tutorial.
defaultConfig :: String
defaultConfig = "{ \"url\": \"postgres:\\/\\/username:password@localhost/mydb\"}\n"
main :: forall e. Eff (fs :: FS, exception :: EXCEPTION, console :: CONSOLE | e) Unit
main = do
log "A collection of Either examples"
log "Example 1"
log $ openSite getCurrentUser
log "Example 2"
log $ getPrefs getCurrentUser
log "Example 3"
log $ streetName getCurrentUser
log "Example 4"
log $ concatUniq "x" "ys"
log $ concatUniq "y" "y"
log "Example 5"
log "using where keyword in wrapExample"
example <- wrapExample getCurrentExample
log $ unsafeFromForeign example :: String
log "Example 6"
log "Using bind to help parse the database URL"
logShow $ parseDbUrl defaultConfig
log "Game Over"
In the next Tutorial we're going to learn about Semigroups. If you are enjoying this series then please help me to tell others by recommending this article and/or favoring it on social media. Thank you and till next time!