17

在Haskell 中的默认功能组合问题的评论中,人们提到为 制作一个Num实例a -> r,所以我想我会尝试使用函数符号来表示乘法:

{-# LANGUAGE TypeFamilies #-}
import Control.Applicative

instance Show (a->r) where   -- not needed in recent GHC versions
  show f = " a function "

instance Eq (a->r) where     -- not needed in recent GHC versions
  f == g = error "sorry, Haskell, I lied, I can't really compare functions for equality"

instance (Num r,a~r) => Num (a -> r) where
  (+) = liftA2 (+)
  (-) = liftA2 (-)
  (*) = liftA2 (*)
  abs = liftA abs
  negate = liftA negate
  signum = liftA signum
  fromInteger a = (fromInteger a *)

请注意,fromInteger 定义意味着我可以写出3 412 和7 (2+8)70 的值,正如您所希望的那样。

然后一切都变得奇妙,有趣而奇怪!如果可以的话,请解释这种奇怪:

*Main> 1 2 3
18
*Main> 1 2 4
32
*Main> 1 2 5
50
*Main> 2 2 3
36
*Main> 2 2 4
64
*Main> 2 2 5
100
*Main> (2 3) (5 2)
600

[编辑:使用 Applicative 而不是 Monad,因为 Applicative 通常很棒,但它对代码没有太大影响。]

4

1 回答 1

21

2 3 4在与您的实例类似的表达式中,两者23都是函数。2实际上也是如此,(2 *)并且有一个 type Num a => a -> a3是一样的。2 3是 那么(2 *) (3 *)与 相同2 * (3 *)。以您为例,这liftM2 (*) 2 (3 *) 是 then liftM2 (*) (2 *) (3 *)。现在这个表达式可以在没有任何实例的情况下工作。

那么这是什么意思?好吧,liftM2因为函数是一种双重组合。特别liftM2 f g h是,与 相同\ x -> f (g x) (h x)。那么liftM2 (*) (2 *) (3 *)也是如此\ x -> (*) ((2 *) x) ((3 *) x)。稍微简化一下,我们得到:\ x -> (2 * x) * (3 * x). 所以现在我们知道这2 3 4实际上是(2 * 4) * (3 * 4).

那么,为什么liftM2for 函数会这样工作呢?让我们看一下 monad 实例(->) r(请记住,(->) r(r ->)我们不能编写类型级运算符部分):

instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  

return也是如此const>>=有点奇怪。我认为从join. 对于函数,join工作方式如下:

join f = \ x -> f x x

也就是说,它接受两个参数的函数,并通过使用该参数两次将其转换为一个参数的函数。很简单。这个定义也是有道理的。对于函数,join必须将两个参数的函数变成一个函数;唯一合理的方法是使用该参数两次。

>>=fmap紧随其后的是join。对于函数,fmap只是(.). 所以现在>>=等于:

h >>= f = join (f . h)

这只是:

h >>= f = \ x -> (f . h) x x

现在我们只是摆脱.得到:

h >>= f = \ x -> f (h x) x

所以现在我们知道了如何>>=工作,我们可以看看liftM2. liftM2定义如下:

liftM2 f a b = a >>= \ a' -> b >>= \ b' -> return (f a' b')

我们可以简单的一点一点。首先,return (f a' b')变成\ _ -> f a' b'. 结合\ b' ->,我们得到:\ b' _ -> f a' b'。然后b >>= \ b' _ -> f a' b'变成:

 \ x -> (\ b' _ -> f a' b') (b x) x

由于第二个x被忽略,我们得到:\ x -> (\ b' -> f a' b') (b x)然后减少到\ x -> f a' (b x). 所以这给我们留下了:

a >>= \ a' -> \ x -> f a' (b x)

同样,我们替换>>=

\ y -> (\ a' x -> f a' (b x)) (a y) y

这减少到:

 \ y -> f (a y) (b y)

这正是我们liftM2之前使用的!

希望现在的行为2 3 4完全有意义。

于 2012-08-26T21:45:46.123 回答