Reputation: 233
Let's say I have this sample code:
h = 2
add = \x y -> (x + y)
addH = add h
main = return (fmap addH [1,2])
Running it evaluates to [3,4]
Now, let's say h is not set to "2", but to "Just 2".
Question, part 1:
What is the proper refactor, then, to still return [3,4] in the end?
Question, part 2:
Would a skilled Haskell developper prefer to change the return value to be [Just 3, Just 4]
For example with a refactor like this:
h = Just 2
add = \x y -> (x + y)
addH = liftM2(add) h . pure --isn't there a better syntax for that?
main = return (fmap addH [1,2])
More generally, in an existing codebase, how to minimize the refactor impact when a function that used to return 'Num t => [t]' must now return 'Num t => [Maybe t]'?
Upvotes: 0
Views: 114
Reputation: 3625
I would like to point out that in your original code, []
and Maybe
are redundant, and that a closer inspection of the meaning of these two functors leads to an interesting discussion. []
(list) represents a success or a failure in the same sense that Maybe
does, the main difference being that []
can represent 0 or more values, whereas Maybe
is limited to 0 or 1 value.
This leads to another consideration about the meaning of the Just
in Just 2
. What does it mean for 2
to be wrapped in a Just
, and what would a Nothing
mean? To take this one step further, here are five different results that you could opt for, each having a different meaning:
Just [3, 4]
: There was one monolithic computation that dealt with multiple values, and it succeeded.Nothing
: There could have been a monolithic value, but there is none.[3, 4]
: There was a batch of computations, and these are the values that came out.[]
: There could have been multiple values, but there are none.[Just 3, Just 4]
: There was a batch of computations, where each computation could have failed, and all of them succeeded.Case 1: If h
represents a monolithic value, and is the unique point of failure in this computation chain, then options 1 and 2 seem like a reasonable choice. The following code accomplishes this:
import Control.Applicative (LiftA2)
h = Just 2
add = (+)
addH = LiftA2 add h . pure
-- Returns Just [3,4] or Nothing
mainFunc = traverse addH [1,2]
Case 2: If add
represents a function that can fail depending on its argument (for example, think of the function hDivBy
, which would fail on every 0), then options 3 and 4 seem like a good choice.
import Data.Maybe (mapMaybe)
hDivBy = \x -> (`div` x) <$> h
--Returns [1]
mainFunc = mapMaybe hDivBy [0, 2]
Case 3: If you want to keep track of element indexing, then option 5 will give you just that:
--Returns [Nothing, Just 2]
mainFunc = fmap hDivBy [0,2]
Notice that this code keeps everything in the Applicative
realm, making it more general, amongst other advantages.
Upvotes: 1
Reputation: 1533
To acheive Fyodor Soikin result you can try the below one.
h = Just 2
add = \x y -> x + y
addH arr = pure fmap <*> (add <$> h) <*> pure arr
main = return $ addH [1, 2]
Upvotes: 0