7

我正在查看一些代码并遇到以下 gem,我敢打赌它是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)但我不确定在哪里/如何进行。


PS以下是我怀疑它来自pointfree(输出来自GHCI:pl别名为pointfree)的原因:

λ: :pl coordinates s = (x s, y s)
coordinates = liftM2 (,) x y
4

3 回答 3

10

这利用了 的Monad实例(->) r,也称为“阅读器单子”。这是特定类型到a. (看看这里为什么它首先存在的动机。)

要查看它如何用于各种功能,请替换m(r ->in 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)

现在,这如何应用于您的代码?(,)是双参数函数,并且xy类型的单参数函数Battleship -> Int(因为这就是字段访问器的工作方式)。考虑到这一点:

liftM2 (,) x y = \ r -> (,) (x r) (y r)
               = \ r -> (x r, y r)

一旦你内化了像这样的多函数组合的想法,像这样的无点代码变得更具可读性——无需使用无点工具!在这种情况下,我认为非无点版本仍然更好,但无点版本本身并不可怕。

于 2014-11-19T23:42:54.203 回答
5

monadliftM2在这里工作的是函数 monad (->) aReader正如您之前可能看到的,这相当于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

所以现在如果我们用(,)for fxformayfor 代替mb,我们得到

liftM2 (,) x y = do
    a <- x
    b <- y
    return $ (,) a b

由于x, y :: Battleship -> Intwhich 等价于((->) 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")
于 2014-11-19T23:36:21.080 回答
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)

它不是无点风格,但很简单

于 2014-11-20T08:48:54.050 回答