Elementary study of statement sequence in Haskell

In this blog post, I would like to study how statements are sequenced in Haskell. Actually, in Haskell, if you want to provide a sequence of statements, you would use the do notation, which is syntactic sugar for bind. What this means is, that Haskell uses monadic notation and approach (bind) to provide a sequence of statements, and this approach is easier to create (syntactic sugar) if you use the do notation.

I already went straight to the “monadic” approach. But what if we have a pure virtual function? Why do we have to use a monadic approach to provide a sequence of statements? Are all pure functions made of a single statement (for each guard, if you have them or for each mention of the function name on the left)? What if you want to provide an analytic approach to how things are done in a pure function? How do you create a sequence of statements?

I am going to present some examples that will help clarify the previous points. But just to be clear from the start, I have never seen a pure function in Haskell that has more than one statement, nor do I know how to make a pure function with more than one statement. Sure, a function can have guards or a deeply nested if statement or its name mentioned more than one time on the left side of its definition. But apart from that, a pure function can have only one statement. Then, how can we provide an analytic version of it? Is a monadic approach our only way? The answer is, surprisingly, “no”. We can have an analytic version and still avoid monads or the do notation. Let me explain.

All right, our first example is the following:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs = xs

We are going to study the function called myFunction. We are going to leave the main program to be monadic and use the do notation. In each subsequent program, we are going to change myFunction, in order to eventually give answers to all the questions posed earlier in this blog post.

So, in this first program, myFunction takes a list and returns the same list without any modifications. myFunction is pure and it has only one statement (xs = xs).

In the second program, myFunction modifies the list passed to it. It adds the element 5. Still myFunction is pure and it has only one statement (xs = xs ++ [5]).

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs = xs ++ [5]

Here is the third program:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs = [123] ++ xs

In this program, myFunction insert element 123 in the beginning of the list. Still myFunction is pure and it has only one statement (xs = [123] ++ xs).

Right. Let us now take things a step further. This is the fourth program:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs = [300] ++ [200] ++ [100] ++ xs ++ [400] ++ [500] ++ [600]

In this program, myFunction inserts 6 elements in the list. And it does it all in one statement (xs = [300] ++ [200] ++ [100] ++ xs ++ [400] ++ [500] ++ [600]). So, still myFunction is pure and it has only one statement.

Now, we can see that this statement does six different things all at once: it inserts element 100 in the beginning of the list, it then inserts element 200 in the beginning of the list, it then inserts element 300 in the beginning of the list, it then appends element 400 at the end of the list, it then appends element 500 at the end of the list and at last, it appends element 600 at the end of the list. Actually, the first three actions should be done sequentially, the last three actions should be done sequentially,  but between these two batches, sequence does not matter. Operations at the beginning of the list can precede, succeed or be interspersed with operations at the end of the list.

Anyway, we have one statement in myFunction that behaves really well and does the job perfectly. At then end, it produces the list  [300,200,100,1,2,3,4,400,500,600], if it is given the list [1,2,3,4].

Now suppose someone comes and tell us that she finds this statement complex. Let us suppose that she wants this operation to be presented analytically. After all, it comprises of six different actions. Now, how can you argue with that 🙂

OK, let us see how this can be done. The fifth program follows:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs =
   let
      as = [100] ++ xs
      bs = [200] ++ as
      cs = [300] ++ bs
      ds = cs ++ [400]
      es = ds ++ [500]
   in
      es ++ [600]

Right. Here, myFunction presents the steps with great detail. But I would say that myFunction still has only statement (xs = es ++ [600]). The other five statements are “helper statements”.  But here is the real kicker: these five statements in the “let block” can appear in whatever order we wish! You read that right. Try it. The order of the five statements in the “let block” does not matter. The Haskell compiler, being the highly advanced software that it is, can “sort through” the “helper statements” and put them in their “correct” order. (It is like what happens  for the “helper statements” in list comprehensions. There, too, the Haskell compiler makes sense out of them, no matter what order they are presented.)

So, still myFunction is pure (non monadic) and it has only one statement.

There is an equivalent way to write the previous program. Here is the sixth program:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      let myList = myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> [Integer]
myFunction xs =
   es ++ [600]
      where
         as = [100] ++ xs
         bs = [200] ++ as
         cs = [300] ++ bs
         ds = cs ++ [400]
         es = ds ++ [500]

Here the “helper statements” appear in the “where block” and again, they can appear in whatever order we wish! Try it! Again, I would say that myFunction has only one statement (xs = es ++ [600]). And still, myFunction is pure (non monadic).

Great. So far, so good. We have seen how we can take a function and present its operations in a more analytic way, while still preserving its pureness. But let us say that we want to do away with purity and handle things the monadic way.  Well, a psychiatrist might consider this to be a perversion, but I am not a psychiatrist. Let’s rock and roll ourselves in mud! This is the seventh program:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      myList <- myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> IO [Integer]
myFunction xs =
   do
      let
         as = [100] ++ xs
         bs = [200] ++ as
         cs = [300] ++ bs
         ds = cs ++ [400]
         es = ds ++ [500]
      return (es ++ [600])

OK, we have a lot to say about this seventh program. To begin with, the statements in the “let block” can appear in any order. Also, myFunction is not pure; it is monadic. It uses the do notation and the return statement. And while it uses only one statement (return (es ++ [600])), when we use the do notation we can put as many statements in sequence as we want. This is what we were seeing all along in the main program.

So, in this seventh program, we use a myFunction which is monadic and we have to denote that to the Haskell compiler, in order to compile it. This is why we have the IO monad in the declaration of the output myFunction. Even though myFunction does not do IO, it uses the do notation and the return statement, and the Haskell compiler has to be warned about this.

But the biggest change is in the main program. Now that myFunction is monadic, we have to use the <- operator to pass its result to myList. While we used the statement let myList = myFunction [1,2,3,4] in the previous programs, here we use the statement myList <- myFunction [1,2,3,4].

And somewhere around here, my analysis is finished. I would just like to leave you with a bonus: Remember the first program? Here is how you would write it with a monadic myFunction:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      myList <- myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> IO [Integer]
myFunction xs = return xs

Also, the fourth program, written with a monadic myFunction, would be as follows:

module Main where

main :: IO ()
main  =
   do
      putStrLn "Program begins"

      myList <- myFunction [1,2,3,4]
      print myList

      putStrLn "Program ends"

myFunction   :: [Integer] -> IO [Integer]
myFunction xs = return ([300] ++ [200] ++ [100] ++ xs ++ [400] ++ [500] ++ [600])

Again, in the last three programs, please notice the <- operator in the main program. It is needed to get the output value from the monadic myFunction.

Advertisements

About Dimitrios Kalemis

I am a systems engineer specializing in Microsoft products and technologies. I am also an author. Please visit my blog to see the blog posts I have written, the books I have written and the applications I have created. I definitely recommend my blog posts under the category "Management", all my books and all my applications. I believe that you will find them interesting and useful. I am in the process of writing more blog posts and books, so please visit my blog from time to time to see what I come up with next. I am also active on other sites; links to those you can find in the "About me" page of my blog.
This entry was posted in Development. Bookmark the permalink.