How can I get this currying experiment to behave as expected?

114 views Asked by At

I am writing a currying experiment to get a feel for how multiple statements in haskell are chained together to work one after another.

Here is what I got so far

testCurry :: IO ()
testCurry =     
    (\b ->
        (\_ -> putStrLn b)
        ((\a -> 
            putStrLn a
        ) (show 2))
    ) (show 3)

testCurryExpected :: IO ()
testCurryExpected = do {
    a <- return (show 2);
    putStrLn a;
    b <- return (show 3);
    putStrLn b;
}

main :: IO ()
main = 
    putStrLn "expected: " >>
    testCurryExpected >>
    putStrLn "given: " >>
    testCurry

I know it works if I do it this way:

testCurry :: IO ()
testCurry =     
    (\b ->
        (\next -> next >> putStrLn b)
        ((\a -> 
            putStrLn a
        ) (show 2))
    ) (show 3)

testCurryExpected :: IO ()
testCurryExpected = do {
    a <- return (show 2);
    putStrLn a;
    b <- return (show 3);
    putStrLn b;
}

main :: IO ()
main = 
    putStrLn "expected: " >>
    testCurryExpected >>
    putStrLn "given: " >>
    testCurry

But I don't know how to simulate the ">>"(then) behavior only using functions.

I know a >> b is defined in terms of a >>= \_ -> b, but I am not sure how >>= is defined in terms of IO a >>= IO b but don't know how to translate this into raw function composition.

Can somebody please help me get this experiment to work?

In short, I want to know if there is a way to do this without >> or >>= operators nor wrapping these operators.

(\a -> \b -> a >> b)(putStrLn "one")(putStrLn "two")

Note: For the sake of concept, I restrict myself to using anonymous functions of at most one argument.

Edit: I found a good-enough solution by creating my own Free representation of putStrLn called Free_PutStrLn that is free of interpretation; using Lists to construct the operation chain, then evaluate it myself later.

data Free_PutStrLn = Free_PutStrLn String deriving Show

eval :: [Free_PutStrLn] -> IO ()
eval a = 
    foldl (\a -> \b -> 
        let deconstruct (Free_PutStrLn str) = str in
        a >> putStrLn (deconstruct b)
    ) (return ()) a

testCurry :: [Free_PutStrLn]
testCurry = 
    (\a ->
        [Free_PutStrLn a] ++
        ((\b ->
            [Free_PutStrLn b]
        ) (show 3))
    )(show 2)

main = 
    putStrLn (show testCurry) >>
    eval (testCurry)

JavaScript proof of concept:

// | suspends an object within a function context.
// | first argument is the object to suspend.
// | second argument is the function object into which to feed the suspended
// |     argument(first).
// | third argument is where to feed the result of the first argument feeded into 
// |     second argument. use a => a as identity.
const pure = obj => fn => f => f(fn(obj));

// | the experiment
pure({'console': {
    'log': str => new function log() {
        this.str = str;
    }
}})(free =>
    pure(str => () => console.log(str))
    (putStrLn => 
        pure("hello")(a => 
            [free.console.log(a)].concat (
            pure("world")(b => 
                [free.console.log(b)]
            )(a => a))
        )((result => 
            pure(logObj => logObj.str)
            (deconstruct => 
                result.map(str => putStrLn(deconstruct(str)))
            )(result =>
                result.forEach(f => f())
            )        
        )
    )
    )(a => a)
)(a => a)
1

There are 1 answers

11
leftaroundabout On

But I don't know how to simulate the >> (then) behavior only using functions.

Well, you can't! >> is (in this case) about ordering side-effects. A Haskell function can never have a side effect. Side effects can only happen in monadic actions, and can be thus ordered by monadic combinators including >>, but without a Monad constraint the notion of “do this and also that” simply doesn't make any sense in Haskell. A Haskell function is not executed, it's merely a mathematical transformation whose result you may evaluate. That result may itself be an actual action with type e.g. IO (), and such an action can be executed and/or monadically chained with other actions. But this is actually somewhat orthogonal to the evaluation of the function that yielded this action.

So that's the answer: “How can I get this currying experiment to behave as expected?” You can't, you need to use one of the monadic combinators instead (or do notation, which is just syntactic sugar for the same).


To also tackle this question from a bit of a different angle: you do not “need monads” to express sequencing of side effects. I might for instance define a type that “specifies side-effects” by generating e.g. Python code which when executed has these effects:

newtype Effect = Effect { pythons :: [String] }

Here, you could then sequence effects by simply concatenating the instruction lists. Again though, this sequencing would not be accomplished by any kind of currying exercise but by boring list concatenation. The preferable interface for this is the monoid class:

import Data.Monoid
instance Monoid Effect where
  mempty = Effect []
  mappend (Effect e₀) (Effect e₁) = Effect $ e₀ ++ e₁

And then you could simply do:

hello :: Effect
hello = Effect ["print('hello')"] <> Effect ["print('world')"]

(<> is just a shorthand synonym for mappend. You could as well define a custom operator, say # instead to chain those actions, but if there's a standard class that supports some operation it's usually a good idea to employ that!)

Ok, perfectly fine sequencing, no monadic operators required.

But very clearly, just evaluating hello would not cause anything to be printed: it would merely give you some other source code. You'd actually need to feed these instructions to a Python interpreter to accomplish the side-effects.
And in principle that's no different with the IO type: evaluating an IO action also never causes any side-effects, only linking it to main (or to the GHCi repl) does. How many lambdas you wrap the subexpressions in is completely irrelevant for this, because side-effect occurance has nothing to do with whether a function gets called anywhere! It only has to do with how the actions get linked to an actual “executor”, be that a Python interpreter or Haskell's own main.

If you now wonder why it has to be those whacky monads if the simpler Monoid also does the trick... the problem with Effect is that it has no such thing as a return value. You can perfectly well generate “pure output” actions this way that simply execute a predetermined Python program, but you can never get back any values from Python this way to use within Haskell to decide what should happen next. This is what monads allow you to do.


Yes, never. Haskell does not include something called unsafePerformIO. Anybody who claims otherwise in the comments shall suffer nuclear retaliation.

To be precise, the weaker Applicative is sufficient.