Haskell 为什么类的实例不能用作值?
为什么我不能写这样的东西:Haskell 为什么类的实例不能用作值?,haskell,Haskell,为什么我不能写这样的东西: data Color = R | G | B deriving Show showColor :: Show Color showColor = Show Color main = do putStrLn (showColor.show R) putStrLn (showColor.show G) 为什么在哈斯克尔,阶级的例子不是一等公民 很难讨论为什么语言的某个特性是这样的,因为只有语言的设计者才能真正回答这个问题。然而,他们决定每种类型最多只
data Color = R | G | B deriving Show
showColor :: Show Color
showColor = Show Color
main = do
putStrLn (showColor.show R)
putStrLn (showColor.show G)
为什么在哈斯克尔,阶级的例子不是一等公民 很难讨论为什么语言的某个特性是这样的,因为只有语言的设计者才能真正回答这个问题。然而,他们决定每种类型最多只能有一个类的实例。这可能是由于实例是隐式使用的。考虑一个类
class Foo a where foo :: a -> String
这个模块呢
import A -- defines an instance Foo Int
bar :: Int -> String
bar n = "The number is " ++ foo n
bar
函数隐式引用模块A
的实例。现在假设我们添加另一个导入
import A -- defines an instance Foo Int
import B -- defines another instance Foo Int
bar :: Int -> String
bar n = "The number is " ++ foo n
这现在是模棱两可的。Haskell可能已经提供了一个语法来消除歧义,但是却选择了不允许它。一个优点是,无论谁读取代码,都可以更容易地找到所使用的实例,因为只有一个实例
不过,可以部分模拟多个实例。GHC扩展允许定义隐式参数,允许在每次函数调用时为这些参数指定不同的“实例”:
{-# LANGUAGE ImplicitParams #-}
data Color = R | G | B
showColor :: (?showC :: Color -> String) => Color -> String
showColor c = "The color is: " ++ ?showC c
main :: IO ()
main = do
let ?showC = \c -> case c of R -> "Red" ; _ -> "Not Red"
in putStrLn (showColor B)
let ?showC = \c -> case c of G -> "Green" ; _ -> "Not Green"
in putStrLn (showColor B)
上面的输出是:
The color is: Not Red
The color is: Not Green
Agda编程语言没有类型类,但有起类似作用的类型类。在那里,一个实例是隐式传递的(如在Haskell中),但如果需要,您可以手动重写隐式参数并使用特殊语法指定不同的实例。在Haskell中,类型类是非常隐式的。(例如,您不能显式导出或不导出类实例,这有时会让人恼火。) 类型类的全部要点在于它是一组函数,在这些函数中会自动选择正确的类型。如果您希望能够为每种类型传递不同的函数,那么就这样做吧 例如,有一个类
class Monoid m where
mempty :: m
mappend :: m -> m -> m
所以我们可以做一些类似的事情
fold :: Monoid m => [m] -> m
fold ( []) = mempty
fold (x:xs) = x `mappend` fold xs
module Color where
import qualified Prelude
data Color = ...
show :: Color -> String
show c = Prelude.show c
然而,没有人这样做。相反,它们显式传递参数:
foldr :: (x -> y -> y) -> y -> [x] -> y
foldr f y0 ( []) = y0
foldr f y0 (x:xs) = f x (foldr f y0 xs)
因为这样,使用多个不同的函数/起始值折叠相同的数据类型要容易得多
如果希望能够以多种不同的方式“显示”值,只需将show函数作为参数传入即可。如果您希望函数自动为数据类型选择正确的show函数,请使用
show
类-但在这种情况下,只能有一个实例(因为这是一个要点)。您的问题很难理解,主要是因为
showColour :: Show Color
showColour = Show Color
没什么意义。Show Color
如何属于Show Color
类型?
Show
可以看作是一个约束,因此最后一个Show Color
是一个Bool
,在我们的例子中,它的计算结果将为True
无论如何,可以使用模块实现静态作用域。你可以很容易地做类似的事情
fold :: Monoid m => [m] -> m
fold ( []) = mempty
fold (x:xs) = x `mappend` fold xs
module Color where
import qualified Prelude
data Color = ...
show :: Color -> String
show c = Prelude.show c
然后使用
print(Color.show R)
类型类背后的概念是:类型类是一个类型类(或类型集)(所有类型都支持给定的接口)
例如,Eq
是一类支持相等的类型
类型为Eq A=>[A]->可能为A
的函数指定类型变量A
在属于Eq
成员的类型范围内;在调用此函数之前,您需要证明您的类型是Eq
的成员
然后,通过演示特定类型如何支持接口,实例只是一个特定类型是类成员的构造性证明
这样看来,不仅仅是Haskell不支持多个实例;多实例的想法没有意义。类型是否在类中;类/集的概念允许您查询成员资格,但不能存储多个不同的实现
最关键的是,函数不应该仅仅因为使用了不同的成员资格证明就能够为同一输入提供不同的返回值!现有的Haskell代码确实依赖于这个属性:典型的例子是Data.Map
中的函数,它们(几乎)都有Ord
约束,以便在Map
内部作为树进行操作。如果一个类型有许多不同的Ord
实例并在每次调用中传递一个您选择的实例是可能的(也是正常的),那么这将是一个非常不安全的接口
为有效实例必须符合的类定义法律的实践也将取决于此属性。如果我编写了一组相互关联的函数,假设Monad
的所有实例都遵守Monad定律,那么如果可以为对同一数据进行操作的不同调用提供不同的实例,我的代码很可能会被破坏
请注意,Scala没有类型类。它所具有的是隐式参数;有一种设计模式可以使用隐式参数自己实现类型类。但这是一个漏洞百出的抽象概念;您可以使用隐式参数来执行不符合“类型集”概念框架的操作
Haskell基本上使用隐式参数传递实现类型类,但它强制抽象;Haskell不要求一致地选择类型类的所有隐式参数,而是拒绝任何实例可能不一致的情况。隐式参数是一个内部实现细节;问为什么它们不是一流的,有点像问为什么vtables在OO语言中不是一流的
现在Haskell确实明确支持隐式参数传递(带有扩展),因此您可能可以使用相同的设计模式在编写的代码中实现“多个实例”。但是使用该工具编写的代码期望隐式参数是参数,并且在任意传递不同的值时应该工作。使用类型类编写的代码要求使用类型类抽象。为什么需要它?你可以就这么说