Haskell:类型类问题

Posted

技术标签:

【中文标题】Haskell:类型类问题【英文标题】:Haskell: type classes question 【发布时间】:2009-06-19 20:33:27 【问题描述】:

我希望定义以下类型类Mapping

-# LANGUAGE MultiParamTypeClasses #-

class Mapping k v m where
  empty :: m v
  insert :: k -> v -> m v -> m v
  search :: k -> m v -> Maybe v
  delete :: k -> m v -> m v

Mapping 的一个实例是Data.Map.Map

-# LANGUAGE ..., FlexibleInstances #-

instance Ord k => Mapping k v (Map.Map k) where
  empty = Map.empty
  search = Map.lookup
  insert = Map.insert
  delete = Map.delete

现在我想创建一个类型Trie :: * -> * -> * -> * 比如

-# LANGUAGE ..., UndecidableInstances #-

data Trie m k v = Trie 
  trValue :: Maybe v,
  trChildren :: m (Trie m k v)


instance Mapping k (Trie m k v) m => Mapping [k] v (Trie m k) where
  search [] tree = trValue tree
  search (x:xs) tree =
    search xs =<< search x (trChildren tree)

到目前为止一切顺利, 现在我还想定义Trieinsertempty,这就是我遇到问题的地方。

我将讨论empty,因为它更简单,insert 无论如何都需要它。 如果我试试这个:

instance Mapping k (Trie m k v) m => Mapping [k] v (Trie m k) where
  empty = Trie  trValue = Nothing, trChildren = empty 
  ...

这让我得到以下错误:

Could not deduce (Mapping k (Trie m k1 v) (m k1))
  from the context (Mapping [k1] v (Trie m k1),
                    Mapping k1 (Trie m k1 v) (m k1))
  arising from a use of `empty' at test.hs:27:49-53
Possible fix:
  add (Mapping k (Trie m k1 v) (m k1)) to the context of
    the instance declaration
  or add an instance declaration for (Mapping k (Trie m k1 v) (m k1))
In the `trChildren' field of a record
In the expression: Trie trValue = Nothing, trChildren = empty
In the definition of `empty':
    empty = Trie trValue = Nothing, trChildren = empty

我已经尝试并试图解决它但失败了。

有人知道如何让它工作吗?有没有可能?

【问题讨论】:

顺便说一句,我建议从类型类定义中删除v(但将其保留在方法的签名中)。你不需要它,至少对于你迄今为止给出的所有结构来说,因为它们都将采用任何包含的类型,它使一切变得更简单。 【参考方案1】:

添加functional dependency:

-# LANGUAGE ..., FunctionalDependencies #-

class Mapping k v m | m -> k where
   ...

您之前遇到的错误是因为程序对在某些地方使用哪种键类型不明确,因此出现了关于类型变量k1 的错误。函数依赖允许从映射类型推导出键类型(通过声明只有一个可能的答案),从而解决了这个问题。

【讨论】:

谢谢!我对它进行了编码(见下文),由于您关于从类型类定义中删除 v 的建议,结果非常简洁。【参考方案2】:

演示 Ganesh 答案的代码:

-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, StandaloneDeriving, UndecidableInstances #-

import qualified Data.Map as Map
import Data.Maybe (fromMaybe)

class Mapping k m | m -> k where             
  empty :: m v
  insert :: k -> v -> m v -> m v
  search :: k -> m v -> Maybe v
  delete :: k -> m v -> m v

instance Ord k => Mapping k (Map.Map k) where
  empty = Map.empty
  search = Map.lookup
  insert = Map.insert
  delete = Map.delete

data Trie m v = Trie 
  trValue :: Maybe v,
  trChildren :: m (Trie m v)


deriving instance (Show v, Show (m (Trie m v))) => Show (Trie m v)

trieMod :: Mapping k m => Maybe v -> [k] -> Trie m v -> Trie m v
trieMod val [] trie = trie  trValue = val 
trieMod val (x:xs) trie =
  trie  trChildren = insert x newChild children 
  where
    children = trChildren trie
    newChild = trieMod val xs prevChild
    prevChild = fromMaybe empty . search x $ children

instance Mapping k m => Mapping [k] (Trie m) where
  empty = Trie  trValue = Nothing, trChildren = empty 
  search [] trie = trValue trie
  search (x:xs) trie =
    search xs =<< search x (trChildren trie)
  insert key val = trieMod (Just val) key
  delete = trieMod Nothing

type TernarySearchTree a = Trie (Map.Map a)

顺便说一句:如果不存在函数依赖关系,我们可能需要在烦人的接口上妥协,并使用函数表而不是类型类。

【讨论】:

@Titou:关于您的编辑 - 请参阅上面与 @GaneshSittampalam 讨论的关于从类型类定义中删除 v 的讨论。 ***.com/questions/1019928/…***.com/questions/1019928/… 感谢您花时间指出错误,因为这是一个错误的信念,如果没有您的信息,我不会质疑。

以上是关于Haskell:类型类问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Haskell 没有在函数签名中推断数据类型的类型类?

Haskell 的类型类和 Go 的接口

Haskell 多态性和类型类实例

Haskell入门篇七:类型类

Haskell中不同类型之间的关系

使用 haskell 读取和类型类 - 模棱两可的类型变量错误