如何在保证类型安全的情况下处理关联类型?


我的问题是:

假设我有各种各样的对象要处理,但它们具有相同的形式:我们有由字符串(例如id)组成的Items和可以是任何东西的Content。所以分解后的问题可以总结如下:我希望能够从一个content中产生一个item,以一种通用的方式将它与id关联起来,但是我希望类型系统约束接口,这样我就知道我会返回传递的content的一个Item

下面是一个尝试使用FunctionalDependencies的例子:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
main :: IO ()
main = do
putStrLn $ show $ handleContent TitiAssociation $ read "TitiContent"
putStrLn $ show $ handleContent TotoAssociation $ read "TotoContent"
-- Expected
-- "TitiItem"
-- "TotoItem"
-- definition of the domain
data TitiContent = TitiContent String deriving (Read, Show)
data TotoContent = TotoContent String deriving (Read, Show)
data TitiItem = TitiItem String TitiContent deriving (Read, Show)
data TotoItem = TotoItem String TotoContent deriving (Read, Show)
--
class (Read a, Show a) => Content a where
class (Read a, Show a) => Item a where
instance Content TitiContent where
instance Content TotoContent where
instance Item TitiItem where
instance Item TotoItem where
-- Association of types
class (Content contentType, Item itemType) => ItemContentAssociation association contentType itemType | association -> contentType, association -> itemType, itemType -> association where
-- tokens to identify the types which will be handled
data TitiAssociation = TitiAssociation
data TotoAssociation = TotoAssociation
instance ItemContentAssociation TitiAssociation TitiContent TitiItem where
instance ItemContentAssociation TotoAssociation TotoContent TotoItem where
-- generic function for handling
handleContent :: (ItemContentAssociation association contentType itemType) => association -> contentType -> itemType
handleContent TitiAssociation TitiContent = TitiItem
handleContent TotoAssociation TotoContent = TotoItem

但是编译器报错:

tmp.hs:41:15: error:
* Couldn't match expected type `ass' with actual type `TitiAss'
`ass' is a rigid type variable bound by
the type signature for:
handleContent :: forall ass contentType itemType.
ItemContentAss ass contentType itemType =>
ass -> contentType -> itemType
at tmp.hs:40:1-92
* In the pattern: TitiAss
In an equation for `handleContent':
handleContent TitiAss TitiContent = TitiItem
* Relevant bindings include
handleContent :: ass -> contentType -> itemType
(bound at tmp.hs:41:1)

我也尝试过各种类型的TypeFamilies扩展,但编译器总是抱怨(如果需要,可以提供更多的例子,但最初打算保持一个合理的大小)。

我是函数式编程领域的新手,所以请不要犹豫,提出新的方法,因为我确信我忽略了问题的许多方面。

提前感谢,干杯!

几乎可以肯定,在mptc/FunDeps世界和TF世界中,正确的答案是将handleContent作为ItemContentAssociation的方法。下面是类型族的具体情况,因为你在评论中问了这个问题。

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
main :: IO ()
main = do
putStrLn $ show $ handleContent TitiAssociation $ read "TitiContent "titi""
putStrLn $ show $ handleContent TotoAssociation $ read "TotoContent "toto""
-- Expected
-- "TitiItem"
-- "TotoItem"
-- definition of the domain
data TitiContent = TitiContent String deriving (Read, Show)
data TotoContent = TotoContent String deriving (Read, Show)
data TitiItem = TitiItem String TitiContent deriving (Read, Show)
data TotoItem = TotoItem String TotoContent deriving (Read, Show)
--
class (Read a, Show a) => Content a where
class (Read a, Show a) => Item a where
instance Content TitiContent where
instance Content TotoContent where
instance Item TitiItem where
instance Item TotoItem where
-- Association of types
class (Content (ContentType association), Item (ItemType association)) =>
ItemContentAssociation association
where
type ContentType association = content | content -> association
type ItemType association
handleContent :: association -> ContentType association -> ItemType association
-- tokens to identify the types which will be handled
data TitiAssociation = TitiAssociation
data TotoAssociation = TotoAssociation
instance ItemContentAssociation TitiAssociation where
type ContentType TitiAssociation = TitiContent
type ItemType TitiAssociation = TitiItem
handleContent TitiAssociation c@(TitiContent s) = TitiItem s {- what string should be used here? if s, why also have c? -} c
instance ItemContentAssociation TotoAssociation where
type ContentType TotoAssociation = TotoContent
type ItemType TotoAssociation = TotoItem
handleContent TotoAssociation c@(TotoContent s) = TotoItem s {- what string? -} c

也就是说,这里有什么不对劲的地方。大量的重复代码让我怀疑您从其他喜爱的语言中引入了错误的设置理念。但是,如果不更多地了解这些定义的动机,就很难更多地讨论如何修复它。