Can I always replace a do-block like this:
do
...
pure ...
where the last line is "pure" something, with an ado-block like this:
ado
...
in ...
?
I know do
only requires Bind
and not Monad
(i.e. Applicative
), but we're already using pure
so we're already Applicative
.
The difference between Applicative (and therefore applicative do - ado
) and Monad (bind
or do
) is that when combining two applicative computations, the second one cannot depend on the result of the first computation. This means:
do
x <- getX
y <- getY
pure { x, y }
-- Can be turned into
ado
x <- getX
y <- getY
in { x, y }
But if y depends on x we cannot do that:
do
x <- getX
y <- getY x
pure { x, y }
-- This won't compile
ado
x <- getX
y <- getY x
in { x, y }
This means the answer to your question is "no".
Let's think about an intuitive example. The Aff Monad allows us to run asynchronous effects, like fetching data from an API. If the requests are independent, we can run them in parallel.
ado
user1 <- get "/user/1"
user2 <- get "/user/2"
in [user1, user2]
If they depend on each other, we need to run one request after another:
do
user1 <- get "/user/1"
bestFriend <- get ("/user/" <> user1.bestFriendId)
pure { user1, bestFriend }
Please note, that Aff
always runs things sequentially no matter if you use bind
or apply
, unless we use parallel
which turns an Aff
into a ParAff
. Notice the lack of a Bind/Monad instance for ParAff
because two parallel computations cannot depend on each others results! https://pursuit.purescript.org/packages/purescript-aff/7.1.0/docs/Effect.Aff#t:ParAff
Meaning for the first example to really run in parallel, we have to actually
ado
user1 <- parallel $ get "/user/1"
user2 <- parallel $ get "/user/2"
in [user1, user2]
Another example I like is parsing/validation. Imagine you are parsing two fields of user input: A date string and an age. We can check both properties an parallel and return two errors if both properties are invalid. But to validate a date, we might first have to check if an input value is a string and then check if the value is a date string. For an example library, check out purescript-foreign
validated = ado
date <- do
str <- isString dateInput
isDate date
age <- do
int <- isInt ageInput
isPositive int
in { date, age }
We see that Applicative is much weaker than Monad. But there are intances, where forgoing in the power of Monad can give us other interesting possibilities like parallelisation or multiple errors.