Haskell 中的抽象类型类可以通过哪些方式使困难的事情变得更容易?
In which ways can the abstract typeclasses in Haskell make hard things easier?
我是 Haskell 的新手。 monad 和 monoid 等概念及其对应的类型类非常有趣,但高度抽象和遥远。我想知道那些先进的概念如何使事情更容易实施。一些独立的具体例子会很高兴看到。
您将获得一种根据类型和类型 类 理解您的问题域的方法。这是我通过查看 V3
及其实例可以收集到的信息,而无需考虑用于表示什么类型(3D 向量)。
type V3 :: Type -> Type
data V3 a = V3 a a a
这是一个简单的类型。且不置可否。 a
完全没有限制。这就是它的力量来源,Edward Kmett 在这些 'dumb reusable datatypes'.
上有一个 classic talk
V3
具有基本属性。这个 deriving
子句可能会为您提供超过 20 个函数来操作 V3
。 Foldable
等于定义 toList (V3 a b c) = [a, b, c]
.
deriving stock (Functor, Foldable, Traversable)
我专注于类型构造函数类。我们可以自动派生出许多类型 类,但这些实例很少说明 V3
的结构。它们是相反的指标,如果一个类型没有派生 Eq
那么它可能是因为无法定义或者因为它使用了一个值得注意的非标准定义。
deriving stock (Eq, Ord, Show, Read, Data, Generic, Generic1, Lift)
然后我看到它是Representable
,这是有道理的,因为可表示表示一种“静态形状”。实际上可表示意味着 V3 a
是(同构)一个函数!
type Index3 :: Type
data Index3 = O | I | II
instance Representable V3 where
type Rep V3 = Index3
index :: V3 a -> (Index3 -> a)
index (V3 a b c) = \case
O -> a
I -> b
II -> c
tabulate :: (Index3 -> a) -> V3 a
tabulate make = V3 (make O) (make I) (make II)
而不是 V3 1.0 2.0 3.0
你可以写 tabulate vec
:
vec :: Index3 -> Double
vec = \case
O -> 1.0
I -> 2.0
II -> 3.0
证明 V3
(自然地)同构于 (Index3 ->)
(= Representable
) 很重要。我们对函数非常熟悉,我们可以通过见证同构来继承任何函数实例。
此规则由Co
and you can list all possible instances V3
can give us through its function representation (:instances Co V3
)
打包
> :instances Co V3
instance Applicative (Co V3)
instance Functor (Co V3)
instance Monad (Co V3)
instance Apply (Co V3)
instance Bind (Co V3)
instance Distributive (Co V3)
instance Representable (Co V3)
所以我们至少可以从中推导出 Applicative
和 Monad
,如果 Index3
是一个 Monoid 我们可以定义一个 Comonad
deriving (Applicative, Monad)
via Co V3
我们也看到很多实例是他们自己代数的提升版本。
instance Num a => Num (V3 a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = fmap negate
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
instance Semigroup a => Semigroup (V3 a) where
(<>) = liftA2 (<>)
instance Monoid a => Monoid (V3 a) where
mempty = pure mempty
这可以用 Ap V3 a
来概括,它允许对任何 Applicative
.
进行这些操作
deriving (Num, Semigroup, Monoid)
via Ap V3 a
我已经彻底描述了这种简单类型的特征,即使我以前从未见过这种数据类型,它对我来说也很清楚。实例(和非实例)赋予它特征,推导就像一个执行摘要,以恰到好处的详细程度告诉您正在发生的事情。
定义 V3
后,您现在可以通过两个向量的组合来定义 TicTacToe
板,这两个向量将 TicTacToe
定义为与 (Index3, Index3)
的函数同构。我们可以用 pure Nothing :: TicTacToe (Maybe Player)
构建一个空板并用 (Index3, Index3)
.
索引到板中
type TicTacToe :: Type -> Type
newtype TicTacToe a = TicTacToe (V3 (V3 a))
deriving (Functor, Foldable, Applicative, Representable)
via Compose V3 V3
deriving (Monad)
via Co TicTacToe
deriving (Num, Semigroup, Monoid)
via Ap TicTacToe a
-- > index board (O, O)
-- Nothing
-- > index board (O, II)
-- Just Player2
board :: TicTacToe (Maybe Player)
board = TicTacToe do
V3 do V3 Nothing Nothing (Just Player2)
do V3 Nothing Nothing Nothing
do V3 Nothing Nothing (Just Player1)
周而复始,TicTacToe
现在是一个Monad
,可以对它进行数字和幺半群运算。
我是 Haskell 的新手。 monad 和 monoid 等概念及其对应的类型类非常有趣,但高度抽象和遥远。我想知道那些先进的概念如何使事情更容易实施。一些独立的具体例子会很高兴看到。
您将获得一种根据类型和类型 类 理解您的问题域的方法。这是我通过查看 V3
及其实例可以收集到的信息,而无需考虑用于表示什么类型(3D 向量)。
type V3 :: Type -> Type data V3 a = V3 a a a
这是一个简单的类型。且不置可否。 a
完全没有限制。这就是它的力量来源,Edward Kmett 在这些 'dumb reusable datatypes'.
V3
具有基本属性。这个 deriving
子句可能会为您提供超过 20 个函数来操作 V3
。 Foldable
等于定义 toList (V3 a b c) = [a, b, c]
.
deriving stock (Functor, Foldable, Traversable)
我专注于类型构造函数类。我们可以自动派生出许多类型 类,但这些实例很少说明 V3
的结构。它们是相反的指标,如果一个类型没有派生 Eq
那么它可能是因为无法定义或者因为它使用了一个值得注意的非标准定义。
deriving stock (Eq, Ord, Show, Read, Data, Generic, Generic1, Lift)
然后我看到它是Representable
,这是有道理的,因为可表示表示一种“静态形状”。实际上可表示意味着 V3 a
是(同构)一个函数!
type Index3 :: Type data Index3 = O | I | II instance Representable V3 where type Rep V3 = Index3 index :: V3 a -> (Index3 -> a) index (V3 a b c) = \case O -> a I -> b II -> c tabulate :: (Index3 -> a) -> V3 a tabulate make = V3 (make O) (make I) (make II)
而不是 V3 1.0 2.0 3.0
你可以写 tabulate vec
:
vec :: Index3 -> Double
vec = \case
O -> 1.0
I -> 2.0
II -> 3.0
证明 V3
(自然地)同构于 (Index3 ->)
(= Representable
) 很重要。我们对函数非常熟悉,我们可以通过见证同构来继承任何函数实例。
此规则由Co
and you can list all possible instances V3
can give us through its function representation (:instances Co V3
)
> :instances Co V3
instance Applicative (Co V3)
instance Functor (Co V3)
instance Monad (Co V3)
instance Apply (Co V3)
instance Bind (Co V3)
instance Distributive (Co V3)
instance Representable (Co V3)
所以我们至少可以从中推导出 Applicative
和 Monad
,如果 Index3
是一个 Monoid 我们可以定义一个 Comonad
deriving (Applicative, Monad) via Co V3
我们也看到很多实例是他们自己代数的提升版本。
instance Num a => Num (V3 a) where (+) = liftA2 (+) (-) = liftA2 (-) (*) = liftA2 (*) negate = fmap negate abs = fmap abs signum = fmap signum fromInteger = pure . fromInteger instance Semigroup a => Semigroup (V3 a) where (<>) = liftA2 (<>) instance Monoid a => Monoid (V3 a) where mempty = pure mempty
这可以用 Ap V3 a
来概括,它允许对任何 Applicative
.
deriving (Num, Semigroup, Monoid) via Ap V3 a
我已经彻底描述了这种简单类型的特征,即使我以前从未见过这种数据类型,它对我来说也很清楚。实例(和非实例)赋予它特征,推导就像一个执行摘要,以恰到好处的详细程度告诉您正在发生的事情。
定义 V3
后,您现在可以通过两个向量的组合来定义 TicTacToe
板,这两个向量将 TicTacToe
定义为与 (Index3, Index3)
的函数同构。我们可以用 pure Nothing :: TicTacToe (Maybe Player)
构建一个空板并用 (Index3, Index3)
.
type TicTacToe :: Type -> Type
newtype TicTacToe a = TicTacToe (V3 (V3 a))
deriving (Functor, Foldable, Applicative, Representable)
via Compose V3 V3
deriving (Monad)
via Co TicTacToe
deriving (Num, Semigroup, Monoid)
via Ap TicTacToe a
-- > index board (O, O)
-- Nothing
-- > index board (O, II)
-- Just Player2
board :: TicTacToe (Maybe Player)
board = TicTacToe do
V3 do V3 Nothing Nothing (Just Player2)
do V3 Nothing Nothing Nothing
do V3 Nothing Nothing (Just Player1)
周而复始,TicTacToe
现在是一个Monad
,可以对它进行数字和幺半群运算。