maro
maro

Reputation: 77

Haskell eta reduction using map and $ for function application

Hi I have task for writing a function that takes a list of functions from Int -> Int and a single Int and checks if any function when applied to that Int gives a result which is greater than 5.

This is my solution :

foo :: [Int -> Int] -> Int -> Bool
foo ls = any (>5) . f ls
     where f ls v = map ($ v) ls

I would like to know is there a way to write this in single line (without using where or let) with eta reducing last argument (now it is eta reduced but it is not in single line). I tried something like this :

foo ls = any (>5) . flip map ls

But now the problem is call of function that looks like this for it to work

foo [(+3), (*4), (+1)] ($2)

I would like to avoid writin $ for last argument. I tried something like this (and similar to this) :

foo ls = any (>5) . flip map ls ($)

But I always get some kind of error. Be aware that I must not change the type signature, thus the flip.

Upvotes: 0

Views: 389

Answers (4)

assembly.jc
assembly.jc

Reputation: 2066

Actually, your last trial in the question

foo ls = any (>5) . flip map ls ($)

is already very close to solution.

The only one problem is, considers the definition of ($):

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

the first argument of ($) is a function (a->b), but in your case, need be a value a, so just flip ($) swap the order of the arguments of ($) and put it back as:

foo ls = any (>5) . flip map ls . flip ($)

is the function that you want.

Upvotes: 1

Starting with a fully non-reduced definition:

foo ls v = any (>5) (map (\f -> f v) ls)

We can use lambdabot to have a fully eta-reduced solution:

foo = (any (> 5) .) . flip (map . flip id)

However, this looks awful, and should not be recommended to do.

Upvotes: 1

Carl
Carl

Reputation: 27023

Is there a way? Sure. Is it readable? No. Haskell's design pushes you away from some common mistakes, like unnecessary mutability or hidden use of (necessary) mutable state. But it makes it exciting to follow the path of other mistakes, like making code point-free in a manner harmful to readability and maintainability.

The thing about point-free definitions is that converting to them is a completely mechanical process (so long as pattern matching isn't involved in the starting definition). There are tools to do it automatically.

Start by writing the definition fully eta-expanded:

foo ls x = any (>5) (map ($ x) ls)

Then throw it into your favorite tool for doing the conversion. http://pointfree.io/ is an online service that will do it.

And it spits out the totally unreadable:

foo = (any (> 5) .) . flip (map . flip id)

Don't use that. It's not worth it.

But maybe you only wanted to eta-contract away one argument, and this is too much? Well, you can do that by cheating a little bit, and pretending the first argument is a value in scope, not an argument, and throwing the following definition in:

foo x = any (>5) (map ($ x) ls)

It returns foo = any (> 5) . flip map ls . flip id, which you then insert the argument back into:

foo ls = any (> 5) . flip map ls . flip id

This is mildly better, thanks to lacking sections of the (.) operator. But it still has flip id in it, which is obfuscation at its simplest.

Don't do it. Exercise restraint. Write code that's easy to read and modify.

Upvotes: 3

Redu
Redu

Reputation: 26191

Since you have an applicative list at hand you may simply apply it with the applicative operator <*> as [(+3), (*4), (+1)] <*> [2] to obtain the list of results such as [5,8,3]. Now it's the any :: Foldable t => (a -> Bool) -> t a -> Bool function that comes handy. So all together

checker x = any (>5) $ [(+3), (*4), (+1)] <*> [x]

should do the job. If you need the pointfree version than it would be like

checker = any (>5) . ([(+3), (*4), (+1)] <*>) . pure

Upvotes: 0

Related Questions