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)
CodePudding user response:
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 -> f x (g x)
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.
CodePudding user response:
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
. 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.