我们如何在保持类型安全的同时通用地处理关联类型

How can we handle associated types generically while keeping type-safety

这是我的问题:

假设我有各种对象要处理,但它们共享相同的形式:我们有 Items,它们由一个字符串(比如一个 id)和一个 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 扩展,但编译器总是抱怨(如果需要可以提供更多示例,但最初打算保持合理大小的初始 post)。

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

非常感谢, 干杯!

几乎可以肯定,在 MPTCs/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

也就是说,这里闻起来很不对劲。重复代码的数量让我怀疑您对如何从您喜欢的其他语言进行设置提出了错误的想法。但是,如果不进一步了解这些定义的动机,就很难多说如何修复它。