in reply to Re^12: World's shortest intro to function programming
in thread Thread on Joel on software forum : "I hate Perl programmers."

Here's where I'll heartily recommend the book The Haskell School of Expression: Learning Functional Programming through Multimedia

Well. As an interim measure I read the lecture slides. From that cursory glance--that actually went at about the right pace (4 or 5 slides representing a few 10s of pages)--it does at least appear to attempt to deal with (what I term) real-world problems.

However, and here's the reason I will want to browse the book before purchasing rather buying on line, it (appears) to skips the nasty little details.

For example, the treatment of the trivial, but strongly indicative example of the unix wc program. (Which I had to type in because it doesn't come as part of the source file distribution with the book)

import System wcf :: ( Int, Int, Int ) -> String -> ( Int, Int, Int ) wcf ( cc, w, lc ) [] = ( cc, w, lc ) wcf ( cc, w, lc ) ( ' ' : xs ) = wcf( cc+1, w+1, lc ) + xs wcf ( cc, w, lc ) ( '\t' : xs ) = wcf( cc+1, w+1, lc ) + xs wcf ( cc, w, lc ) ( '\n' : xs ) = wcf( cc+1, w+1, lc+1 ) + xs wcf ( cc, w, lc ) ( x : xs ) = wcf( cc+1, w, lc ) + xs wc :: IO() wc = do name <- getLine contents <- readFile name let ( cc, w, lc ) = wcf( 0, 0, 0 ) contents putStrLn ( "The file:" ++ name ++ " has " ) putStrLn ( show cc ++ " chars " ) putStrLn ( show w ++ " words " ) putStrLn ( show lc ++ " lines." )

Compiling and running that fails because it has no main. Okay, it's off a slide, so add  main = wc. Compile and I get a (500kb!) executable. Try running it--on a file with 2 lines of 30 spaces:

C:\ghc\test>wc 60sp2l.txt wc: interrupted C:\ghc\test>wc 60sp2l.txt The file:60sp2l.txt has 62 chars 62 words 2 lines. C:\ghc\test>wc p:\test\1GB.dat The file:p:\test\1GB.dat has Heap exhausted; Current maximum heap size is 268435456 bytes (256 Mb); use `+RTS -M<size>' to increase it.

Impressed? Not! Doesn't handle command line parameters. No prompt. Counts spaces and newlines as words. Can't handle a big file.

Yes. I know it's only a demo. I should be able to add a prompt easily enough:

wcf :: ( Int, Int, Int ) -> String -> ( Int, Int, Int ) wcf ( cc, w, lc ) [] = ( cc, w, lc ) wcf ( cc, w, lc ) ( ' ' : xs ) = wcf( cc+1, w+1, lc ) x +s wcf ( cc, w, lc ) ( '\t' : xs ) = wcf( cc+1, w+1, lc ) x +s wcf ( cc, w, lc ) ( '\n' : xs ) = wcf( cc+1, w+1, lc+1 ) x +s wcf ( cc, w, lc ) ( x : xs ) = wcf( cc+1, w, lc +) xs wc :: IO() wc = do putStr ( "Filename; " ) name <- getLine contents <- readFile name let ( cc, w, lc ) = wcf( 0, 0, 0 ) contents putStrLn ( "The file:" ++ name ++ " has " ) putStrLn ( show cc ++ " chars " ) putStrLn ( show w ++ " words " ) putStrLn ( show lc ++ " lines." ) main = wc

Compile:

C:\ghc\test>ghc -o wc.exe wc.hs wc.hs:9:8: Parse error in pattern

Hmmm. Informative!

So, skip the prompt, and try and get the argument from the command line. Scan the library docs. System looks promising. But there is nothing that looks like it gives me access to the commmand line? Okay, skip that. Try dealing with the "words are spaces" problem. In an imperative language I'd simple 'remember' the previous character and treat consecutive spaces (and newlines) as a single delimiter for the purpose of counting words...but of course, that's state!

So, how about dealing with the memory issue? I thought the beauty of Haskell was that it was non-strict or lazy. That it dealt with infinite lists. Then why does getContents insist on loading the whole darn file?

Another example drawn from the same source.

From Chapter 8, containsS for Rectangles is defined as:

Rectangle s1 s2 `containsS` (x,y) = let t1 = s1 / 2 t2 = s2 / 2 in -t1<=x && x<=t1 && -t2<=y && t2<=t2

But there is a classical GUI problem here. If you divide the screen into a grid, of say 10 x 10 pixels, then by the above definition any point on the right edge of one rectangle also appears on the left edge of the adjacent rectangle to its right. Likewise for points on the other three edges and their corresponding neighbours. This leaves a point at the crossroads between 4 adjacent squares testing as being contained within all four squares simultaneously! Might work for a Corner bet in roulette, but it surely doesn't work well for the majority of hit-testing purposes in graphical applications.

And that's the problem. All the demos are the same. They concentrate on (laborious formal) examination of Haskell's strengths and completely skip over all the messy edge cases.

All languages have their strengths and weaknesses. What defines a languages usability is the way in which it deals with it's weaknesses. What puts the P in Perl, is the practical way in which it has built-in mechanisms for dealing with the messiness of the real world--the edge cases--even where that means it has to eshew orthoganality and purity in order to achieve that practicality. Where the same problem in usability crops up frequently, the language has been extended and "special cased" to deal with that situation in a reasonable and usually quite intuative way. And the special cases are the subject of extensive documentation in the FAQ.

My problem with trying to get to grips with FP is that the FP language documentation reflects their theoretical origins by expounding on the formalism of their definitions extensively, but (from what I've seen so far) leaving the messy detail of dealing with the edge cases to .... I don't know yet. I haven't found the answer.


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.

Replies are listed 'Best First'.
Re^14: World's shortest intro to function programming
by Anonymous Monk on Jun 20, 2005 at 15:46 UTC
    However, and here's the reason I will want to browse the book before purchasing rather buying on line
    Go to your nearest public library. If they don't have it, ask them to do an interlibrary loan. You'll get to browse it for a month before you decide if you want to buy a copy.
    For example, the treatment of the trivial, but strongly indicative example of the unix wc program. (Which I had to type in because it doesn't come as part of the source file distribution with the book)
    I don't remember any word counting problem in the book. So I grabbed my copy off the shelf and looked in the index. No luck. I skimmed it quickly. No luck. I can't even think what chapter it would be in. Maybe it doesn't come as part of the source for the book because it not in there? I don't know.
    Compile and I get a (500kb!) executable.
    Yeah, its a high level language. The current compilers statically link everything in the runtime into one executable. Just for comparison, my version of perl clocks in at over 1MB.
    wc.hs:9:8: Parse error in pattern Hmmm. Informative!
    It's that 2D syntax thing biting you again. You need to indent the "name <- getLine" line in to match up with everything else...
    wc = do putStr ( "Filename; " ) name <- getLine contents <- readFile name let ( cc, w, lc ) = wcf( 0, 0, 0 ) contents putStrLn ( "The file:" ++ name ++ " has " ) putStrLn ( show cc ++ " chars " ) putStrLn ( show w ++ " words " ) putStrLn ( show lc ++ " lines." )
    So, skip the prompt, and try and get the argument from the command line. Scan the library docs. System looks promising. But there is nothing that looks like it gives me access to the commmand line?
    getArgs
    And that's the problem. All the demos are the same. They concentrate on (laborious formal) examination of Haskell's strengths and completely skip over all the messy edge cases.
    I think the assumption is that the books provides a high-level overview of the concepts necessary and its up to the user to provide the nitty-gritty details which are important to them.
    All languages have their strengths and weaknesses.
    Yeah, Haskell isn't Perl. They live in different niches of the programming language eco-system. If you're looking for a better lanugage to bang out one-off scripts in 15 minutes, Haskell isn't what you're looking for. Before I discovered functional programming, I always thought there must be a better way of creating programs then I was currently doing. When I saw the light of FP, I realized that it went a long way towards making programming work the way I thought it should. I could now make good looking programs that weren't brittle. And they looked beautiful. Learning FP made me a better Perl programmer too. But I'm not going to tell you that FP is the one true way for everybody. If imperative curly brace Algol descended languages suit you fine, stick with them. Different strokes for different folks.
      I can't even think what chapter it would be in.

      The implementation is shown on the lecture slides for CH.3.

      I just assumed (probably wrongly) that it was drawn from the book. I will go find the book next time I'm in town (rare) and give it a good browse. I will say that I have learnt a lot from just the slides already and I thankyou for bringing it to my attention.

      It's that 2D syntax thing biting you again. You need to indent the "name <- getLine" line in to match up with everything else...

      Hmmm. Seems I used the tab key instead of the space key for that one line. Looked fine in my editor and I didn't notice the difference in the post till I just looked back. I'm still working on my editor configuration for Haskell. It now converts tabs to spaces on save.

      The error message could be a little more informative.

      GetArgs

      I looked under System.Console; System.IO; System.Info; System.Process; System.Environment didn't get my attention. Before I discovered functional programming, I always thought there must be a better way of creating programs then I was currently doing.

      Likewise, I really want to like Haskell. My point was not that Perl was perfect or that Haskell was in anyway limited--Autrijus is proving otherwise every day. I'd just really like to see a reasonably simple but complete program developed from start to finish, by a competent Haskell programmer, as a way of getting a step up into the use of the language.

      With most Algol like languages, it is fairly easy for me to pick up the source to a program who's function I understand and learn by reading and modifying it. I've tried this with Haskell, but it is so different, not just in the layout, but the entire way of structuring the program, that even the simplest of real world examples leaves me wondering where to start.

      Simple example: When should I use data, when type or newtype?


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
        Hmmm. Seems I used the tab key instead of the space key for that one line. Looked fine in my editor and I didn't notice the difference in the post till I just looked back.
        Significant whitespace can introduce some strange looking error messages (tabs are a tool of the devil).
        I've tried this with Haskell, but it is so different, not just in the layout, but the entire way of structuring the program, that even the simplest of real world examples leaves me wondering where to start.
        Yeah, its a real mindbending experience for a while, what with all the recursion and lack of state and partially applied functions and lazy evaluation and type inference and significant whitespace and unfamiliar syntax/precedence-rules/semantics (whew!). So I really encourage you to get a book to help you out on your new journey. (And if you get a used book and you don't like it, you can probably sell it back for about the same as you paid for it, less postage and tax)
        Simple example: When should I use data, when type or newtype?
        If you know C, think of "data" like "struct" and "type" like "typedef". A "newtype" is just like "data", with only one type constructor (which gets optimized away at runtime).

        I'd just really like to see a reasonably simple but complete program developed from start to finish, by a competent Haskell programmer, as a way of getting a step up into the use of the language.

        You may have seen this already, but nothingmuch was also fed-up with having no real-world examples in the Haskell tutorials. So to help himself learn, and maybe help out the other like-minded imperative programmers, he's developing a Forth compiler targetting Parrot, in a tutorial-like format. It starts off simple and builds up, so it's not too difficult to follow. Give it a look if you haven't already.

        Simple example: When should I use data, when type or newtype?

        The Anonymous Monk's reply to this question is clear and concise, but for anyone following along that doesn't know C, here's a more verbose answer.

        Also, you might want to look at the Computer Language Shootout. There are a lot of little programs implementated in a lot of languages which can give you some code examples to look at. (although I hesisitate a little because of the shootout's emphasis on performance over simple and clear code)
Re^14: World's shortest intro to function programming
by kelan (Deacon) on Jun 21, 2005 at 19:45 UTC

    So, how about dealing with the memory issue? I thought the beauty of Haskell was that it was non-strict or lazy. That it dealt with infinite lists. Then why does getContents insist on loading the whole darn file?

    I was curious about why this happened, so I tried my own variations to see if I could overcome it. Nothing I tried worked; Hugs ran out of memory with a large file every time. So I did some more searching online and through the Haskell mailing list. There's a thread that talks about the 'wc' program, and gives the reasons why it uses so much memory.

    In essence, it's not that Haskell is not being lazy enough and reading in the entire file. The problem is that it's being too lazy, and not evaluating the '+1's in the recursive function call. So it keeps all these '+1's around in memory, waiting to be evaluated until it hits the base case, but you run out of memory before it gets there.

    The solution they came up with on the mailing list was to create a data type to hold the stats, and put strictness markers on the data type so that the '+1's would get evaluated immediately. Below is your example converted to that style. The strictness markers are the exclamation points before the parametric types in the data constructor Stats. They tell Haskell to evaluate whatever value is about to be put into that slot in the Stats value being created, instead of keeping the "thunks" around to evaluate later. This version does run in constant memory space with a huge file.

    data Stats = Stats !Int !Int !Int wcf :: Stats -> String -> Stats wcf stats [] = stats wcf (Stats cc w lc) (' ' : xs) = wcf (Stats (cc+1) (w+1) (lc ) ) xs wcf (Stats cc w lc) ('\t' : xs) = wcf (Stats (cc+1) (w+1) (lc ) ) xs wcf (Stats cc w lc) ('\n' : xs) = wcf (Stats (cc+1) (w+1) (lc+1) ) xs wcf (Stats cc w lc) ( x : xs) = wcf (Stats (cc+1) (w ) (lc ) ) xs wc :: IO() wc = do putStr ( "Filename: " ) name <- getLine contents <- readFile name let (Stats cc w lc) = wcf (Stats 0 0 0) contents putStrLn ( "The file " ++ name ++ " has " ) putStrLn ( show cc ++ " chars " ) putStrLn ( show w ++ " words " ) putStrLn ( show lc ++ " lines." ) main = wc

      Thankyou! You have no idea how much that will help me with my chosen 'first program'.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.