Haskell类定义类型歧义问题与带校验newtype变换实现咨询
解决方案
你最初类型类编译失败的核心原因是多参数类型类默认没有绑定参数之间的对应关系,GHC在类型推断时无法关联a和对应的容器类型f、元素类型b、错误类型err。推荐使用关联类型(Type Family)的方案实现通用transform默认方法:
首先需要开启对应的GHC扩展:
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-}
定义统一的类型类,把所有关联类型和目标类型a绑定,直接内置默认的transform实现:
class Validated a where -- 定义a对应的容器类型(你的示例中为[]) type InnerContainer a :: * -> * -- 定义a内部容器的元素类型(你的示例中为Double) type InnerElem a :: * -- 定义校验失败的错误类型(你的示例中为String) type ValidationErr a :: * -- 需要手动实现的带校验构造方法 create :: InnerContainer a (InnerElem a) -> Either (ValidationErr a) a -- 需要手动实现的内部数据提取方法 get :: a -> InnerContainer a (InnerElem a) -- 通用默认transform实现,不需要每个实例单独编写 transform :: (InnerElem a -> InnerElem a) -> a -> Either (ValidationErr a) a transform f val = create $ f <$> get val
针对你定义的A类型,仅需要实现类型类的三个基础方法即可,transform会自动生效:
newtype A = A [Double] instance Validated A where type InnerContainer A = [] type InnerElem A = Double type ValidationErr A = String create [] = Left "err" create xs = Right $ A xs get (A xs) = xs
使用示例:
main :: IO () main = do let a1 = create [1,2,3] :: Either String A res = fmap (transform (*2)) a1 print res -- 输出 Right (Right (A [2.0,4.0,6.0]))
替代方案
如果你更习惯功能依赖的写法,也可以通过FunctionalDependencies扩展实现,核心是通过a -> f b err声明a可以唯一确定其他三个类型参数:
{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleInstances #-} class Functor f => Transform a f b err | a -> f b err where create :: f b -> Either err a get :: a -> f b transform :: (b -> b) -> a -> Either err a transform f a = create $ f <$> get a instance Transform A [] Double String where create [] = Left "err" create xs = Right $ A xs get (A xs) = xs
如果你的场景不需要太多个性化扩展,也可以不使用类型类,直接在模块封装时不导出newtype的构造函数,只导出create、get和通用的transform高阶函数,也能达到避免非法构造、复用转换逻辑的效果。
内容的提问来源于stack exchange,提问作者Umaxo
相关产品推荐
相关产品推荐

