为什么我不能这样写:
data Color = R | G | B deriving Show
showColor :: Show Color
showColor = Show Color
main = do
putStrLn (showColor.show R)
putStrLn (showColor.show G)
为什么类的实例不是Haskell中的一等公民?
答案 0 :(得分:5)
很难讨论为什么语言的某个特征是这样的,因为只有该语言的设计者才能真正回答这个问题。然而,他们确定每个类型每个类最多只能有一个实例。这很可能是因为实例是隐含使用的。考虑一个班级
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编程语言没有类型类,但implicit instances具有类似的作用。在那里,隐式传递一个实例(如在Haskell中),但是如果需要,您可以手动覆盖隐式参数并使用特殊语法来指定不同的实例。
答案 1 :(得分:0)
在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
然而,没有人这样做。相反,他们明确传递参数:
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
类 - 但在这种情况下,只能有一个实例(因为那是要点)。
答案 2 :(得分:0)
类型类背后的思想在于名称:类型类是类(或集)类型(它们都支持给定的接口)。
例如Eq
是支持相等的类型类。
类型为Eq a => [a] -> Maybe a
的函数指定类型变量a
的范围超出Eq
成员的类型;您需要证明您的类型是Eq
的成员才能调用此函数。
然后,实例通过演示类型如何支持接口,只是一个特定类型是类成员的建设性证据。
从这个角度来看,不仅仅是Haskell不支持多个实例;多个实例的想法没有意义。一个类型属于一个类或它不是; class / set的概念允许您查询成员资格,但不能存储多个不同的实现。
最重要的是,功能不应该能够为同一输入提供不同的返回值,因为您使用了不同的会员证明!现有的Haskell代码确实依赖于这个属性:经典的例子是Data.Map
中的函数,它们(几乎)都有一个Ord
约束,以便在{{{}}上运行1}}作为内部树。如果有可能(和正常)为一个类型设置许多不同的Map
实例并在每次调用时传递您选择的一个,那么这将是一个非常不安全的界面!
为有效实例必须符合的类定义法则的做法也取决于此属性。如果我编写一组相互关联的函数,假设Ord
的所有实例都支持monad定律,那么如果可以为运行在相同数据上的不同调用提供不同的实例,则很可能会破坏我的代码。
请注意,Scala 不具有类型类。它有什么隐含参数;有一个设计模式来自己使用隐式参数来实现类型类。但这是一个漏洞的抽象;你可以使用隐式参数来做一些不符合"类型集"的概念框架的事情。
Haskell基本上使用隐式参数传递实现类型类,但它强制执行抽象; Haskell不是要求对所有类型类的隐式参数的使用进行连贯选择,而是拒绝实例可以不连贯的任何情况。隐含参数是内部实现细节;问他们为什么不是一流的,有点像问为什么vtable不是OO语言的第一堂课。
这些天Haskell明确支持隐式参数传递(带扩展),因此您可能使用相同的设计模式来实现"多个实例"在您编写的代码中。但是使用该工具编写的代码期望隐式参数是参数,并且应该在任意传递不同值时起作用。使用类型类编写的代码期望类型类抽象是正在使用的。
答案 3 :(得分:-1)
你的问题很难理解主要是因为
showColour :: Show Color
showColour = Show Color
没有意义。 Show Color
如何属于Show Color
类型?
Show
可以被视为一种约束,因此最终Show Color
是Bool
,在我们的情况下将评估为True。
无论如何,使用模块可以实现静态范围。您可以轻松地执行类似
的操作module Color where
import qualified Prelude
data Color = ...
show :: Color -> String
show c = Prelude.show c
然后使用print (Color.show R)
。