Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell using $ instead of parentheses is invalid

I'm new in haskell. I want to learn the usage of $ and I wrote two little functions, but the second one is not working, can somebody explain me please what's wrong with that little snippet? If I understand it right $ is working like parentheses. Thanks in advance :)

myButLast::[a]->a
myButLast l = l !! (length l-2)

--not working
myButLast::[a]->a
myButLast l = l !! $ length l-2
like image 905
MrNobody Avatar asked Dec 17 '20 16:12

MrNobody


Video Answer


2 Answers

This doesn't parse for the same reason True && || False doesn't parse: you've used two infix operators right next to each other. What could the compiler possibly do with something like that?

You could write it this way:

myButLast l = (l!!) $ length l-2

which means, apply the function (l!!) to length l-2. This works because you can partially apply any Haskell function, including infix operators. But it rather defeats the point of using $ to avoid parens...

On a somewhat unrelated note: length and !! are code smells in Haskell. Especially !! is both unsafe and slow, it is almost never a good idea to use it. If you really need such direct-access operations, you should use arrays/vectors (see below), but to make it at least safe you can change the signature:

myButLast :: [a] -> Maybe a
myButLast v = case length v of
 l | l>=2   -> Just . (v!!) $ l-1
 _ -> Nothing

Note that in this case, $ does make sense, because it allows me to parenthesize both the RHS and the LHS (the composition of the Just and the (v!!) functions).

Fast vector version of the same thing:

import qualified Data.Vector.Generic as VG

myButLast :: VG.Vector v a => v a -> Maybe a
myButLast v = case VG.length v of
 l | l>=2   -> Just . VG.unsafeIndex v $ l-1
 _ -> Nothing
like image 88
leftaroundabout Avatar answered Nov 15 '22 07:11

leftaroundabout


If I understand it right $ is working like parentheses.

No. ($) :: (a -> b) -> a -> b is not some special syntax, it is just an operator like (+), (-) or operators you define yourself.

The reason that this works is because of the infixr 0 precedence, it thus means that this operator has the lowest precedence, and thus will group elements on the left on the right even if they contain operators, because the precedence will be higher.

($) itself is implemented as:

infixr 0  $

($) :: (a -> b) -> a -> b
($) f x = f x

it is thus in essence simple function application, but the operator precedence let it look as if there are hidden parenthesis for the left and right operand.

The problem with your expression is that it contains now two operators after each other, indeed:

myButLast l = l !! $ length l-2

so that means that the parser got confused.

You can use operator sectioning and construct a function (l !!) and then use the operator:

myButLast l = (l !!) $ length l-2
--              ↑ operator sectioning

But it is probably better not to use length here in the first place: by using length you will iterate twice over the list: first to determine the list, and then to obtain the one-but-last index. This is inefficient, and likely will also result in large amounts of memory used.

You can simplify this with:

myButLast :: [a] -> a
myButLast (x:x2:xs) = go xs x x2
    where go [] y _ = y
          go (y2:ys) _ y = go ys y y2 
myButLast _ = error "empty list"
like image 35
Willem Van Onsem Avatar answered Nov 15 '22 08:11

Willem Van Onsem