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

Yeah! I get all that no problem, but that is as useful in understanding how to program in FP as HelloWorld.(c|pl|etc.) is in teaching you how to program in an imperative language--basically nil.

FP is far more than the use of high order functions and functional composition and recursion, which can be crudely approximated by:

P:\test>p1 perl> sub prompt { printf "What's your name?: "; } sub getAnswer{ chomp( $_ =<STDIN> ); return $_; } sub greetings{ print "Hello $_[ 0 ]. Welcome to functional programming +!\n" } sub main { prompt , greetings getAnswer };; perl> main;; What's your name?: BrowserUk Hello BrowserUk. Welcome to functional programming!

The real essence and subtlety of FP is the typing system, but there is a dearth of documentation on this that isn't couched in complex theoretical explainations and references to research papers that you need a subscription to download, in pdf form (which I hate vehmently because the Adobe reader and GhostSscript UI's are so crap). And when you do download them, you need a greater-than-grad-level-degree of understanding of math notation to even begin to understand. I didn't do math at that level.

Example:

4.4.3.1 Function bindings A function binding binds a variable to a function value. The general f +orm of a function binding for variable x is: x p11 ... p1k match1 ... x pn1 ... pnk matchn where each pij is a pattern, and where each matchi is of the general f +orm: = ei where { declsi } or | gi1 = ei1 ... | gimi = eimi where { declsi } and where n>=1, 1<=i<=n, mi>=1. The former is treated as shorthand for + a particular case of the latter, namely: | True = ei where { declsi } Note that all clauses defining a function must be contiguous, and the +number of patterns in each clause must be the same. The set of patter +ns corresponding to each match must be linear---no variable is allowe +d to appear more than once in the entire set. Alternative syntax is provided for binding functional values to infix +operators. For example, these three function definitions are all equi +valent: plus x y z = x+y+z x `plus` y = \ z -> x+y+z (x `plus` y) z = x+y+z Translation: The general binding form for functions is semantically equivalent to t +he equation (i.e. simple pattern binding): x = \ x1 ... xk -> case (x1, ..., xk) of (p11, ..., p1k) match1 ... (pn1, ..., pnk) matchn where the xi are new identifiers.

And didn't that translation at the bottom really clarify everything!


