14

I have a task in Haskell (no, it's not my homework, I'm learning for exam).

The task is:

Write point-free function numocc which counts occurrences of element in given lists. For example: numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]] = [1, 2, 0]

This is my code:

addif :: Eq a => a -> Int -> a -> Int
addif x acc y = if x == y then acc+1 else acc

count :: Eq a => a -> [a] -> Int
count = flip foldl 0 . addif

numocc :: Eq a => a -> [[a]] -> [Int]
numocc = map . count

numocc and count are 'point-free', but they are using function addif which isn't.

I have no idea how can I do the function addif point-free. Is there any way to do if statement point-free? Maybe there is a trick which use no if?

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 1
    are you allowed to do math-chenigans like ` let f x y = 1 - ceiling (fromIntegral (x-y) / fromIntegral y) :: Int`? (which is not exactly what you need ;)) – Random Dev Dec 17 '15 at 19:29
  • but `1 - ceiling (abs $ fromIntegral (x-y) / fromIntegral (max x y)) :: Int` should do it if I did not miss some nasty border case somewhere - maybe you will want to reason about it or run some quickchecks ;) (well I missed some negatives so you'll have to but more `abs` in ... but the principle should be obvious ... the real solution is *trivial* and left for the reader **:D**) – Random Dev Dec 17 '15 at 19:34
  • 4
    In general you can replace an `if` statement with the function `bool :: Bool -> a -> a -> a; bool False f _ = f; bool True _ t = t`, in which case you can always form a "normal" expression that can be made point free with the regular methods. – user2407038 Dec 17 '15 at 22:56

6 Answers6

11

I would use the fact that you can easily convert a Bool to an Int using fromEnum:

addif x acc y = acc + fromEnum (x == y)

Now you can start applying the usual tricks to make it point-free

-- Go prefix and use $
addif x acc y = (+) acc $ fromEnum $ (==) x y
-- Swap $ for . when dropping the last argument
addif x acc = (+) acc . fromEnum . (==) x

And so on. I won't take away all the fun of making it point free, especially when there's tools to do it for you.

Alternatively, you could write a function like

count x = sum . map (fromEnum . (==) x)

Which is almost point free, and there are tricks that get you closer, although they get pretty nasty quickly:

count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==)

Here I think it actually looks nicer to use fmap instead of (.), although you could replace every fmap with (.) and it would be the exact same code. Essentially, the (fmap fmap fmap) composes a single argument and a two argument function together, if you instead give it the name .: you could write this as

count = (sum .: map) . (fromEnum .: (==))

Broken down:

> :t fmap fmap fmap sum map
Num a => (a -> b) -> [a] -> b

So it takes a function from b to a numeric a, a list of bs, and returns an a, not too bad.

> :t fmap fmap fmap fromEnum (==)
Eq a => a -> a -> Int

And this type can be written as Eq a => a -> (a -> Int), which is an important thing to note. That makes this function's return type match the input to fmap fmap fmap sum map with b ~ Int, so we can compose them to get a function of type Eq a => a -> [a] -> Int.

bheklilr
  • 53,530
  • 6
  • 107
  • 163
8

why not

numocc x 
  = map (length . filter (== x))
  = map ((length .) (filter (== x)) )
  = map (((length .) . filter) (== x))
  = map (((length .) . filter) ((==) x))
  = map (((length .) . filter . (==)) x)
  = (map . ((length .) . filter . (==))) x
  = (map . (length .) . filter . (==)) x

and then the trivial eta-contraction.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 2
    I think this conforms most to the described task. – András Kovács Dec 17 '15 at 19:54
  • @AndrásKovács the step-back approach... :) cf. [this recent Lisp entry](http://stackoverflow.com/questions/34207933/print-adjacent-duplicates-of-a-list-scheme) for some complicated equivalents of `map head . filter (not.null.drop 1) . group`. – Will Ness Dec 17 '15 at 20:06
6

One trick would be to import one of the many if functions, e.g. Data.Bool.bool 1 0 (also found in Data.Bool.Extras).

A more arcane trick would be to use Foreign.Marshal.Utils.fromBool, which does exactly what you need here. Or the same thing, less arcane: fromEnum (thanks @bheklilr).

But I think the simplest trick would be to simply avoid counting yourself, and just apply the standard length function after filtering for the number.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Why not just use `fromEnum` instead of the `Foreign.Marshal` function? It does the same thing and exists in `Prelude` already. – bheklilr Dec 17 '15 at 19:36
  • @bheklilr: Uh, didn't realize that `Bool` is an instance of `Enum` - you got my upvote for that already :-) `fromBool` just crossed my mind, I had seen it somewhere in the past… – Bergi Dec 17 '15 at 19:38
1

Using the Enum instance for Bool, it is possible to build a pointfree replacement for if that can be used in more general cases:

chk :: Bool -> (a,a) -> a
chk = ([snd,fst]!!) . fromEnum

Using chk we can define a different version of addIf:

addIf' :: Eq a => a -> a -> Int -> Int
addIf' = curry (flip chk ((+1),id) . uncurry (==))

Now we can simply replace chk in addIf':

addIf :: Eq a => a -> a -> Int -> Int
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))
Jakob Runge
  • 2,287
  • 7
  • 36
  • 47
0

I think you’re looking for Data.Bool’s bool, which exists since 4.7.0.0 (2014–04–08).

incif :: (Eq a, Enum counter) => a -> a -> counter -> counter
incif = ((bool id succ) .) . (==)

The additional . allows == to take two parameters, before passing the expression to bool.

Since the order of parameters is different, you need to use incif like this:

(flip . incif)

(Integrating that into incif is left as an exercise to the reader. [Translation: It’s not trivial, and I don’t yet know how. ;])

Evi1M4chine
  • 6,992
  • 1
  • 24
  • 18
0

Remember that in Haskell list comprehensions, if conditionals can be used in the result clause or at the end. But, most importantly, guards without if can be used to filter results. I am using pairs from zip. The second of the pair is the list number. It stays constant while the elements of the list are being compared to the constant (k). Your result [1,2,0] does not include list numbers 1, 2 or 3 because it is obvious from the positions of the sums in the result list. The result here does not add the occurrences in each list but list them for each list.

nocc k ls = [ z | (y,z) <- zip ls [1..length ls], x <- y, k == x]
nocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]

[1,2,2] -- read as [1,2,0] or 1 in list 1, 2 in list 2 and 0 in list 3

fp_mora
  • 718
  • 6
  • 11