[英]How to impose type constraints on typeclass instances?
我正在通过阅读为您学习 Haskell 来学习 Haskell 非常好! . 在制作我们自己的类型和类型类部分接近尾声时,定义了 class YesNo
来模拟 javascript 等语言的真实性:
class YesNo a where
yesno :: a -> Bool
instance YesNo Int where
yesno 0 = False
yesno _ = True
(etc.)
在阅读参考之前,我试图自己充实实例作为练习,并认为我可以很聪明并为所有Num
类型定义它:
instance (Num a) => YesNo a where
yesno 0 = False
yesno _ = True
我将跳过这对FlexibleInstances
的要求,我想我在文档和这个答案之间已经理解了。 一旦打开,编译器就会抱怨“约束‘Num a’不小于实例头‘YesNo a’”。 这个问题的答案很好地解释了这意味着什么。 使用newtype
提供的新类型解决方案,我想出了类似的东西
newtype TruthyNum a = TruthyNum a
instance (Num a, Eq a) => YesNo (TruthyNum a) where
yesno (TruthyNum 0) = False
yesno _ = True
但现在我不得不说例如yesno $ TruthyNum 0
而不是yesno 0
。
这感觉不对。 如果不为每种此类类型写出一个实例,真的没有办法干净地表达Num
类型的yesno
吗? 或者,退一步说,一个经验丰富的 Haskell 黑客如何在“定义一个按照 [选择你的脚本语言] 实现真实性的类型类”的前提下来?
非常好的问题! 我会newtype
你一样定义一个新类型。 我不会直接使用它,而是通过它派生。
{-# Language DerivingVia #-}
{-# Language StandaloneDeriving #-}
{-# Language StandaloneKindSignatures #-}
import Data.Kind (Type, Constraint)
type YesNo :: Type -> Constraint
class YesNo a where
yesno :: a -> Bool
type TruthyNum :: Type -> Type
newtype TruthyNum a = TruthyNum a
instance (Num a, Eq a) => YesNo (TruthyNum a) where
yesno (TruthyNum 0) = False
yesno _ = True
-- standalone deriving, is used when deriving an instance
-- outside of the data declaration
deriving via TruthyNum Int
instance YesNo Int
deriving via TruthyNum Integer
instance YesNo Integer
deriving via TruthyNum Float
instance YesNo Float
Applicative
提升是另一种以这种方式重叠的行为。 给定Applicative f
你可以举起像
Semigroup a
, Monoid a
, Num a
进入
Semigroup (fa)
, Monoid (fa)
, Num (fa)
instance (Applicative f, Num a) => Num (f a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = liftA . negate
abs = liftA . abs
signum = liftA . signum
fromInteger = liftA0 . fromInteger where liftA0 = pure
我们不编写重叠实例,而是创建新类型Ap fa
的实例。
type Ap :: (k -> Type) -> (k -> Type)
newtype Ap f a = Ap (f a)
deriving newtype (Functor, Applicative, ..)
instance (Applicative f, Num a) => Num (Ap @Type f a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = liftA . negate
abs = liftA . abs
signum = liftA . signum
fromInteger = liftA0 . fromInteger where liftA0 = pure
这是一个蹦床示例,它派生依赖于先前推导的实例。 数据类型是“3D 向量”,但它只是具有 3 个相同类型的 arguments 的数据类型。
V3
的通用表示Generically1
(一个newtype
)使用来自 1 的泛型表示派生Applicative
。Ap
通过从 2 中提升Applicative
来导出Num
。TruthyNum
使用相等性和Num
实例从 3 派生YesNo
。 我们推导出的Applicative
涉及提升pure a = V3 aaa
所以当我们写0:: V3 Int
我们实际上是指V3 0 0 0:: V3 Int
。
这意味着您的YesNo
实例是通过与(/= V3 0 0 0)
进行比较来实现的。 因此,当值为0
时,我们将V3
视为“假”。
-- >> 0 :: V3 Int
-- V3 0 0 0
-- >> yesno (V3 0 0 0)
-- False
-- >> yesno (V3 0 0 2)
-- True
data V3 a = V3 a a a
deriving
stock (Eq, Show, Generic1)
deriving (Functor, Applicative)
via Generically1 V3
deriving (Semigroup, Monoid, Num)
via Ap V3 a
deriving YesNo
via TruthyNum (V3 a)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.