Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a pure value from a impure method

I know it must sound trivial but I was wondering how you can unwrap a value from a functor and return it as pure value?

I have tried:

f::IO a->a
f x=(x>>=) 

f= >>=

What should I place in the right side? I can't use return since it will wrap it back again.

like image 910
Bercovici Adrian Avatar asked Dec 01 '22 14:12

Bercovici Adrian


2 Answers

It's a frequently asked question: How do I extract 'the' value from my monad, not only in Haskell, but in other languages as well. I have a theory about why this question keeps popping up, so I'll try to answer according to that; I hope it helps.

Containers of single values

You can think of a functor (and therefore also a monad) as a container of values. This is most palpable with the (redundant) Identity functor:

Prelude Control.Monad.Identity> Identity 42
Identity 42

This is nothing but a wrapper around a value, in this case 42. For this particular container, you can extract the value, because it's guaranteed to be there:

Prelude Control.Monad.Identity> runIdentity $ Identity 42
42

While Identity seems fairly useless, you can find other functors that seem to wrap a single value. In F#, for example, you'll often encounter containers like Async<'a> or Lazy<'a>, which are used to represent asynchronous or lazy computations (Haskell doesn't need the latter, because it's lazy by default).

You'll find lots of other single-value containers in Haskell, such as Sum, Product, Last, First, Max, Min, etc. Common to all of those is that they wrap a single value, which means that you can extract the value.

I think that when people first encounter functors and monads, they tend to think of the concept of a data container in this way: as a container of a single value.

Containers of optional values

Unfortunately, some common monads in Haskell seem to support that idea. For example, Maybe is a data container as well, but one that can contain zero or one value. You can, unfortunately, still extract the value if it's there:

Prelude Data.Maybe> fromJust $ Just 42
42

The problem with this is that fromJust isn't total, so it'll crash if you call it with a Nothing value:

Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing

You can see the same sort of problem with Either. Although I'm not aware of a built-in partial function to extract a Right value, you can easily write one with pattern matching (if you ignore the compiler warning):

extractRight :: Either l r -> r
extractRight (Right x) = x

Again, it works in the 'happy path' scenario, but can just as easily crash:

Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight

Still, since functions like fromJust exists, I suppose it tricks people new to the concept of functors and monads into thinking about them as data containers from which you can extract a value.

When you encounter something like IO Int for the first time, then, I can understand why you'd be tempted to think of it as a container of a single value. In a sense, it is, but in another sense, it isn't.

Containers of multiple values

Even with lists, you can (attempt to) extract 'the' value from a list:

Prelude> head [42..1337]
42

Still, it could fail:

Prelude> head []
*** Exception: Prelude.head: empty list

At this point, however, it should be clear that attempting to extract 'the' value from any arbitrary functor is nonsense. A list is a functor, but it contains an arbitrary number of values, including zero and infinitely many.

What you can always do, though, is to write functions that take a 'contained' value as input and returns another value as output. Here's an arbitrary example of such a function:

countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor

While you can't 'extract the value' out of a list, you can apply your function to each of the values in a list:

Prelude> fmap countAndMultiply [("foo", 2), ("bar", 3), ("corge", 2)]
[6,9,10]

Since IO is a functor, you can do the same with it as well:

Prelude> foo = return ("foo", 2) :: IO (String, Int)
Prelude> :t foo
foo :: IO (String, Int)
Prelude> fmap countAndMultiply foo
6

The point is that you don't extract a value from a functor, you step into the functor.

Monad

Sometimes, the function you apply to a functor returns a value that's already wrapped in the same data container. As an example, you may have a function that splits a string over a particular character. To keep things simple, let's just look at the built-in function words that splits a string into words:

Prelude> words "foo bar"
["foo","bar"]

If you have a list of strings, and apply words to each, you'll get a nested list:

Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]

The result is a nested data container, in this case a list of lists. You can flatten it with join:

Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]

This is the original definition of a monad: it's a functor that you can flatten. In modern Haskell, Monad is defined by bind (>>=), from which one can derive join, but it's also possible to derive >>= from join.

IO as all values

At this point, you may be wondering: what does that have to do with IO? Isn't IO a a container of a single value of the type a?

Not really. One interpretation of IO is that it's a container that holds an arbitrary value of the type a. According to that interpretation, it's analogous to the many-worlds interpretation of quantum mechanics. IO a is the superposition of all possible values of the type a.

In Schrödinger's original thought experiment, the cat in the box is both alive and dead until observed. That's two possible states superimposed. If we think about a variable called catIsAlive, it would be equivalent to the superposition of True and False. So, you can think of IO Bool as a set of possible values {True, False} that will only collapse into a single value when observed.

Likewise, IO Word8 can be interpreted as a superposition of the set of all possible Word8 values, i.e. {0, 1, 2,.. 255}, IO Int as the superposition of all possible Int values, IO String as all possible String values (i.e. an infinite set), and so on.

So how do you observe the value, then?

You don't extract it, you work within the data container. You can, as shown above, fmap and join over it. So, you can write your application as pure functions that you then compose with impure values with fmap, >>=, join, and so on.

like image 85
Mark Seemann Avatar answered Dec 05 '22 04:12

Mark Seemann


It is trivial, so this will be a long answer. In short, the problem lies in the signature, IO a -> a, is not a type properly allowed in Haskell. This really has less to do with IO being a functor than the fact that IO is special.

For some functors you can recover the pure value. For instance a partially applied pair, (,) a, is a functor. We unwrap the value via snd.

snd :: (a,b) -> b
snd (_,b) = b

So this is a functor that we can unwrap to a pure value, but this really has nothing to do with being a functor. It has more to do with pairs belonging to a different Category Theoretic concept, Comonad, with:

extract :: Comonad w => w a -> a

Any Comonad will be a functor for which you can recover the pure value.

Many (non-comonadic) functors have--lets say "evaluators"--which allow something like what is being asked. For instance, we can evaluate a Maybe with maybe :: a -> Maybe a -> a. By providing a default, maybe a has the desired type, Maybe a -> a. Another useful example from State, evalState :: State s a -> s -> a, has its arguments reversed but the concept is the same; given the monad, State s a, and initial state, s, we unwrap the pure value, a.

Finally to the specifics of IO. No "evaluator" for IO is provided in the Haskell language or libraries. We might consider running the program itself an evaluator--much in the same vein of evalState. But if that's a valid conceptual move, then it should only help to convince you that there is no sane way to unwrap from IO--any program written is just the IO a input to its evaluator function.

Instead, what you are forced to do--by design--is to work within the IO monad. For instance, if you have a pure function, f :: a -> b, you apply it within the IO context via, fmap f :: IO a -> IO b

TL;DR You can't get a pure value out of the IO monad. Apply pure functions within the IO context, for instance by fmap

like image 37
trevor cook Avatar answered Dec 05 '22 05:12

trevor cook