Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to apply arguments of mixed, possibly Applicative, types to a function

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:

  • Some are Int, some are Maybe Int (result of the application would be Maybe Int)
  • Some are 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)
  • Some are [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!

like image 876
Aiueiia Avatar asked May 21 '20 10:05

Aiueiia


1 Answers

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.

Some 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.

Some 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!".

Some [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.

like image 120
Mark Seemann Avatar answered Nov 15 '22 05:11

Mark Seemann