在问题Tacit function composition in 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 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
[编辑:使用Applicative而不是Monad是因为Applicative通常很出色,但与代码没有多大区别。]
最佳答案
在带有实例的2 3 4
这样的表达式中,2
和3
都是函数。因此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
的monad实例(请记住(->) 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
的行为完全有意义。关于haskell - 数字作为乘法函数(很奇怪但很有趣),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12133932/