Point equivalent
I have this function from another SO question ,
f :: Ord a => [a] -> [(a, Int)]
f xs = zipWith (\x ys -> (x, length $ filter (< x) ys)) xs (inits xs)
I am trying to write it point-free style,
f = flip (zipWith (\x -> (,) x . length . filter (< x))) =<< inits
Is it possible to get rid of this x?
source to share
As a rule of thumb: if a variable appears more than once in an expression, then it is probably not recommended to do it without dots. If you do decide, the least unreadable way is with combinators Arrow
, because it's pretty clear when the data stream is "split". For xs
I would write
uncurry (zipWith (...)) . (id &&& inits)
For x
, the same method gives
zipWith ( curry $ uncurry(,) . (fst &&& length . uncurry filter . first(>)) )
It's even more than the (->)
-monad solution you used and lambdabot suggests, but looks much more organized.
source to share
It is possible, but absolutely not worth the pain. To answer your question, LambdaBot tells FreeNode:
f = flip (zipWith (liftM2 (.) (,) ((length .) . filter . flip (<)))) =<< inits
At this point, the function lost all clarity and became unattainable. It's much better for you to present real names here. Remember, just because we can make things accurate doesn't mean we have to.
source to share
The pointfree style point doesn't just omit names for values, but prefers names for functions. This is much easier to do when you use very small definitions. Of course, any code becomes unreadable if you include everything and don't use good names.
So, let's start with your original function and break it down into several smaller definitions.
f xs = zipWith combine xs (inits xs)
combine x xs = (x, countWhere (< x) xs)
countWhere f xs = length (filter f xs)
We can now easily make these definitions accurate in a readable way.
f = zipWith combine <*> inits
where combine = compose (,) countLessThan
compose = liftA2 (.)
countLessThan = countWhere . flip (<)
countWhere = length .: filter
(.:) = (.) . (.)
The use of names is smart and the preference for composition over the application allows us to code our code into small, easily understandable definitions. Named parameters are equivalent goto
for data-powerful, but are best used to create higher-level reusable structures that are easier to understand and use correctly. These compositional combinators, such as (.)
and <*>
, are data streams for which map
, filter
and fold
are intended to control flow.
source to share
My blow to it:
f :: Ord a => [a] -> [(a, Int)] f = zip <*> ((zipWith $ (length .) . filter . (>)) <*> inits)
Here I changed (<)
to (>)
, to have (length .) . filter . (>)
as a function of the arguments in the correct order a->[a]->Int
. Let's move on to zipWith
get it [a]->[[a]]->[Int]
.
Assuming in input [a]
, we can see it as f ([[a]]->[Int])
for Applicative ((->) [a])
, which can be combined with inits :: f [[a]]
with <*> :: f ([[a]]->[Int])->f [[a]]->f [Int]
. This gives us [a]->[Int]
, now we need to consume simultaneously [a]
and [Int]
. zip
already has the correct type: [a]->[Int]->[(a,Int)]
applied with <*>
.
source to share
Not saying I recommend it, but King Pointfree Control.Arrow
import Control.Arrow
-- A special version of zipWith' more amenable to pointfree style
zipWith' :: ((a, b) -> c) -> ([a], [b]) -> [c]
zipWith' = uncurry . zipWith . curry
f :: Ord a => [a] -> [(a, Int)]
f = zipWith' (fst &&& (length <<< uncurry filter <<< first (>))) <<< id &&& inits
Let me clarify here: I really don't recommend this unless you intend to generalize in some way the kind of arrow that your program is running in (like in Arrowized FRP).
source to share
With a known
(f .: g) x y = f (g x y)
it is semi-readable
zipWith (curry (fst &&& uncurry (length .: (filter . flip (<))) )) <*> inits
-- \(x,ys) -> (x , length ( (filter . flip (<)) x ys) )
Using Control.Applicative
( f <*> g $ x = f x (g x)
, combinator S ) and Control.Arrow
(like others, but slightly different).
source to share