piggyback
piggyback

Reputation: 9254

Keep variable inside another function in Haskell

I am lost in this concept. This is my code and what it does is just asking what is your name.

askName = do
  name <- getLine
  return ()

main :: IO ()
main = do
       putStrLn "Greetings once again.  What is your name?"
       askName

However, How can I access in my main the variable name taken in askName?

This is my second attempt:

askUserName = do
  putStrLn "Hello, what's your name?"  
  name <- getLine  
  return name

sayHelloUser name = do
  putStrLn ("Hey " ++ name ++ ", you rock!")

main = do
  askUserName >>=  sayHelloUser

I can now re-use the name in a callback way, however if in main I want to call name again, how can I do that? (avoiding to put name <- getLine in the main, obviously)

Upvotes: 1

Views: 894

Answers (2)

zurgl
zurgl

Reputation: 1930

We can imagine that you ask the name in order to print it, then let's rewrite it.
In pseudo code, we have

main =   
    print "what is your name"  
    bind varname with userReponse    
    print varname  

Your question then concern the second instruction.
Take a look about the semantic of this one.

  1. userReponse is a function which return the user input (getLine)
  2. varname is a var
  3. bind var with fun : is a function which associate a var(varname) to the output of a function(getLine)

Or as you know in haskell everything is a function then our semantic is not well suited.
We need to revisit it in order to respect this idiom. According to the later reflexion the semantic of our bind function become bind fun with fun

As we cannot have variable, to pass argument to a function, we need, at a first glance, to call another function, in order to produce them. Thus we need a way to chain two functions, and it's exactly what's bind is supposed to do. Furthermore, as our example suggest, an evaluation order should be respected and this lead us to the following rewriting with fun bind fun

That's suggest that bind is more that a function it's an operator.
Then for all function f and g we have with f bind g.
In haskell we note this as follow

f >>= g  

Furthermore, as we know that a function take 0, 1 or more argument and return 0, 1 or more argument.
We could refine our definition of our bind operator.
In fact when f doesn't return any result we note >>= as >>
Applying, theses reflexions to our pseudo code lead us to

main = print "what's your name" >> getLine >>= print 

Wait a minute, How the bind operator differ from the dot operator use for the composition of two function ?

It's differ a lot, because we have omit an important information, bind doesn't chain two function but it's chain two computations unit. And that's the whole point to understand why we have define this operator.

Let's write down a global computation as a sequence of computation unit.

f0 >>= f1 >> f2 >> f3 ... >>= fn

As this stage a global computation could be define as a set of computation unit with two operator >>=, >>.

How do we represent set in computer science ?
Usually as container.

Then a global computation is a container which contain some computation unit. On this container we could define some operator allowing us to move from a computation unit to the next one, taking into account or not the result of the later, this is ours >>= and >> operator.

As it's a container we need a way to inject value into it, this is done by the return function. Which take an object and inject it into a computation, you could check it through is signature.

return :: a -> m a -- m symbolize the container, then the global computation

As it's a computation we need a way to manage a failure, this done by the fail function.
In fact the interface of a computation is define by a class

class Computation  
    return  -- inject an objet into a computation 
    >>=     -- chain two computation
    >>      -- chain two computation, omitting the result of the first one
    fail    -- manage a computation failure  

Now we can refine our code as follow

main :: IO ()
main = return "What's your name" >>= print >> getLine >>= print 

Here I have intentionally include the signature of the main function, to express the fact that we are in the global IO computation and the resulting output with be () (as an exercise enter $ :t print in ghci).
If we take more focus on the definition for >>=, we can emerge the following syntax

f >>= g <=> f >>= (\x -> g)  and f >> g <=> f >>= (\_ -> g)  

And then write

main :: IO ()
main =
    return "what's your name" >>= \x ->
    print x                   >>= \_ -> 
    getLine                   >>= \x -> 
    print x 

As you should suspect, we certainly have a special syntax to deal with bind operator in computational environment. You're right this is the purpose of do syntax
Then our previous code become, with do syntax

main :: IO ()
main = do
    x <- return "what's your name"
    _ <- print x  
    x <- getLine              
    print x     

If you want to know more take a look on monad


As mentioned by leftaroundabout, my initial conclusion was a bit too enthusiastic

You should be shocked, because we have break referential transparency law (x take two different value inside our sequence of instruction), but it doesn't matter anymore,because we are into a computation, and a computation as defined later is a container from which we can derive an interface and this interface is designed to manage, as explain, the impure world which correspond to the real world.

Upvotes: 6

hugomg
hugomg

Reputation: 69934

Return the name from askname. In Haskell its not idiomatic to access "global" variables:

askName :: IO String
askName = do
  name <- getLine
  return name

main :: IO ()
main = do
       putStrLn "Greetings once again.  What is your name?"
       name <- askName
       putStrLn name

Now the only problem is tht the askName function is kind of pointless, since its now just an alias to getLine. We could "fix" that by putting the question inside askName:

askName :: IO String
askName = do
  putStrLn "Greetings once again.  What is your name?"
  name <- getLine
  return name

main :: IO ()
main = do
       name <- askName
       putStrLn name

Finally, just two little points: its usually a good idea to put type declarations when you are learning, to make things explicit and help compiler error messages. Another thing is to remember that the "return" function is only used for monadic code (it is not analogous to a traditional return statement!) and sometimes we could have ommited some intermediary variables:

askName :: IO String
askName = do
  putStrLn "Greetings once again.  What is your name?"
  getLine

Upvotes: 3

Related Questions