Sven Panne wrote:
> > Don't fear! Mr. One-Liner comes to the rescue: ;-)
> >
> > longerThan fn lenlim = readFile fn >>= lines .| filter (length .| (>lenlim)) .|
>zip [1..] .| map (\(n,l) -> shows n ") " ++ l) .| unlines .| putStr
Friedrich wrote:
> Do you want to drive me away from learning Haskell? Who the hell can try
> to write such functions? Is readabilty not a concern in Haskell?
I would have to agree, Sven does seem to be working hard to drive a
beginner away from Haskell. But he is illustrating an important
coding style. If we lay his function out on a few more lines, and
replace his (|.) = flip (.) operator with the standard functional
composition (.), we get the following:
longerThan fn lenlim
= readFile fn >>= procFile
procFile
= putStr .
unlines .
(map (\ (n,l) -> show n ++ ") " ++ l)) .
(zip [1..]) .
(filter ( (>lenlim) . length ) ) .
lines
-- warning: untested code, sorry in advance for any typos...
This program is a good example of the use of higher-order functions.
First, note that (.) is function composition: so f . g is a function
that takes an argument (say x) and returns f (g x) : it applies g
first, and then applies f to the result. So read the definition of
procFile backwards.
procFile is given the contents of the file as a String argument by
readFile fn >>= procFile
Now it splits it into lines, yielding a list of lines, type [String].
Next we use a standard function, filter, which goes over a list
throwing out elements that don't match a given test. Here the test is
(>lenlim) . length - in other words, find the length of the string,
and then check if it is greater than lenlim. If it isn't, throw it
out. We now have a new list of type [String] containing all lines
longer than lenlim.
Now we use another standard function, zip. This takes two lists (like
the two sides of a zipper) and merges them into one list, containing
pairs of elements: zip [1,2,3] ["Alpha","Bravo","Charlie"] gives
[(1,"Alpha"),(2,"Bravo"),(3,"Charlie")]. Here we pass the list
[1,2,3,4,5,6,...] (which goes on forever) as the first argument; zip
stops when the second list runs out. So at the end of this stage we
have a list of pairs of line number and contents of line: type is
[(Integer,String)].
Next we use *another* standard function, map. This applies a given
function to every element of the list. Here the function is (\ (n,l)
-> show n ++ ") " ++ l). This takes a pair (n,l), n being the line
number and l the line, and returns the concatenation of the number as
a string (show n), a close paren (to make it look nice), and the
original line (l). We now have a list of strings again: ["1) Alpha",
"2) Bravo", "3) Charlie"].
Finally, we use unlines to turn the list into a single string
separated by newlines, and we print it out with putStr.
The neat trick here is that by using (.) we don't have to give names
to the intermediate results of the computation. This works because in
Haskell you don't have to give all the parameters for a function; you
can miss out the last one and instead of getting an answer, you get
another function that takes the last parameter and gives you the
answer. This is called currying, after the last name of the guy
Haskell is named after (Haskell B. Curry).
Hope this hasn't confused you too much. One of Haskell's features is
that it is a very concise language; this is both good and bad. You
get used to it after a while.
--KW 8-)