5
votes

I am watching a tutorial on parsers in haskell https://www.youtube.com/watch?v=9FGThag0Fqs. The lecture starts with defining some really basic parsers. These are to be used together to create more complicated parsers later. One of the basic parsers is item. This is used to extract a character from the string we are parsing.

All parsers have the following type:

type Parser a = String -> [(a, String)]                                         

The parser item is defined like this:

item :: Parser Char                                                             
item = \inp -> case inp of                                                      
                    [] -> []                                                
                    (x:xs) -> [(x,xs)]

I am not so used to this syntax, so it looks strange to me. I would have written it:

item' :: Parser Char                                                            
item' [] = []                                                                   
item' (x:xs) = [(x,xs)]

Testing it in ghci indicates that they are equal:

*Main> item ""
[]
*Main> item "abc"
[('a',"bc")]
*Main> item' ""
[]
*Main> item' "abc"
[('a',"bc")]

The lecturer makes a short comment about thinking it looks clearer, but I disagree. So my questions are:

Are they indeed completely identical? Why is the lambda version clearer?

3
I'd say it's a matter of taste. - augustss

3 Answers

11
votes

I believe this comes from the common practice of writing

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = someResult

where we have exactly n function arrows in the type, and exactly n arguments in the left hand side of the equation. This makes it easy to relate types and formal parameters.

If Result is a type alias for a function, then we may write

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn y = something

or

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = \y -> something

The latter follows the convention above: n arrows, n variables in the left hand side. Also, on the right hand side we have something of type Result, making it easier to spot. The former instead does not, and one might miss the extra argument when reading the code quickly.

Further, this style makes it easy to convert Result to a newtype instead of a type alias:

newtype Result = R (... -> ...)

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = R $ \y -> something

The posted item :: Parser Char code is an instance of this style when n=0.

4
votes

Why you should avoid equational function definitions (by Roman Cheplyaka): http://ro-che.info/articles/2014-05-09-clauses

Major Points from the above link:

  • DRY: Function and argument names are repeated --> harder to refactor
  • Clearer shows which arguments the function decides upon
  • Easy to add pragmas (e.g. for profiling)
  • Syntactically closer to lower level code

This doesn't explain the lambda though..

3
votes

I think they are absolutely equal. The lambda-style definition puts a name item to an anonymous lambda function which does pattern matching inside. The pattern-matching-style definition defines it directly. But in the end both are functions that do pattern matching. I think it's a matter of personal taste.

Also, the lambda-style definition could be considered to be in pointfree style, i.e. a function defined without explicitly writing down its arguments actually it is not very much pointfree since the argument is still written (but in a different place), but in this case you don't get anything with this.

Here is another possible definion, somewhere in between of those two:

item :: Parser Char
item inp = case inp of
                [] -> []
                (x:xs) -> [(x, xs)]

It's essentially identical to the lambda-style, but not pointfree.