letSeveral related things really bothered me about Haskell's symmetry
when I began learning Haskell, so I'd like to give other novices a heads up.
This brief post explains the essentials, including let.
First, a straightforward program:
intro txt =
let
welcome = "Welcome! Today we will "
in
welcome ++ txt
main =
let
plan = "talk about symmetry!"
in
do
let hello = intro plan
putStrLn hello
It can be saved in a file, hello.hs, and run like so:
$ runhaskell hello.hs Welcome! Today we will talk about symmetry!
The first thing you notice about the let keywords,
especially if you have worked with F#, first, is
that there are none at the top level!
This is not because Haskell is a hideously asymmetric language: it is because
this symmetry is simply implied.
Each top level function declaration looks, actually, the same way it would if
all of them were wrapped in let and in keywords,
with main called below that. Imagine it was like this:
let intro txt = let welcome = "Welcome! Today we will " in welcome ++ txt main = let plan = "talk about symmetry!" in do let hello = intro plan putStrLn hello in main
The second thing you'll notice about these let keywords
is that after the let hello = ... line, there is no matching
in keyword!
You may pause a moment to ask whether these pesky in keywords
are even needed at all.
In fact, you will rush to test this, by launching the REPL, ghci:
$ ghci Prelude> let hello = "look at me swimming!" Prelude> putStrLn hello look at me swimming!
The glow lasts for a moment, and then you try removing the in after the definitions
of welcome and plan and you find rejection!!
A parse error?
$ runhaskell hello.hs hello.hs:7:1: parse error (possibly incorrect indentation)
The secret is two-fold. First, the let hello = ... is special,
because it is in a do block. (Later, you'll learn how this special
do block sub-language works.) Second, and most shockingly, to me:
ghci provides a REPL wrapped in an implicit do.
To make it easy to copy and paste lines, employing this feature, you can shuffle things around in the source file, like so:
main =
do
let intro txt = "Welcome! Today we will " ++ txt
let plan = "talk about symmetry!"
let hello = intro plan
putStrLn hello
Then you can run it as before, or copy-paste only a portion into ghci, like this:
$ ghci Prelude> let intro txt = "Welcome! Today we will " ++ txt Prelude> let plan = "talk about symmetry!" Prelude> let hello = intro plan Prelude> putStrLn hello Welcome! Today we will talk about symmetry!
There is, however, a big shortcut. To load all the code into ghci
without even stressing at all about copy-paste errors, simply load the file itself
using a ghci command.
The :load command lets us load a file or (module) into the runtime,
so we can then call its functions. (And if we had command line arguments to
the program, we can even use the :main command, with colon,
to test them.
$ ghci Prelude> :load hello.hs *Main> intro "play with hula hoops." "Welcome! Today we will play with hula hoops."
Cheers,
Kevin