5
\$\begingroup\$

I've been teaching myself basic haskell this week in order to be able to better read literature about functional programming (about 80% of which in haskell by my completely off-the-cuff statistic). Just as a check to make sure I got the basic concepts, here is how I answered Project Euler 1.

divisible :: Integral a => a -> a -> Bool
divisible x y = mod y x == 0

-- do any of the predicates match a value
any' :: [a -> Bool] -> a -> Bool
any' predicates match = any ($ match) predicates

euler1 :: Integral a => [a] -> a
euler1 xs = sum . filter (any' [divisible 3, divisible 5]) xs

euler1' = euler1 [1..999]

I opted to describe how the answer is defined (as seems to be the way FP works) as opposed to find a faster solution, as this was an exercise in idiomatic FP and haskell.

Since this is supposed to be a practice in idiomatic haskell, point free seems to be the way to go:

-- with the same type definitions
divisible = ((==) 0 .) . flip mod
any' = flip (any . flip id)
euler1 = sum . filter $ any' [divisible 3, divisible 5]

The main purpose of this review is this: which definition of each of these functions is the most idiomatic haskell? Along those lines, this is not supposed to be , and if a Prelude or stdlib solution to divisible or any' exists, they should be replaced with the stdlib version.

I admit that tools were used in the process of creating the pointfree version. With divisible I think I fully understand the composition (f . . g === f ( g ( arg, arg ) )), but I'd be fecetious in saying I fully understood the pointfree formulation of any'. That may effect the best solution for me, but I'm interested mostly in idiomatic, readable (to more veteran haskellers) code.

\$\endgroup\$
5
\$\begingroup\$

Since this is supposed to be a practice in idiomatic haskell, point free seems to be the way to go

Ah, but point-free Haskell isn't idiomatic in general. Just compare both variants of divisible:

divisible x y = y `mod` x == 0
divisible = ((==) 0 .) . flip mod

The point-free variant is not only harder to read, but even longer, and I've already used infix-mod to increase the pointful version by two characters. The same holds for any, although the original variable names where a little bit long:

any' p m = any ($ m) p
any' = flip (any . flip id)

The point-free version of euler1 is perfectly fine. It's also the most natural one to do by hand, since \x -> f $ g x is f . g, and euler1 follows exactly that style. Defining new functions by combinatiing old ones is great if you have that style of function, e.g.

foo x = bar $ quux $ fox $ tango $ x
foo = bar . quux . fox . tango 

But as soon as a second parameter gets involved, it gets a lot more trickier. And as always, the best code is not worth it, if you cannot understand it later on. Haskell's referential transparency makes that a lot easier, but if you need to sit down and check how flip (any . flip id) works, it's a good call to use \p m -> any ($m) p.

In the end, it boils down to your personal choice. I would have used a list comprehension, because it's still easy to read, but it's only a syntactic sugar variant of your filter:

euler1 xs = sum [x | x <- xs, x `rem` 3 == 0 || x `rem` 5 == 0]

I opted to describe how the answer is defined (as seems to be the way FP works) as opposed to find a faster solution

Just for completeness, have a look at the inclusion-exclusion principle, and try to solve Euler1 in Haskell (or another language).

but I'd be fecetious in saying I fully understood the pointfree formulation of any'.

flip (any . flip id) p m
  = (any . flip id) m p
  = ((any . flip id) m) p
  = (any (flip id m)) p -- see remark below
  = (any ($ m)) p
  = any ($ m) p

id      :: (a -> b) -> a -> b
flip id :: a -> (a -> b) -> b 
($)     :: a -> (a -> b) -> b

But the flipless version is easier to understand.

That may effect the best solution for me, but I'm interested mostly in idiomatic, readable (to more veteran haskellers) code.

Any veteran will tell you that the first version is more readable. They will understand the latter, but after all, both versions do exactly the same.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.