Question: Function application and composition versus bind (>>=)
Answer: For the following discussion, a, b, and c are built in Haskell data types.
We use normal function composition when we compose a function a -> b with a function b -> c. The result is a function with a type signature of a -> c and with an output of type c.
We use bind when we compose a function a -> M b with a function b -> M c. The result is a function a -> M c. M is a Monad and bind is a function that a Monad has to have and that instructs on how such a composition is to be made. Someone has to provide the code for bind. The type signature for bind is M a -> (a -> M b) -> M b. Since a, b, and c are arbitrary types, this can be written equivalently as M b -> (b -> M c) -> M c. Thus we see that bind takes the output of a function a -> M b and a function b -> M c and composes them producing the output M c.
Question: let versus <-
Answer: First of all, let and <- are not interchangeable; they perform differently.
The <- statement has to have a Monad on its right side and it will extract the value from that Monad.
The let statement is just assignment. It does not extract anything from its right side and its right side can have any type (even a Monad).
Here is a program that demonstrates the use of the <- operator:
module Main where import Data.Typeable myFunction :: String -> IO Int myFunction xs = do putStrLn "Inside the function that returns the length of a string plus 10." return ((length xs) + 10) main :: IO () main = do putStrLn "Program begins." a <- myFunction "Dimitrios Kalemis" print a print (typeOf a) print (typeOf myFunction) putStrLn "Program ends."
The <- statement is syntactic sugar used in the do notation. The following two programs are equivalent:
module Main where main :: IO () main = do nameVariable <- getLine putStrLn ("You typed: " ++ nameVariable)
module Main where main :: IO () main = getLine >>= \nameVariable -> putStrLn ("You typed: " ++ nameVariable)
The StackOverflow entry “<-” bindings in do notation has excellent answers concerning this matter.
Question: data versus type versus newtype
Answer: The data declaration is used for the creation of new types.
The type declaration is used for the creation of type synonyms for existing types.
The newtype declaration is almost like the data declaration. The newtype declaration and the data declaration have some differences.
The underlying reason for these differences is that types declared with the data keyword are lifted – that is, they contain their own bottom (⊥) value that is distinct from all the others. (The term bottom refers to a computation which never completes successfully.) Thus, Haskell provides the newtype keyword, for the construction of unlifted types.
This fact leads to the following two differences:
- Although you can replace the newtype keyword with the data keyword, the converse is not true: the data keyword can only be replaced with newtype if the type has exactly one value constructor with exactly one field inside it.
- The value constructor introduced by data is lazy (thus carries more overhead) than the value constructor introduced by newtype which is strict (thus faster).
For more information I recommend the following two sources:
More insight on this issue can be found in John Hughes’ excellent paper “Generalising monads to arrows”. In paragraph 2.2, John Hughes describes the State Monad. He writes:
In this case we represent a computation with result type a and a state of type s by a value of the type
newtype StateMonad s a = SM (s -> (a,s))
For the newtype declaration, John Hughes provides following footnote:
The Haskell newtype declaration introduces a new type isomorphic to an existing one, where the constructor names the isomorphism. Its purpose is to enable us to define overloaded operations which behave differently on the new type and the old. In this case, we will define the monad operations for StateMonad quite independently of any definition that may be given for functions. The difference between a newtype and data declaration is subtle, but quite important for efficiency: at run-time, a value of a type defined by newtype is represented in exactly the same way as the type it is isomorphic to – the constructor is not represented at all – whereas the constructors of a data type are always represented explicitly.