Srinivas
Srinivas

Reputation: 2100

Why do groupBy in Haskell need two variables to check

I was going through groupBy in Haskell and the way I understand it is that the groupBy function takes a List and a condition and groups the element based on the specified condition.

let value = [-3,-5,-1,1,1,1,2,3,-1,-2]
groupBy (\x y -> (x > 0) == (y > 0)) value

It works fine, but why should we give two variables in the lambda function? Why does "groupBy (>0) value" not work? Also should both the conditions be the same? What will happen if they are different. Please explain with examples.

Upvotes: 2

Views: 1051

Answers (3)

duplode
duplode

Reputation: 34378

Lignum's answer explains why the groupBy predicate takes two arguments. To put it in another way, groupBy sets up boundaries, and you need two sides to be able to define a boundary.

Also should both the conditions be the same? What will happen if they are different.

Yes, they should. As the Data.List documentation notes...

The predicate is assumed to define an equivalence.

In other words, if p :: a -> a -> Bool is the predicate you pass to groupBy, then all of...

  • p x x = True
  • p x y = p y x
  • if p x y && p y z then p x z

... should hold. Weird things happen if that isn't the case. For instance, we might at first think that...

groupBy (\x y -> (x > 0) == (y < 0)) [-1,2,-3,-4,2,1,2,-3,1,1,2]

... would produce groups composed of alternating above zero and below zero elements. Instead, we get bogus results:

GHCi> groupBy (\x y -> (x > 0) == (y < 0)) [-1,2,-3,-4,2,1,2,-3,1,1,2]
[[-1,2],[-3],[-4,2,1,2],[-3,1,1,2]]

(The reason it goes wrong is that the guarantee of the predicate being an equivalence is exploited by implementing groupBy in terms of span.)


Finally, there is a nicer way of writing your groupBy predicate: using on from Data.Function:

GHCi> groupBy ((==) `on` (> 0)) value
[[-3,-5,-1],[1,1,1,2,3],[-1,-2]]

(g `on` f) x y = g (f x) (f y). You might even define:

-- A more general type: Eq b => (a -> b) -> [a] -> [[a]]
groupBySingle :: (a -> Bool) -> [a] -> [[a]]
groupBySingle p = groupBy ((==) `on` p)

Upvotes: 5

ThreeFx
ThreeFx

Reputation: 7360

groupBy operates by checking whether the current element belongs to the group based on the previous one.

The behaviour you want is best descibed by a separate function, repeatedly applying span and break:

myFunc :: (a -> Bool) -> [a] -> [[a]]
myFunc _ [] = []
myFunc p xs = satp : notsatp : myFunc p rest
  where
    (satp, rest') = span p xs
    (notsatp, rest) = break p rest'

Upvotes: 2

Emily
Emily

Reputation: 1096

[...] the way I understand it is that the groupBy function takes a List and a condition and groups the element based on the specified condition.

Not entirely, it groups adjacent elements based on the specified condition. Notice how your code evaluates to this list:

[[-3,-5,-1],[1,1,1,2,3],[-1,-2]]

instead of this list:

[[-3,-5,-1,-1,-2],[1,1,1,2,3]]

Therefore, the lambda expression that you pass to groupBy determines whether two adjacent elements should be in a group together.

Upvotes: 2

Related Questions