数字作为乘法函数(奇怪但有趣)

And*_*ewC 17 haskell

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 *)
Run Code Online (Sandbox Code Playgroud)

请注意,fromInteger定义意味着我可以编写3 4哪个计算结果为12,并且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
Run Code Online (Sandbox Code Playgroud)

[编辑:使用Applicative而不是Monad因为Applicative很普遍,但它对代码没有多大影响.]

Tik*_*vis 21

在这样的表达2 3 4你的情况下,无论是23是函数.所以2实际上(2 *)并且有一种类型Num a => a -> a.3是一样的.2 3然后是(2 *) (3 *)哪个是相同的2 * (3 *).根据你的实例,这就是liftM2 (*) 2 (3 *) 那个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).

那么,为什么liftM2函数以这种方式工作?让我们来看看单子实例(->) r(记住,(->) r(r ->),但我们不能写的类型级运营商的部分):

instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  
Run Code Online (Sandbox Code Playgroud)

所以returnconst.>>=有点奇怪.我认为从这方面来看更容易join.对于函数,join工作方式如下:

join f = \ x -> f x x
Run Code Online (Sandbox Code Playgroud)

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

>>=fmap其次join.对于功能,fmap只是(.).所以现在>>=等于:

h >>= f = join (f . h)
Run Code Online (Sandbox Code Playgroud)

这只是:

h >>= f = \ x -> (f . h) x x
Run Code Online (Sandbox Code Playgroud)

现在我们摆脱.得到:

h >>= f = \ x -> f (h x) x
Run Code Online (Sandbox Code Playgroud)

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

liftM2 f a b = a >>= \ a' -> b >>= \ b' -> return (f a' b')
Run Code Online (Sandbox Code Playgroud)

我们可以一点一点地这样做.首先,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
Run Code Online (Sandbox Code Playgroud)

因为第二个x被忽略,我们得到:\ x -> (\ b' -> f a' b') (b x)然后减少到\ x -> f a' (b x).所以这让我们:

a >>= \ a' -> \ x -> f a' (b x)
Run Code Online (Sandbox Code Playgroud)

我们再次代替>>=:

\ y -> (\ a' x -> f a' (b x)) (a y) y
Run Code Online (Sandbox Code Playgroud)

这减少到:

 \ y -> f (a y) (b y)
Run Code Online (Sandbox Code Playgroud)

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

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

  • @AndrewC:我刚刚写下了我的思维过程,从我确切地知道'2 3 4'做了什么,所以它和其他人一样帮助自己:P. (2认同)