# "do"ing it right

I feel like there is a better way to do this, but I"m not clear where to start. Currently, I have this code (snippet):

``````sub :: OpResult -> OpResult -> OpResult
sub (OpInt x) (OpInt y) = OpInt \$ x - y
sub (OpInt x) (OpNumber y) = OpNumber \$ (toNumber x) - y
sub (OpNumber x) (OpInt y) = OpNumber \$ x - (toNumber y)
sub (OpNumber x) (OpNumber y) = OpNumber \$ x - y

doTheSubtraction :: Either Error OpResult -> Either Error OpResult -> Either Error OpResult
doTheSubtraction (Left e1) (Left e2) = Left (concat e1 e2)
doTheSubtraction (Left e1) _ = Left e1
doTheSubtraction _ (Left e2) = Left e2
doTheSubtraction (Right x) (Right y) = Right (sub x y)

subtract :: SubtractOperation -> Maybe OpResult -> Effect (Either Error OpResult)
subtract (SubtractOperation r) =
( \v -> do
m <- makeOperate r.minuend v
s <- makeOperate r.subtrahend v
pure \$ doTheSubtraction m s
)

makeOperate :: Operation -> Maybe OpResult -> Effect (Either Error OpResult)
makeOperate (FromFormFieldOp op) = getFromFormField op
makeOperate (SubtractOp op) = subtract op
-- ...
``````

There are more Operations. The `From` return a value in an Effect (Either). The other Ops (`subtract`) operate on those values. These can be nested.

As you can see Iâ€™ve made three separate functions to get the values out of the nested operations and then perform subtraction on them. But is there a way to do all this in the `do` of `subtract`. More importantly, is it a better way? What do the experts recommend?

Hi! Since this is only part of the code, itâ€™s hard to give comprehensive feedback. However, I think there are still some useful things to say

• `sub` looks reasonable to me. It could be simplified if it always returned an `OpNumber`, but since it sometimes returns an `OpInt` I think the way youâ€™ve written it is probably the simplest

• `doTheSubtraction` can potentially be simplified. Assuming `concat` in your code could be replaced by `append`, then `doTheSubtraction a b` is the same as `toEither (sub <\$> V a <*> V b)`. This uses the Validation applicative to handle the error logic (ie, what to do with `Left`) and deferrs to `sub` for the success logic (ie, what to do with `Right`).

• I suspect that `subtract` and `makeOperate` can be improved as well (their use of `Maybe` looks suspiecious to me), but itâ€™s hard to tell without more context

Thanks! This is helpful. The current version of this (alpha) code is here: operations-ps/src/Sitebender.purs at main Â· site-bender/operations-ps Â· GitHub

Iâ€™ll take a look at `doTheSubtraction`. I have been looking at `V` already.

What I was curious about was whether `sub` or `doTheSubtraction` could be folded into `subtract` â€“ one function vs. three. But maybe that would just overcomplicate things.

The use of Maybe in `makeOperate` is because it returns a function (to be called from JS) which has an optional parameter which might be used in the calculation, or might not.

Thank you much for your help. Sounds like Iâ€™m not as far off as I thought I might be.

1 Like

`doTheSubtraction` can definitely be folded into `subtract`:

``````subtract (SubtractOperand r) v = do
m <- makeOperate r.minuend v
s <- makeOperate r.subtrahend v
pure <<< toEither \$ sub <\$> V m <*> V s
``````

I wouldnâ€™t recommend inlining `sub` the same way. However, you might be interested in factoring out the type-conversion logic:

``````converting ::
(Int -> Int -> Int)              -- What to do on int
-> (Number -> Number -> Number)  -- What to do on float
-> OpResult -> OpResult -> OpResult
converting f g =
case _, _ of
OpInt x, OpInt y -> f x y
x, y -> g (toNumber' x) (toNumber' y)

where
toNumber' = case _ of
OpNumber a -> a
OpInt a -> toNumber a
``````

Then you could write `subtract` as

``````subtract (SubtractOperand r) v = do
m <- makeOperate r.minuend v
s <- makeOperate r.subtrahend v
let minus = converting (-) (-)
pure <<< toEither \$ minus <\$> V m <*> V s
``````
1 Like

Outstanding! Thanks much. Not only does this work, but I actually understand it.

``````add :: AddOperation -> Maybe OpResult -> Effect (Either (Array String) OpResult)
let plus = converting (+) (+)
pure <<< toEither \$ plus <\$> V m <*> V s
``````

The only thing I still donâ€™t get is how I could change this to take a single â€śaddendsâ€ť foldable and then use `foldl` and `zero` to add two or more addends.

Here is where I go wrong. I just canâ€™t quite wrap my head around (pun intended) unwrapping all these contexts. This doesnâ€™t work:

``````type AddOpRow r = (addends :: (Array Operation) | r)

add :: AddOperation -> Maybe OpResult -> Effect (Either (Array String) OpResult)
let plus = converting (+) (+)
let sum l r = plus <\$> V (makeOperate l v) <*> V (makeOperate r v)
pure <<< toEither \$ foldl plus r.addends
``````

The problem, of course, is that `(makeOperate l v)` and `(makeOperate r v)` return Effect. I canâ€™t figure out how to make this work. I tried `lift2`, which seems right, but then I get the `V` issue (Effect vs. Either t0):

``````add (AddOperation r) v = do
let plus = converting (+) (+)
let sum l r = lift2 plus (V (makeOperate l v)) (V (makeOperate r v))
pure <<< toEither \$ foldl sum zero r.addends
``````

Hopefully, all this wrapping and unwrapping will eventually work its way into my brain and it will become second nature. Hopefully â€¦

Your new `AddOperation` is isomorphic to `Array Operation`, so Iâ€™m going to just use that for simplicity

It is indeed troublesom that `makeOperate` returns an `Effect`. It is extra annoying that it returns an `Effect (Either _ _)`. We can deal with these by using the monadic `map` function:

``````Data.Traversable.traverse :: (a -> Effect b) -> Array a -> Effect (Array b)
``````

as well as

``````Data.Traversable.sequence :: Array (Either e a) -> Either e (Array a)
``````

(Presented with simplified types)

One way to write your variadic `add` is like this:

``````add :: Array Operation -> Maybe OpResult -> Effect (Either (Array String) OpResult)
(addendsAE :: Array (Either _ _)) <- traverse (\op -> makeOperate op v) ops
(addendsEA :: Either _ (Array _)) = toEither (sequence (V <\$> addendsEA))
let plus = converting (+) (+)
pure \$ (foldl plus zero <\$> addendsEA)
``````

(untested)

Traverse. Sigh, of course. I was actually using that in the TS version of this code.

Iâ€™ll work through this. Thanks.

The reason for the Effect is that at the bottom of the tree of operations are â€śinjectorsâ€ť that get the values (operands) from form fields, session storage, etc. Hence, Effect (soon Aff).

1 Like