在 Haskell 中为复合类型定义实例

7

我有一个类,它有一个类似于mappend的函数,但不是真正的mappend,因此它不是一个Semigroup。例如:

data MyType = MyType Int deriving Show

myMerge :: MyType -> MyType -> Maybe MyType
myMerge (MyType x) (MyType y)
  | (x < 0) || (y < 0) = Nothing
  | otherwise          = Just $ MyType $ x + y

当我的类型(MyType)被包装在Maybe中时,我总是需要处理它。如果我能够在"组合"类型Maybe MyType上定义一个Semigroup实例,那么我需要的语义将完美地表示为:

instance Semigroup (Maybe MyType) where
  (Just x) <> (Just y) = myMerge x y
  Nothing  <> Nothing  = Nothing
  Nothing  <> (Just _) = Nothing
  (Just _) <> Nothing  = Nothing

即,当两个参数都是Just时,我可以得到JustNothing,否则我总是得到Nothing。但是这是不可能的,我会遇到错误:

All instance types must be of the form (T a1 ... an)

我应该如何表示所需的语义?


2
你对 myMerge 的定义肯定无法编译 - 参数应该是 MyType xMyType y - Robin Zigmond
2
错误应该指向一些Haskell扩展,这些扩展应该启用以使实例正常工作。现代Haskell广泛使用许多GHC扩展。 - chi
2
你实际上想要实现什么还不清楚。你只是想要一个表示严格正整数的类型吗?惯用的方法是定义一个抽象类型,其中包含一个函数 toMyType :: Int -> Maybe MyType,如果参数小于0,则返回 Nothing。否则,我建议不要试图将你的类型适应不支持它的接口 - 将其定义为自由函数是惯用的方式。(最后注意你的 <> 定义只是 liftA2 myMerge - 在需要时直接内联编写可能更容易) - user2407038
1
@user2407038 我认为这不是 liftA2 myMerge。那需要 myMerge 的签名是 MyType -> MyType -> MyType。但是,即使给出两个 Justs,myMerge 也可以选择返回 Nothing,这是完全不同的事情。 - amalloy
4
如果你总是在处理Maybe包装过的MyType,那么也许将Maybe作为MyType的一部分会更好? - luqui
显示剩余3条评论
1个回答

9

你定义的实例是不合法的,因为它基本上试图为Maybe定义一个不同的(部分)Semigroup实例,但Maybe已经有一个。相反,使用newtype包装:

newtype MaybeMyType = MaybeMyType (Maybe MyType)

instance Semigroup MaybeMyType where
  ...

在想要使用类型的Semigroup实例的情况下,您将不得不通过这个MaybeMyType包装器与其交互。


我有一种感觉,类型包装器可以完成这项工作,尽管它看起来很笨重。但是你关于我试图为相同类型定义第二个实例的解释是有道理的。 - crosser
这真的是一个简化:即使 Maybe 没有 Semigroup 实例,这也是不合法的,因为您不能仅为参数化类型的某些特化定义实例,而不为其他类型定义实例。此外,我同意 luqui 的评论,只需在类型中包含可能性,如果您始终以这种方式工作。将其定义为 Maybe Int 的 newtype 包装器,或创建自己的等效 data 定义。 - amalloy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接