使数字函数成为Num的一个实例?

时间:2014-10-22 19:02:16

标签: haskell pointfree

我希望能够使用二元运算符在haskell中组合数值函数。因此,例如,使用一元数字函数:

f*g

应转换为:

\x -> (f x)*(g x)

并且类似地添加。让你自己的操作符执行此操作非常简单,但我真的只想让Num a => a -> a函数成为Num的一个实例,但我不知道该怎么做。

我也想让这个arity成为通用的,但是在Haskell中执行arity泛型函数的难度可能会太麻烦,所以定义单独的Num a => a -> a -> a,{可能更好一些{1}}等......实例数量相当大。

2 个答案:

答案 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中编写212345等同于撰写fromInteger 2fromInteger 12345。因此,任何一个表达式都具有类型Num a => a

因此,fromInteger在Haskell中绝对是普及。通常这很好用;当你在源代码中写一个数字时,你得到一个数字 - 相应的类型。但是,对于函数的Num实例,fromInteger 2的类型很可能是a -> Integera -> b -> Integer。事实上,GHC很乐意用一个函数替换文字2,而不是一个数字 - 也是一个特别危险的函数,它会丢弃给它的任何数据。 (fromInteger n = \_ -> nconst 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指出的那样,这种方法存在问题。如果您计划将这些实例用于任何重要事项或只是想了解问题,您应该阅读他提供的资源并自行决定这是否符合您的需求。我的目的是使用自然符号提供一种简单的方法来使用数字函数,尽可能简单的实现。