我正在审核一些代码,并遇到了以下宝石,我下注的是pointfree
输出的复制粘贴:
(对于这个特殊问题,我认为以下内容比通常foo
/ bar
更合适:P)
import Control.Monad (liftM2)
data Battleship = Battleship { x :: Int
, y :: Int
} deriving Show
placeBattleship :: Int -> Int -> Battleship
placeBattleship x' y' = Battleship { x = x', y = y' }
coordinates :: Battleship -> (Int, Int)
coordinates = liftM2 (,) x y
有人会善意地解释简化所需的步骤:
(i)coordinates b = (x b, y b)
至:
>(ii)coordinates = liftM2 (,) x y
?
特别是,我对使用liftM2
感到有点困惑,因为我甚至不知道monad潜伏在后台。
我知道(i)也可以表示为:coordinates s = (,) (x s) (y s)
但我不知道在哪里/如何继续。
P.S。以下是我怀疑它来自pointfree
(输出来自GHCI
而:pl
别名为pointfree
)的原因:
λ: :pl coordinates s = (x s, y s)
coordinates = liftM2 (,) x y
答案 0 :(得分:9)
这利用了Monad
的{{1}}实例,也称为"读者monad"。这是从特定类型到(->) r
的函数monad。 (看一下here的动机,了解它为什么存在。)
要了解它如何适用于各种功能,请将a
替换为m
中的(r ->
。例如,如果我们只做m a
,我们会得到:
liftM
......这只是功能构成。整齐。
我们可以为liftM :: (a -> b) -> (m a -> m b)
liftM :: (a -> b) -> ((r -> a) -> (r -> b))
:: (a -> b) -> (r -> a) -> (r -> b) -- simplify parentheses
执行相同的操作:
liftM2
所以我们看到的是用双参数函数组合两个单参数函数的方法。它是将正常函数组合推广到多个参数的一种方法。我们的想法是创建一个函数,通过传递两个单参数函数来获取单个liftM2 :: (a -> b -> c) -> m a -> m b -> m c
liftM2 :: (a -> b -> c) -> (r -> a) -> (r -> b) -> (r -> c)
,将两个参数传递给双参数函数。因此,如果我们有r
,f :: (r -> a)
和g :: (r -> b)
,我们会生成:
h :: (a -> b -> c)
现在,这如何适用于您的代码? \ r -> h (f r) (h r)
是双参数函数,(,)
和x
是y
类型的单参数函数(因为那是字段访问器的工作方式)。考虑到这一点:
Battleship -> Int
一旦你内置了像这样的多功能组合的想法,像这样的无点代码变得更加可读 - 不需要使用pointfree工具!在这种情况下,我认为非无版本的版本仍然更好,但是无点版本本身并不可怕。
答案 1 :(得分:5)
Monad liftM2
在这里工作的是monad (->) a
函数。这相当于Reader
monad,正如您之前所见。
回想liftM2
:
liftM2 :: Monad m => (a -> b -> r) -> m a -> m b -> m r
liftM2 f ma mb = do
a <- ma
b <- mb
return $ f a b
现在,如果我们将(,)
替换为f
,将x
替换为ma
,将y
替换为mb
,我们就会
liftM2 (,) x y = do
a <- x
b <- y
return $ (,) a b
由于x, y :: Battleship -> Int
相当于((->) Battleship) Int
,所以m ~ (->) Battleship
。函数monad定义为
instance Monad ((->) a) where
return x = const x
m >>= f = \a -> f (m a) a
本质上monad所做的功能是允许你从几个函数中提取输出,只要它们都具有相同的输入。一个更明确的例子可能是
test = do
a <- (^2)
b <- (^3)
c <- (^4)
d <- show
return (a, b, c, d)
> test 2
(4, 8, 16, "2")
答案 2 :(得分:1)
你可以轻松地重写
data Battleship = Battleship { x :: Int
, y :: Int
} deriving Show
placeBattleship :: Int -> Int -> Battleship
placeBattleship x y = Battleship x y
coordinates :: Battleship -> (Int, Int)
coordinates (Battleship x y) = (x, y)
这不是无点风格,而是非常简单