1

I am very new to Haskell, and struggling a bit with a function here. The premise is simple enough: Run through a list, and combine each 3 items next to each other with another function and return a list with the results. The problem is to do it in a nice way.

Here is what I've got:

foo :: [Int] -> [Int]
foo xs
    | length xs < 3 = []
    | otherwise     = n : foo (tail xs)
    where n = calc (xs!!0) (xs!!1) (xs!!2)

-- This function is actually significantly more complicated.
calc :: Int -> Int -> Int -> Int
calc x y z = x + y - (z * 2)

-- And we can use it like this:
foo [1,2,3]     -- [-3]
foo [1,2,3,4]   -- [-3,-3]
foo [1,1,5,3,3] -- [-8,0,2]

What I don't like, is the 5th line, containing all the !!'s. It feels like I'm thinking about it the wrong way, and that there should be a better way of doing this. I'd like to do something like

foo (x:y:z:xs)
    -- ...

But that will fail when the list gets less than three items. So, then I'd have to declare other patterns for when the list has fewer items?

Also, in case there is already a function that does what foo does (there probably is, it seems there is one for everything), then I'm not really all that interested in it. I'm trying to grok the Haskell way of doing things, more than expanding my repetoire of functions.

Edit: In JS, I'd do something like n = calc.apply(null, take(3, xs)). I wonder if Haskell has something like apply that takes an array and applies it to a function as parameters.

Edit 2 -- Solution: (based on comment below)

foo (x:y:z:xs) = calc x y z : foo (y:z:xs)
foo _          = []

Last pattern match is a catch-all, so if the first "fails" it will fall through and just return an empty list.

  • 3
    You should indeed just pattern match on `foo (x:y:z:xs)` and have a second pattern for everything else `foo _ = []`. Checking the length of a list to establish the existence of a certain amount of elements is not idiomatic. – user2407038 Jun 14 '15 at 21:24
  • What? I can do that? One sec... –  Jun 14 '15 at 21:27
  • 1
    So... smooth. I think I might like this language. –  Jun 14 '15 at 21:30

1 Answers1

2

Well, foo (x:y:z:xs) plus a “too short clause” certainly wouldn't be a bad solution. Another would be

foo xs = case splitAt 3 xs of
           ([x,y,z],xs') -> calc x y z : foo (y:z:xs')
           _ -> []

Or, perhaps nicest,

import Data.List (tails)

foo xs = [ calc x y z | (x:y:z:_) <- tails xs ]
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • `xs'` is just another variable name, right? Nothing fancy about the `'`. But, aren't you calling `calc` with a list? –  Jun 14 '15 at 21:31
  • @GormCasper I believe the statement from [another SO answer](http://stackoverflow.com/a/5673954/3041008) _"The convention in Haskell is that, like in math, the apostrophe on a variable name represents a variable that is somehow related, or similar, to a prior variable."_ applies here. – mucaho Jun 14 '15 at 21:34
  • Yes, `xs'` is just another variable name. And yes, I was calling `calc` with a list, hadn't properly read its signature in the question. – leftaroundabout Jun 14 '15 at 21:39
  • Right, that's what I thought. @leftaroundabout: This doesn't seem to work. I get `[-8]` when calling it with `[1,1,5,3,3]` –  Jun 14 '15 at 21:40
  • Again didn't read the question properly... you must pass on `y` and `z` to the recursive call. – leftaroundabout Jun 14 '15 at 21:47
  • Oh wow. I like the look of the last one, but I don't quite understand what's going on yet. I have yet to read about the `<-`. It'll come. –  Jun 14 '15 at 21:53