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%