Haskell中如何实现ToYao类的默认泛型与特化实例
问题说明
我定义了ToYao和ToValue两个类型族,其中Value类型会转换为元组形式的两个Yao。目前ToYao已有字符、字符列表等转换实例,但希望[Value]类型能转换为([Yao], [Yao])的形式。我想了解如何为该类设置默认泛型实例,同时保留特化实例;已知GHC匹配实例仅看箭头右侧,希望让GHC优先匹配特化实例,无匹配时再使用泛型实例。
原始代码
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} -- | 仅能为0或1的姚字面值 newtype Yao = Yao Char deriving (Show, Eq) -- | ToYao类型族提供通过`toYao`函数将多种类型转换为Yao的方法 class ToYao a where -- | Yao的类型族包装器 type YaoType a {-| 将类型转换为对应的Yao类型,若输入不是'0'或'1'则抛出错误。 >>> toYao '1' Right (Yao '1') >>> toYao "101010" Right [Yao '1',Yao '0',Yao '1',Yao '0',Yao '1',Yao '0'] >>> toYao $ Just '1' Right $ Just (Yao '1') -} toYao :: a -> Either String (YaoType a) -- | 仅为'0'和'1'字符实现ToYao instance ToYao Char where type YaoType Char = Yao toYao c | c `elem` ['0', '1'] = Right $ Yao c | otherwise = Left "Yao只能是'0'或'1'" instance ToYao Value where type YaoType Value = (Yao, Yao) toYao v | v == Value '6' = toYao ('0','1') | v == Value '7' = toYao ('1','1') | v == Value '8' = toYao ('0','0') | v == Value '9' = toYao ('1','0') | otherwise = Left "" -- 此情况不会发生 instance {-# OVERLAPS #-} ToYao [Value] where type YaoType [Value] = ([Yao], [Yao]) toYao xs | length xs == 6 = bimap sequence sequence $ unzip <$> traverse toValue xs | otherwise = Left "[Yao]和[Value]的长度必须为6" -- | 为字符串和字符列表实现ToYao instance {-# OVERLAPS #-} ToYao a => ToYao [a] where type YaoType [a] = [YaoType a] toYao xs | length xs == 6 = traverse toYao xs | otherwise = Left "[Yao]的长度必须为6" -- | 仅能为6、7、8或9的值字面值 newtype Value = Value Char deriving (Show, Eq) -- | ToValue类型族提供通过`toValue`函数将多种类型转换为Value的方法 class ToValue a where -- | Yao的类型族包装器 type ValueType a {-| 将类型转换为对应的Value类型,若输入不是'6'、'7'、'8'或'9'则抛出错误。 >>> toValue '7' Value '7' >>> toValue "787879" [Value '7',Value '8', Value '7', Value '8', Value '7',Value '9'] >>> toValue $ Just 9 Just (Value '9') -} toValue :: a -> Either String (ValueType a)
解决方案
要实现特化实例优先、泛型实例作为默认的效果,可按以下步骤调整代码:
1. 修正代码顺序与实例优先级标记
GHC的实例匹配遵循"具体实例优先于约束实例"的规则,结合{-# OVERLAPPING #-}和{-# OVERLAPPABLE #-}pragma可明确实例优先级:
- 特化实例(如
ToYao [Value])标记为{-# OVERLAPPING #-},表示它会优先被匹配; - 泛型默认实例(如
ToYao a => ToYao [a])标记为{-# OVERLAPPABLE #-},表示它可被特化实例覆盖; - 必须先定义
Value类型,再编写其对应的ToYao实例,否则会触发未定义类型的编译错误。
2. 调整后的核心代码片段
-- 先定义Value类型,再编写实例 newtype Value = Value Char deriving (Show, Eq) instance ToYao Value where type YaoType Value = (Yao, Yao) toYao v | v == Value '6' = toYao ('0','1') | v == Value '7' = toYao ('1','1') | v == Value '8' = toYao ('0','0') | v == Value '9' = toYao ('1','0') | otherwise = Left "无效的Value值" -- 特化实例:优先匹配[Value]类型 instance {-# OVERLAPPING #-} ToYao [Value] where type YaoType [Value] = ([Yao], [Yao]) toYao xs | length xs == 6 = do valuePairs <- traverse toYao xs let (yaos1, yaos2) = unzip valuePairs pure (yaos1, yaos2) | otherwise = Left "[Value]的长度必须为6" -- 默认泛型实例:仅当无特化实例匹配时生效 instance {-# OVERLAPPABLE #-} ToYao a => ToYao [a] where type YaoType [a] = [YaoType a] toYao xs | length xs == 6 = traverse toYao xs | otherwise = Left "列表长度必须为6"
3. 关键说明
- 优先级控制:
OVERLAPPING标记的实例会被GHC优先选择,确保[Value]类型使用特化的转换逻辑,其他列表类型(如[Char])则使用泛型实例; - 类型一致性:每个
ToYao实例的YaoType必须与toYao函数的返回值类型一致,GHC会自动验证这一点; - 编译依赖:类型定义必须在其对应的实例之前,否则会出现"未定义类型"的编译错误。
内容的提问来源于stack exchange,提问作者Atavixion
相关产品推荐
相关产品推荐

