我希望能够使用二元运算符在haskell中组合数值函数。因此,例如,使用一元数字函数:
f*g
应转换为:
\x -> (f x)*(g x)
并且类似地添加。让你自己的操作符执行此操作非常简单,但我真的只想让Num a => a -> a
函数成为Num的一个实例,但我不知道该怎么做。
我也想让这个arity成为通用的,但是在Haskell中执行arity泛型函数的难度可能会太麻烦,所以定义单独的Num a => a -> a -> a
,{可能更好一些{1}}等......实例数量相当大。
答案 0 :(得分:13)
Applicative
有一个(->) a
的实例,这意味着所有函数都是适用的函子。以您描述的方式编写任何函数的现代习语是使用Applicative
,如下所示:
(*) <$> f <*> g
liftA2 (*) f g -- these two are equivalent
这使操作变得清晰。这两种方法都略显冗长,但在我看来更清楚地表达组合模式。
此外,这是一种更为通用的方法。如果你理解这个习语,你就可以在许多其他语境中应用它,而不仅仅是Num
。如果您不熟悉Applicative
,则可以从Typeclassopedia开始。如果您在理论上有倾向,可以查看McBride and Patterson's famous article。 (为了记录,我在普通意义上使用“成语”,但注意双关语。)
Num b => Num (a -> b)
NumInstances package中提供了您想要的实例(以及其他实例)。您可以复制发布的@genisage实例;它们在功能上完全相同。 (@genisage更明确地写出来;比较这两个实现可能很有启发性。)在Hackage上导入库有一个好处,就是突出显示你正在使用孤立实例的其他开发人员。
Num b => Num (a -> b)
存在问题。简而言之,2
现在不仅是一个数字,而且是一个具有无数个参数的函数,它们都忽略了这些参数。 2 (3 + 4)
现在等于2
。使用整数文字作为函数几乎肯定会产生意外和不正确的结果,并且没有办法警告程序员没有运行时异常。
正如2010 Haskell Report section 6.4.1中所述,“整数文字表示函数fromInteger
应用于类型Integer
的适当值。”这意味着在源代码或GHCi中编写2
或12345
等同于撰写fromInteger 2
或fromInteger 12345
。因此,任何一个表达式都具有类型Num a => a
。
因此,fromInteger
在Haskell中绝对是普及。通常这很好用;当你在源代码中写一个数字时,你得到一个数字 - 相应的类型。但是,对于函数的Num
实例,fromInteger 2
的类型很可能是a -> Integer
或a -> b -> Integer
。事实上,GHC很乐意用一个函数替换文字2
,而不是一个数字 - 也是一个特别危险的函数,它会丢弃给它的任何数据。 (fromInteger n = \_ -> n
或const n
;即抛弃所有参数,只需提供n
。)
通常,您可以通过不实现不适用的类成员,或通过使用undefined
实现它们来逃避,其中任何一个都会产生运行时错误。出于同样的原因,这不能解决目前的问题。
Num a => Num (a -> a)
如果您愿意限制自己增加并添加Num a => a -> a
类型的一元函数,我们可以稍微改善fromInteger
问题,或者至少2 (3 + 5)
等于{{} 1}}而不是16
。答案只是将2
定义为fromInteger 3
而不是(*) 3
:
const 3
instance (a ~ b, Num a) => Num (a -> b) where
fromInteger = (*) . fromInteger
negate = fmap negate
(+) = liftA2 (+)
(*) = liftA2 (*)
abs = fmap abs
signum = fmap signum
请注意,虽然这可能在道德上等同于ghci> 2 (3 + 4)
14
ghci> let x = 2 ((2 *) + (3 *))
ghci> :t x
x :: Num a => a -> a
ghci> x 1
10
ghci> x 2
40
,但必须使用等式约束(需要Num a => Num (a -> a)
或GADTs
)来定义它。否则,我们会因TypeFamilies
之类的内容而出现歧义错误。抱歉,我不解释原因。基本上,等式约束(2 3) :: Int
允许推断或声明的a ~ b => a -> b
类型在推理期间传播到b
。
有关此实例的原因和方式的详细解释,请参阅Numbers as multiplicative functions (weird but entertaining)的答案。
不要暴露任何包含的模块 - 或导入包含的模块 - 或者导入导入包含...的模块的模块 - 这些实例中的任何一个都不了解orphan instances的问题/或相应地警告您的用户。
答案 1 :(得分:5)
具有通用arity的实例
instance Num b => Num (a->b) where
f + g = \x -> f x + g x
f - g = \x -> f x - g x
f * g = \x -> f x * g x
negate f = negate . f
abs f = abs . f
signum f = signum . f
fromInteger n = \x -> fromInteger n
编辑:正如Christian Conkle指出的那样,这种方法存在问题。如果您计划将这些实例用于任何重要事项或只是想了解问题,您应该阅读他提供的资源并自行决定这是否符合您的需求。我的目的是使用自然符号提供一种简单的方法来使用数字函数,尽可能简单的实现。