I'm fairly new to Haskell and functional programming and I have recently been learning about Functors, Applicatives and Monads. While I seem to understand the basics, I have trouble figuring out the best/most idiomatic way apply function arguments when the type of some arguments changes to an Applicative. Consider the following, simple code:
myfun :: Int -> Int -> Int -> Int
myfun a b c = a + b + c -- lets pretend this does something more complicated
a = 5
b = 10
c = 20
result = myfun a b c
Using myfun
to calculate the result is fairly straightforward. However, as our requirements change, our inputs a
, b
and c
may change to be i.e. Maybe Int
or [Int]
rather then Int
. We can still use our unmodified myfun
by doing one of the following:
result = myfun <$> a <*> b <*> c -- either like this
result = liftA3 myfun a b c -- or like that
However, in practice the arguments a
, b
and c
may not always end up to be inside the same Applicative, and thus the two methods mentioned above would not work. What is the best way to still make the myfun
function work without modifying it? Consider the following scenarios for a
, b
and c
:
Int
, some are Maybe Int
(result of the application would be Maybe Int
)Maybe Int
, some are Either String Int
(result could be Maybe Int
or Either String Int
, with the semantics of short-circuiting the calculation if any argument is Nothing
or Left
)[Int]
, some are Maybe Int
(result should be Maybe [Int]
, with the semantics of calculating all possible combinations as if all arguments were [Int]
, then wrapping that inside a Just
, unless on of the Maybies are Nothing
, in which case we short-circuit to Nothing
)Any insights are very much appreciated!
It depends on what you want to happen. There may not be any universal way to combine different monads. In general, you can often (always?) use a monad transformer when you truly need to combine different monads, but usually there are simpler solutions. That's the case with the specific combinations you mention.
In all of these specific cases, you can transform one of the monads into another. In the following, I'll give some examples of ways this could be done.
Some of these examples use functions from Data.Maybe
, so I'll start with:
import Data.Maybe
It's not required in the first example, but will be in the second and third.
Int
, some Maybe Int
If you have a combination of Int
and Maybe Int
values, the solution is straightforward. Just elevate the Int
values to Maybe Int
. You can use Just
or pure
for this. Here's an example using pure
:
a1 = 5
b1 = Just 10
c1 = 20
result1 :: Maybe Int
result1 = myfun <$> pure a1 <*> b1 <*> pure c1
The result is Just 35
.
Maybe Int
, some Either String Int
You can repeat the trick with transforming one of the monads into the other. You can transform Maybe Int
values to Either String Int
values if you have a good String
to use for Nothing
cases. You can also transform Either String Int
values to Maybe Int
values by throwing away the String
values.
Here's an example that transforms Maybe Int
to Either String Int
:
a2 = Just 5
b2 = Right 10
c2 = Left "Boo!"
result2 :: Either String Int
result2 = myfun <$> maybe (Left "No value") Right a2 <*> b2 <*> c2
This combination uses the maybe
function from Data.Maybe
. The result is Left "Boo!"
.
[Int]
, some Maybe Int
You can easily turn Maybe Int
into [Int]
using maybeToList
:
a3 = [5, 10]
b3 = Nothing
c3 = Just 20
result3 :: [Int]
result3 = myfun <$> a3 <*> maybeToList b3 <*> maybeToList c3
The result of doing this is []
because Nothing
transforms to []
, and that's how Applicative
works for lists. This may not be what you want, but I hope these examples can inspire you to come up with the compositions you'd like.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With