patternMinor
Euler 1 but pointless
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.
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:
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
I admit that tools were used in the process of creating the pointfree version. With
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
The point-free variant is not only harder to read, but even longer, and I've already used infix-
The point-free version of
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
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
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
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.
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 modThe 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 . tangoBut 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) -> bBut 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 modany' p m = any ($ m) p
any' = flip (any . flip id)foo x = bar $ quux $ fox $ tango $ x
foo = bar . quux . fox . tangoeuler1 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) -> bContext
StackExchange Code Review Q#161287, answer score: 5
Revisions (0)
No revisions yet.