我正在通过阅读Learn a Haskell for Great Good 来学习 Haskell!。在“创建我们自己的类型和类型类”部分的末尾,YesNo定义了一个类来模拟 javascript 等语言的真实性:
class YesNo a where
yesno :: a -> Bool
instance YesNo Int where
yesno 0 = False
yesno _ = True
(etc.)
Run Code Online (Sandbox Code Playgroud)
在阅读参考文献之前,我试图自己充实这些实例作为练习,并认为我可以聪明地为所有Num类型定义它:
instance (Num a) => YesNo a where
yesno 0 = False
yesno _ = True
Run Code Online (Sandbox Code Playgroud)
我将跳过这需要什么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
Run Code Online (Sandbox Code Playgroud)
但现在我不得不说egyesno $ TruthyNum 0而不是yesno 0。
这感觉不对。难道真的没有办法在不为每个类型编写一个实例的情况下yesno干净地表达类型吗?Num或者,退一步说,一个经验丰富的 Haskell 黑客如何以“定义一个以[选择你的脚本语言]的方式实现真实性的类型类”为前提?
非常好的问题!我会newtype像你一样定义 a 。我不会直接使用它,而是通过它派生。
{-# 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
Run Code Online (Sandbox Code Playgroud)
Applicative举重是另一种以这种方式重叠的行为。鉴于Applicative f你可以像这样提升代数
Semigroup a, Monoid a,Num a进入
Semigroup (f a), Monoid (f a),Num (f a)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
Run Code Online (Sandbox Code Playgroud)
我们不编写重叠的实例,而是创建newtypeAp f a的实例。
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
Run Code Online (Sandbox Code Playgroud)
这是一个蹦床示例,它派生依赖于先前派生的实例。该数据类型是一个“3D 矢量”,但它只是一个具有 3 个相同类型参数的数据类型。
V3Generically1(a )使用 1 中的通用表示法newtype导出。ApplicativeApNum从 2推导出来Applicative。TruthyNumYesNo使用等式和来自 3 的实例进行推导Num。我们Applicative得出的涉及提升pure a = V3 a a a,因此当我们写作时,0 :: V3 Int我们实际上是指V3 0 0 0 :: V3 Int。
这意味着您的YesNo实例是通过与(/= V3 0 0 0). V3因此,当值为 时,我们将其视为“假” 0。
-- >> 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)
Run Code Online (Sandbox Code Playgroud)