示例代码:
{-# LANGUAGE NamedFieldPuns #-}
module Sample where
class Sample a where
isA :: a -> Bool
isB :: a -> Bool
isC :: a -> Bool
data X =
X
instance Sample X where
isA = undefined
isB = undefined
isC = undefined
data Wrapper = Wrapper
{ x :: X
, i :: Int
}
instance Sample Wrapper where
isA Wrapper {x} = isA x
isB Wrapper {x} = isB x
isC Wrapper {x} = isC x
在这里,我有一个由X
实现的类,然后是另一个包含Wrapper
的记录X
。
我希望Wrapper
通过其Sample
字段派生x
实例。
我知道我可以通过获取该字段并针对每个函数自己调用它来实现,如图所示。
是否有一些标志或某种方法可以自动执行或仅执行一次?
这似乎类似于DerivingVia
和GeneralisedNewtypeDeriving
,但两者似乎都只针对newtype
或强制类型
答案 0 :(得分:4)
这里有些策略不需要任何扩展,但是为了便于派生这些类而付出了一些前期成本。
请注意,由于Sample
不是新类型,因此无法保证它只能容纳一个X
,而不能容纳两个,更多或可变数量(Maybe X
?Either X X
?)。因此,正如您将看到的那样,您的选项必须使结构内部的X
选择明确,这可能是扩展存在的原因,该扩展会自动将其导出为 not 。
要满足Sample
,我们确实需要X
。让我们将其作为类型类:
class HasX t where
getX :: t -> X
class Sample t where
isA :: t -> Bool
isB :: t -> Bool
isC :: t -> Bool
default isA :: HasX t => t -> Bool
isA = isA . getX
default isB :: HasX t => t -> Bool
isB = isB . getX
default isC :: HasX t => t -> Bool
isC = isC . getX
instance HasX Wrapper where
getX = x
instance Sample Wrapper -- no implementation necessary
比方说,我们只想处理以X
作为第一个字段的记录。为了匹配类型结构,我们可以使用GHC.Generics。在这里,我们为HasX
添加了一种默认设置为第一个字段的方法:
class HasX t where
getX :: t -> X
default getX :: (Generic a, HasX (Rep a)) => t -> X
getX = getX . from
instance HasX (M1 D d (M1 C c (M1 S s (Rec0 X) :*: ff))) o where
getX (M1 (M1 ((M1 (K1 x)) :*: _))) = x
HasX
的最后一个实例使用单个构造函数(M1 D
)匹配任何记录(M1 C
),该构造函数具有多个(:*:
)字段({{ 1}}),第一个字段的类型为(M1 S
)Rec0
。
(是的,通用实例笨拙。欢迎编辑。)
(要查看X
通用类型的确切表示,请在GHCi控制台中检查Wrapper
。)
现在Rep Wrapper
的实例可以写为:
Wrapper