6

如何在 Haskell 中编写如下内容:

showSquare :: (Show a, Num a) => a -> String
showSquare x = "The square of " ++ (show x) ++ " is " ++ (show (x * x))

showSquare :: (Show a, not Num a) => a -> String
showSquare x = "I don't know how to square " ++ (show x)

基本上,类似于C++ 中的boost::enable_if

GHC 扩展没问题。

4

4 回答 4

6

你为什么要这个?类型检查器确保你永远不会调用在第一种情况下showSquare不是 a 的东西。Haskell中没有Numinstanceof,因为所有内容都是静态输入的。

它不适用于任意类型:您只能定义自己的类型 class,例如

class Mine a where
  foo :: a -> String

instance (Num a) => Mine a where
  foo x = show x*x

您可以为其他类添加更多实例,但您不能只instance Mine a为任意的a. 额外的instance (Show a) => ...也无济于事,因为也不允许重叠实例(该链接描述了一种解决方法,但它需要相当多的额外机器)。

于 2012-08-10T06:45:17.030 回答
3

首先,根本不可能为同一函数的不同方程赋予不同的类型签名。任何函数只能有一种类型,无论它有多少方程。

其次,负约束在 Haskell 中没有(不会)有任何合理的意义。回想一下类约束的含义:

f :: Num a => a -> a -> a
f x y = x + y

Num a在类型中f意味着我们可以将任何类型的类方法应用于Num类型的值a。我们有意识地不命名具体类型以获得通用行为。本质上,我们是在说“我们不关心a究竟是什么,但我们确实知道Num操作适用于它”。因此,我们可以在and上使用Num方法,但仅此而已,也就是说,除了 and 上的方法,我们不能使用任何东西。这就是类型类约束是什么以及为什么需要它们。他们正在为函数指定通用接口。xyNumxy

现在考虑您的假想not Num a约束。这个声明带来了什么信息?好吧,我们知道a不应该Num。但是,这些信息对我们来说完全没有用。考虑:

f :: not Num a => a -> a
f = ???

你可以放置什么来代替????显然,我们知道我们不能放置什么。但除了这个签名没有更多的信息

f :: a -> a

唯一的操作f可能是id(嗯,undefined也是可能的,但那是另一回事)。

最后考虑你的例子:

showSquare :: (Show a, not Num a) => a -> String
showSquare x = "I don't know how to square " ++ (show x)

我不是故意给出您示例的第一部分,请参阅我的回答中的第一句话。你不能有不同类型的不同方程。但仅此功能是完全没用的。您可以在这里安全地删除not Num a约束,它不会改变任何东西。

在静态类型的 Haskell 中,这种负约束的唯一用途是在您提供例如 -constrainted 变量时产生编译时Int错误not Num a。但我认为这没有用。

于 2012-08-10T07:07:13.700 回答
3

如果我真的,绝对需要这样的东西(我不相信我曾经有过),我认为这是 Haskell 中最简单的方法:

class Show a => ShowSquare a where
  showSquare :: a -> String
  showSquare a = "I don't know how to square " ++ (show a)

instance ShowSquare Int where
  showSquare = showSquare'

instance ShowSquare Double where
  showSquare = showSquare'

-- add other numeric type instances as necessary

-- make an instance for everything else
instance Show a => ShowSquare a

showSquare' :: (Show a, Num a) => a -> String
showSquare' x = "The square of " ++ (show x) ++ " is " ++ (show (x * x))

显然,这需要重叠的实例。有些人可能会抱怨所需的样板,但它很少。5 或 6 个实例将涵盖大多数数字类型。

您可能可以使用Advanced Overlap wiki 页面中的想法来完成某些工作。请注意,技术仍然需要明确列出实例,因此是否比这更好可能是个人喜好问题。

也可以通过编写 TH 拼接而不是函数来解决模板 haskell 的问题。拼接必须reify ''Num在调用站点确定Num实例是否在范围内,然后选择适当的函数。但是,使这项工作可能比手动写出 Num 实例更麻烦。

于 2012-08-10T12:45:38.787 回答
2

依赖于“not a Num a”在 Haskell 中非常脆弱,而在 C++ 中并不脆弱。

在 C++ 中,类被定义在一个位置(关闭),而 Haskell 类型的类是开放的,并且可以在模块 C 中声明来自模块 A 的数据和来自模块 B 的类的实例。

类型类的(无扩展)解析有一个指导原则,即导入像“C”这样的模块永远不会改变类型类的先前解析。

如果任何递归导入的模块(例如从另一个包中)定义了“instance Num Custom”,则预期“不是 Num Custom”的代码将会改变。

多态性还有一个额外的问题。考虑模块“D”中的一个函数

useSS :: Show a => a -> Int -> [String]
useSS a n = replicate n (showSquare a)

data Custom = Custom deriving Show
use1 :: Int -> String
use1 = useSS Custom -- no Num Custom in scope

现在考虑另一个包中的模块“E”,它导入了上述模块“D”

instance Num Custom
use2 :: Int -> String
use2 = useSS Custom -- has a Num Custom now

应该评估什么(use1 1)(use2 1)您想使用带有此类陷阱的语言吗?Haskell 试图通过有原则的设计来阻止这个陷阱的存在。

这种特殊的重载在 C++ 解析中无处不在,但正是 Haskell 旨在避免的。使用GHC 扩展可以做这样的事情,但是必须小心不要造成危险的陷阱,并且不鼓励这样做。

于 2012-08-10T13:10:11.580 回答