HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Euler 1 but pointless

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pointlessbuteuler

Problem

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 reinventing-the-wheel, 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.

Solution

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.

Code Snippets

divisible x y = y `mod` x == 0
divisible = ((==) 0 .) . flip mod
any' p m = any ($ m) p
any' = flip (any . flip id)
foo x = bar $ quux $ fox $ tango $ x
foo = bar . quux . fox . tango
euler1 xs = sum [x | x <- xs, x `rem` 3 == 0 || x `rem` 5 == 0]
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

Context

StackExchange Code Review Q#161287, answer score: 5

Revisions (0)

No revisions yet.