简体   繁体   中英

Is it possible to eta (or otherwise) reduce arguments if I depend on them?

I was solving a super simple problem and came out with this code. I feel like there should be a way to eliminate arguments x and y in areaOrPerimeter . But I can't find a way to do it. Is it possible to get rid of them somehow? I have a strong feeling that it should be possible.

areaOrPerimeter :: Double -> Double -> Double
areaOrPerimeter x y
  | x == y = area x y 
  | otherwise = perimeter x y
  

area :: Num a => a -> a -> a
area = (*)

perimeter :: Num a => a -> a -> a
perimeter x y = 2 * (x + y)

If you want to have a chance of doing that in a non-obnoxious way, you should first group together the arguments meaningfully to avoid having to deal with multiple parameters:

data RectangleSize a = RectangleSize {width, height :: a}

Then we can make the functions and the x==y check more meaningful too:

area :: Num a => RectangleSize a -> a
area (RectangleSize w h) = w*h

perimeter :: Num a => RectangleSize a -> a
perimeter (RectangleSize w h) = 2*(w+h)

isSquare :: Eq a => RectangleSize a -> Bool
isSquare (RectangleSize w h) = w==h

That leaves us with the already more promising

areaOrPerimeter :: RectangleSize Double -> Double
areaOrPerimeter r
  | isSquare r  = area r 
  | otherwise   = perimeter r

To make that point-free, you still need to copy around the r three times. This can be done (whether it should is another matter) with the Applicative instance of functions, namely \x -> fx (gx) can be written as f<*>g . In this case, you want to make a decision, so you require a first-class if ; typically that's written

if' :: Bool -> a -> a -> a
if' True x _ = x
if' False _ y = y

Now you can upgrade isSquare to a switching function by composing with if' :

    if' . isSquare :: Eq a => RectangleSize a -> b -> b -> b

And now we can use the applicative instance to distribute the rectangle to all components:

areaOrPerimeter = if'.isSquare <*> area <*> perimeter

Actually not too shabby IMO.

To chain two single parameter functions f and g , . is pretty forward. To chain g :: c -> d and f :: a -> b -> c , use (g .) . f (g .) . f . That way, you can write perimeter as

perimeter = ((2*) .) . (+)

one parameter can be removed with <*> , do that twice for two parameters:

import Control.Applicative(liftA2)
import Data.Bool(bool)

areaOrPerimeter :: Double -> Double -> Double
areaOrPerimeter = bool <<$>> (*) <<*>> ((2*) .) . (+) <<*>> (==)
   where infixl 4 <<*>>
         (<<*>>) = liftA2 (<*>)
         infixl 5 <<$>>
         (<<$>>) = fmap . fmap

Writing point-free is a nice exercise, but don't expect to understand this in 6 months from now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM