Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there ways to call two functions (one just after another) in purely functional language? (in non-io mode)

I'm trying to understand order of execution in purely functional language.

I know that in purely functional languages, there is no necessary execution order.

So my question is:

Suppose there are two functions. I would like to know all ways in which I can call one function after another (except nested call of one function from another) (and except io-mode).

I would like to see examples in Haskell or pseudo-code.

like image 904
uintptr_t Avatar asked Apr 09 '14 13:04

uintptr_t


1 Answers

There is no way to do what you describe, if the functions are totally independent and you don't use the result of one when you call the other.

This is because there is no reason to do this. In a side effect free setting, calling a function and then ignoring its result is exactly the same as doing nothing for the amount of time it takes to call that function (setting aside memory usage).

It is possible that seq x y will evaluate x and then y, and then give you y as its result, but this evaluation order isn't guaranteed.

Now, if we do have side effects, such as if we are working inside a Monad or Applicative, this could be useful, but we aren't truly ignoring the result since there is context being passed implicitly. For instance, you can do

main :: IO ()
main = putStrLn "Hello, " >> putStrLn "world"

in the IO Monad. Another example would be the list Monad (which could be thought of as representing a nondeterministic computation):

biggerThanTen :: Int -> Bool
biggerThanTen n = n > 10

example :: String
example = filter biggerThanTen [1..15] >> return 'a'  -- This evaluates to "aaaaa"

Note that even here we aren't really ignoring the result. We ignore the specific values, but we use the structure of the result (in the second example, the structure would be the fact that the resulting list from filter biggerThanTen [1..15] has 5 elements).

I should point out, though, that things that are sequenced in this way aren't necessarily evaluated in the order that they are written. You can sort of see this with the list Monad example. This becomes more apparent with bigger examples though:

example2 :: [Int]
example2 =
  [1,2,3] >>=
    (\x -> [10,100,1000] >>=
             (\y -> return (x * y)))   --  ==> [10,100,1000,20,200,2000,30,300,3000]

The main takeaway here is that evaluation order (in the absence of side effects like IO and ignoring bottoms) doesn't affect the ultimate meaning of code in Haskell (other than possible differences in efficiency, but that is another topic). As a result, there is never a reason to call two functions "one after another" in the fashion described in the question (that is, where the calls are totally independent from each other).

Do notation

Do notation is actually exactly equivalent to using >>= and >> (there is actually one other thing involved that takes care of pattern match failures, but that is irrelevant to the discussion at hand). The compiler actually takes things written in do notation and converts them to >>= and >> through a process called "desugaring" (since it removes the syntactic sugar). Here are the three examples from above written with do notation:

IO Example

main :: IO ()
main = do
  putStrLn "Hello, "
  putStrLn "World"

First list example

biggerThanTen :: Int -> Bool
biggerThanTen n = n > 10

example :: String -- String is a synonym for [Char], by the way
example = do 
  filter biggerThanTen [1..15]
  return 'a'

Second list example

example2 :: [Int]
example2 = do
  x <- [1,2,3]
  y <- [10,100,1000]
  return (x * y)

Here is a side-by-side comparison of the conversions:

do          --
  m         --  m >> n
  n         --


do          --
  x <- m    -- m >>= (\x ->
  ...       --           ...)

The best way to understand do notation is to first understand >>= and return since, as I said, that's what the compiler transforms do notation into.

As a side-note, >> is just the same as >>=, it just ignores the "result" of it's left argument (although it preserves the "context" or "structure"). So all definitions of >> must be equivalent to m >> n = m >>= (\_ -> n).

Expanding the >>= in the second list example

To help drive home the point that Monads are not usually impure, lets expand the >>= calls in the second list example, using the Monad definition for lists. The definition is:

instance Monad [] where
  return x = [x]
  xs >>= f = concatMap f xs

and we can convert example2 into:

Step 0 (what we already have)

example2 :: [Int]
example2 =
  [1,2,3] >>=
    (\x -> [10,100,1000] >>=
             (\y -> return (x * y)))

Step 1 (converting the first >>=)

example2 =
  concatMap
    (\x -> [10,100,1000] >>=
              (\y -> return (x * y)))
    [1,2,3]

Step 2

example2 =
  concatMap
    (\x -> concatMap
             (\y -> return (x * y))
             [10,100,1000])
    [1,2,3]

Step 3

example2 =
  concatMap
    (\x -> concatMap
             (\y -> [x * y])
             [10,100,1000])
    [1,2,3]

So, there is no magic going on here, just normal function calls.

like image 114
David Young Avatar answered Oct 17 '22 04:10

David Young