Functional programming in Python

Manuel M. T. Chakravarty chak@cse.unsw.edu.au
Tue, 15 May 2001 15:46:23 +1000


brk@jenkon.com wrote,

> > From:	Manuel M. T. Chakravarty [SMTP:chak@cse.unsw.edu.au]
> >  Absolutely.  In fact, you have just pointed out one of the
> > gripes that I have with most Haskell texts and courses.  The
> > shunning of I/O in textbooks is promoting the image of
> > Haskell as a purely academic exercise.  Something which is
> > not necessary at all, I am teaching an introductory course
> > with Haskell myself and did I/O in Week 5 out of 14 (these
> > are students without any previous programming experience).
> > Moreover, IIRC Paul Hudak's book <http://haskell.org/soe/>
> > also introduces I/O early.
> > 
> > In other words, I believe that this a problem with the
> > presentation of Haskell and not with Haskell itself.
>
> 	Since my first mesage and your and Simon Peyton-Jones' response,
> I've taken a little more time to work with Haskell, re-read Tackling the
> Awkward squad, and browsed the source for Simon Marlow's web server, and
> it's starting to feel more comfortable now. In the paper and in the server
> souce, there is certainly a fair amount of IO work happening, and it all
> looks fairly natural and intuitive. 
> 
> 	Mostly I find when I try to write code following those examples (or
> so I think!), it turns out to be not so easy, and the real difficulty is
> that I can't even put my finger on why it's troublesome. I try many
> variations on a theme - some work, some fail, and often I can't see why. I
> should have kept all the versions of my program that failed for reasons I
> didn't understand, but unfortunately I didn't... The only concrete example
> of something that confuses me I can recall is the fact that this compiles:
> 
> 	main = do allLines <- readLines; putStr $ unlines allLines
> 	    where readLines = do
> 	            eof <- isEOF
> 	            if eof then return [] else
> 	                do
> 	                    line <- getLine
> 			    allLines <- readLines
> 	                    return (line : allLines)
> 
> 	but this doesn't:
> 
> 	main = do putStr $ unlines readLines
> 	    where readLines = do
> 	            eof <- isEOF
> 	            if eof then return [] else
> 	                do
> 	                    line <- getLine
> 			    allLines <- readLines
> 	                    return (line : allLines)
> 
> 	Evidently this is wrong, but my intuition is that <- simply binds a
> name to a value, and that:

No, that is not the case.  It does more, it executes an I/O action.

> 	foo <- somefunc
> 	bar foo
> 
> 	should be identical to:
> 	
> 	bar somefunc

But it isn't; however, we have 

  do
    let foo = somefunc
    bar foo

is identical to

  do
    bar somefunc

So, this all boils down to the question, what is the
difference between

  do
    let foo = somefunc	-- Version 1
    bar foo

and

  do
    foo <- somefunc     -- Version 2
    bar foo

The short answer is that Version 2 (the arrow) executes any
side effects encoded in `somefunc', whereas Version 1 (the
let binding) doesn't do that.  Expressions given as an
argument to a function behave as if they were let bound, ie,
they don't execute any side effects.  This explains why the
identity that you stated above does not hold.

So, at the core is that Haskell insists on distinguishing
expressions that can have side effects from those that
cannot.  This distinction makes the language a little bit
more complicated (eg, by enforcing us to distinguish between
`=' and `<-'), but it also has the benefit that both a
programmer and the compiler can immediately tell which
expressions do have side effects and which don't.  For
example, this often makes it a lot easier to alter code
written by somebody else.  It also makes it easier to
formally reason about code and it gives the compiler scope
for rather radical optimisations.

To reinforce the distinction, consider the following two
pieces of code (where `readLines' is the routine you defined
above):

  do
    let x = readLines
    y <- x
    z <- x
    return (y ++ z)

and

  do
    x <- readLines
    let y = x
    let z = x
    return (y ++ z)

How is the result (and I/O behaviour) different?

> 	That was one difficulty. Another was trying to figure out what the $
> sign was for. Finally I realized it was an alternative to parentheses,
> necessary due to the extremely high precedence of function application in
> Haskell. That high precedence is also disorienting, by the way. What's the
> rationale behind it?

You want to be able to write

  f 1 2 + g 3 4

instead of

  (f 1 2) + (g 3 4)

> 	p.s. What data have your students' reactions given you about what is
> and is not difficult for beginners to grasp?

They found it to be a difficult topic, but they found
"Unix/Shell scripts" even harder (and we did only simple
shell scripts).  I actually made another interesting
observation (and keep in mind that for many that was their
first contact with programming).  I had prepared for the
distinction between side effecting and non-side-effecting
expressions to be a hurdle in understanding I/O.  What I
hand't taken into account was that the fact that they had
only worked in an interactive interpreter environment (as
opposed to, possibly compiled, standalone code) would pose
them a problem.  The interactive interpreter had allowed
them to type in input and get results printed all way long,
so they didn't see why it should be necessary to complicate
a program with print statements.

I append the full break down of the student answers.

Cheers,
Manuel

-=-

                   Very difficult
                                Average
                                           Very easy
    Recursive functions
                      3.8%  
                            16.1%  
                                  44.2%  
                                       25.2%  
                                             12.1%  
    List processing
                      5.2%  
                             18%  
                                  44%  
                                       25.4%  
                                             8.8%  
    Pattern matching
                       3%  
                            15.2%  
                                  41.4%  
                                       27.8%  
                                             14%  
    Association lists
                      4.5%  
                            28.5%  
                                  48.5%  
                                       15.4%  
                                             4.5%  
    Polymorphism/overloading
                      10.9%  
                            44.2%  
                                  37.8%  
                                        5.9%  
                                             2.6%  
    Sorting
                      5.7%  
                            33.5%  
                                  47.6%  
                                       11.6%  
                                              3%  
    Higher-order functions
                      16.9%  
                             43%  
                                  31.4%  
                                        8.5%  
                                             1.6%  
    Input/output
                      32.6%  
                            39.7%  
                                  19.7%  
                                        7.3%  
                                             2.1%  
    Modules/decomposition
                      12.8%  
                            37.1%  
                                  35.9%  
                                       12.1%  
                                             3.5%  
    Trees
                      29.5%  
                            41.9%  
                                  21.9%  
                                        5.7%  
                                             2.6%  
    ADTs
                      35.9%  
                            36.4%  
                                  20.9%  
                                        6.1%  
                                             2.1%  
    Unix/shell scripts
                      38.5%  
                            34.7%  
                                  20.7%  
                                        5.7%  
                                             1.9%  
    Formal reasoning
                      11.1%  
                            22.6%  
                                  31.9%  
                                       20.9%  
                                             15%