There seems to be a difference in the evaluation order of applicative do notation / ado
vs. applicative lifting via <$>
/map
on the first argument, and <*>
/apply
for remaining arguments.
At least, this is what I have read so far and what is reflected in the course of the exercise shown below. Questions:
ado
), respecting the preorder assertion from the test?Exercise from the PureScript by Example book (Chapter 7) can be found here:
3.(Medium) Write a function
traversePreOrder :: forall a m b. Applicative m => (a -> m b) -> Tree a -> m (Tree b)
that performs a pre-order traversal of the tree. [...] Applicative do notation (ado) is the easiest way to write this function.
Algebraic data type Tree
:
data Tree a
= Leaf
| Branch (Tree a) a (Tree a)
Test expecting the traverse order [1,2,3,4,5,6,7]
:
Assert.equal (1 .. 7)
$ snd
$ runWriter
$ traversePreOrder (\x -> tell [ x ])
$ Branch (Branch (leaf 3) 2 (leaf 4)) 1 (Branch (leaf 6) 5 (leaf 7))
Note: I am not sure, what tell
and runWriter
exactly do - this is a copied code block from the exercise.
For illustration - the example tree looks like this:
ado
(works)traversePreOrder :: forall a m b. Applicative m => (a -> m b) -> Tree a -> m (Tree b)
traversePreOrder f Leaf = pure Leaf
traversePreOrder f (Branch tl v tr) = ado
ev <- f v
etl <- traversePreOrder f tl
etr <- traversePreOrder f tr
in Branch etl ev etr
traversePreOrder :: forall a m b. Applicative m => (a -> m b) -> Tree a -> m (Tree b)
traversePreOrder f Leaf = pure Leaf
traversePreOrder f (Branch tl v tr) =
let
ev = f v -- I consciously tried to place this evaluation first, does not work
etl = traversePreOrder f tl
etr = traversePreOrder f tr
in
Branch <$> etl <*> ev <*> etr
This triggers the error:
expected [1,2,3,4,5,6,7], got [3,2,4,1,6,5,7]
let ev = f v -- I consciously tried to place this evaluation first, does not work etl = traversePreOrder f tl etr = traversePreOrder f tr in Branch <$> etl <*> ev <*> etr
Source order does not matter in functional programming. You could place these let
declarations in any order, it would work the same - they would create the same values, and these values would describe the same computation, and will form the same programs when used in the same expressions.
The "evaluation order" that actually matters here is a property of the applicative functor you're using - the order in which applicative effects are applied. The order is governed by the operators from the Applicative
typeclass which you are using here, namely <*>
: it is documented to first apply the effects from the left hand side, then the effects from the right hand side. To implement pre-order traversal, you will therefore have to write
traversePreOrder :: forall a m b. Applicative m => (a -> m b) -> Tree a -> m (Tree b)
traversePreOrder f Leaf = pure Leaf
traversePreOrder f (Branch tl v tr) =
(\ev etl etr -> Branch etl ev etr) <$> f v <*> traversePreOrder f tl <*> traversePreOrder f tr
(Disclaimer: I don't know PureScript very well, but it looks very much like Haskell and seems to work the same here.)