Even the gentlest of the tutorials splits it time between

  • Saying the same things in the same way, but repetatively and slowly (like an Englishman speaking to a "foreigner" :).
  • Propagandising the benefits and dogmas of FP. Even the "Haskell tutorial for C programmers" somehow falls into the trap of justifying FP, rather simply explaining how to use it (well).
  • Uses syntax in the examples that you can't simply type into the "runloop interfaces" without modifying it.

    A picture (demo) is worth a thousand words (and 1000 trivial examples). My biggest single problem with all the "tutorial" material for FP languages, (and I think that I have read/scanned/attempted to follow all the commonly available ones for Haskell, Ocaml and Oz over the last few months), is the total absence of a fully worked, practical example. Something that starts with a real world practical problem, including all the "messy stuff" like IO, retained state (even better if a little FFI and concurrency was thrown in), and then takes you through the development process step-by-step without all the propaganda.

    What I mean by "2D syntax" the Python-style required whitespace--2d alignment rules. The FP guys appear to think this is elegant, and I guess it is compared to Lispish, but the always-moving-right, "hanging indents" formatting, looks anything but elegant or even structured to my eyes.

    Examples:

    Lisp

    defun generate-tree (phrase) "Generate a random sentence or phrase, with a complete parse tree." (cond ((listp phrase) (mapcar #'generate-tree phrase)) ((rewrites phrase) (cons phrase (generate-tree (random-elt (rewrites phrase))))) (t (list phrase))))

    Haskell:

    take m ys = case (m,ys) of (0,_) -> [] (_,[]) -> [] (n,x:xs) -> x : take (n-1) xs

    Ocaml:

    #let rec member x btree = match btree with Empty -> false | Node(y, left, right) -> if x = y then true else if x < y then member x left else member x right;; val member : 'a -> 'a btree -> bool = <fun>   #let rec insert x btree = match btree with Empty -> Node(x, Empty, Empty) | Node(y, left, right) -> if x <= y then Node(y, insert x left, right) else Node(y, left, insert x right);; val insert : 'a -> 'a btree -> 'a btree = <fun>

    And what is 'a? Where did that come from? "a" seems to be a "generic type placeholder" generated by the type inferencing, but what is the ' doing? Is it a function that tests the value of the type represented by the "a"?

    I (vaguely) recall that in math, ' is often used to indicate the derivitive of a term or expression, but in that case it is usually used postfix, not prefix as here.

    This sort of stuff pops up early and ubiquitously throughout the tutorials without any explaination. I think I tracked it down that ' is just-another-character used to create identifiers, like "_" in perl or C, but why is it used/generated here?

    The Haskell tuts never explain what '$' does that I have found, but if you look at any non-trivial Haskell code, it pops up all over the place. (I know what it does now through trial and error!).

    And '.'. This is explained, usually as f . g x = f( g( x )) or f . g x = (f (g x ) ) depending upon flavour, but what that doesn't tell you is when you need to use it! In most dialecs, simple abutment of functions f g x achieves the same thing--except when it doesn't and the '.' is required!

    Enough ranting, (sorry you caught me after another fruitless search for information and examples, and several long and ultimately useless downloads of PDFs that I had to search manually because the stupid UI won't let me search for stuff that wasn't bloody indexed. Grrr!).

    For now, I'm making best progress with Oz which has a more traditional syntax and some very nice concurrency features--despite it coming with a emacs interace (Nice OS, shame about the editor!).

    I think Ocaml will (eventually) be my favorite, despite all the same charges applying, because of it's balance of FP, imperative and OO features and very efficient standalone programs. Just a shame about the aweful windows runloop interface.

    I'd like to like Haskell with it's purity and laziness and monads and generics and FFI, but doing anything useful in it seems to be much harder to start with.


    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^12: World's shortest intro to function programming
    by Anonymous Monk on Jun 20, 2005 at 02:11 UTC
      The real essence and subtlety of FP is the typing system
      Ah, ha. Now we're getting somewhere. I could definitely see where one could go astray, if they start following the reasearch guys too closely. They *love* proposing new additions to the type system to see what results (that's their job after all). Here's where I'll heartily recommend the book The Haskell School of Expression: Learning Functional Programming through Multimedia. I found I needed an actual text to use in my journey into FP. While PDF's might be good enough to learn a new language if it is sufficiently close to one I already knew, I found learning FP from PDF's to be mostly unworkable. "The School of Expression" book covers the Haskell-98 standard, which ignores the bleeding edge type system extensions (this is a good thing). Also, as a side note, I'm obligated to mention that while static typing might be used in the more popular FP languages like Haskell and OCaml, it is by no means the essence of FP. You can do FP in Scheme and Perl if you so desire (I'll also mention my new favorite untyped functional language,Joy, although it is pretty much completely impractical at the moment).
      Uses syntax in the examples that you can't simply type into the "runloop interfaces" without modifying it.
      The Haskell interpreter prompt is inside an implicit "do" block (which is what sequences IO actions in a lazy language like Haskell). I could see where that could be confusing. What that basically means is that you have to use the 'let' keyword to introduce a new definition. (Not to be confused with "let .. in" construct you use in regular code).
      What I mean by "2D syntax" the Python-style required whitespace--2d alignment rules.
      That's probably just one of those "matters-of-taste" things. Some folks like curley braces, others don't.
      And what is 'a? Where did that come from? "a" seems to be a "generic type placeholder" generated by the type inferencing, but what is the ' doing? Is it a function that tests the value of the type represented by the "a"?
      I know nothing about Ocaml, but I'll take a stab at it anyway. In Haskell, there's nothing too special about the single quote, so you can use them as part of your variable names. When used that way, it is pronounced "prime". Also in Haskell, use of lower case names in a type signature indicate a type variable (the type signature shows what the types of the arguments and return value are for a function). A type variable can represent any type, as opposed to a specific type instance like Integer, Float, String, etc. (note the capitalization). For example, the type of "map" is...
      map :: (a -> b) -> [a] -> [b]
      ...which means that "map" takes two arguments (ignore currying for now). The first argument (a -> b) is a function which takes elements of type "a" and returns elements of type "b". The second argument ([a]) that "map" takes is a list of elements of type "a". And the return type is a list of elements of type "b". For instance, if...
      strlen :: String -> Integer strlen x = length x example = ["dog", "cat", "orange"]
      ...then "map strlen example" would evaluate to the list [3,3,6].
      The Haskell tuts never explain what '$' does that I have found, but if you look at any non-trivial Haskell code, it pops up all over the place. (I know what it does now through trial and error!).
      Yup. That's a legitimate complaint. The '$' application function is under explained and mainly used to eliminate parenthesis.
      And '.'. This is explained, usually as f . g x = f( g( x )) or f . g x = (f (g x ) ) depending upon flavour, but what that doesn't tell you is when you need to use it! In most dialecs, simple abutment of functions f g x achieves the same thing--except when it doesn't and the '.' is required!
      Yeah, function application precedence was a little tricky for me at first in Haskell (and curried everything doesn't make it easier), but you'll get the hang of it with a little practice.
      Enough ranting, (sorry you caught me after another fruitless search for information and examples, and several long and ultimately useless downloads of PDFs that I had to search manually because the stupid UI won't let me search for stuff that wasn't bloody indexed. Grrr!).
      No problem! Ranting is par for the course when undertaking any new exercise. Hang in there, it'll get easier the more you do.
        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.
          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.

          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