Haskell 中的抽象类型类可以通过哪些方式使困难的事情变得更容易?

R3m*_*R3m 3 haskell typeclass

我是哈斯克尔的新手。诸如 monad 和 monoid 之类的概念及其相应的类型类非常有趣,但高度抽象和遥远。我想知道这些先进的概念如何使事情更容易实施。一些独立的具体例子很高兴看到。

Ice*_*ack 5

您可以通过类型和类型类来理解问题域。以下是我通过查看V3其实例可以收集到的信息,而无需考虑该类型用于表示什么(3D 向量)。

type V3 :: Type -> Type
data V3 a = V3 a a a
Run Code Online (Sandbox Code Playgroud)

这是一个简单的类型。并且不置可否。a对于什么可以是没有任何限制。这就是它的力量的来源,Edward Kmett 有一个关于这些“愚蠢的可重用数据类型”的经典演讲

V3具有基本属性。这一子句deriving可能为您提供了超过 20 个可操作的函数V3Foldable相当于定义toList (V3 a b c) = [a, b, c].

  deriving stock (Functor, Foldable, Traversable)
Run Code Online (Sandbox Code Playgroud)

我专注于类型构造函数类。我们可以自动派生许多类型类,但这些实例很少说明V3. 它们是相反的指示符,如果类型没有派生Eq,那么可能是因为无法定义该类型,或者因为它使用了值得注意的非标准定义。

  deriving stock (Eq, Ord, Show, Read, Data, Generic, Generic1, Lift) 
Run Code Online (Sandbox Code Playgroud)

然后我发现这是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)
Run Code Online (Sandbox Code Playgroud)

V3 1.0 2.0 3.0你可以写tabulate vec

vec :: Index3 -> Double
vec = \case
  O  -> 1.0
  I  -> 2.0
  II -> 3.0
Run Code Online (Sandbox Code Playgroud)

证明 与(= ) V3(自然地)同构是很重要的。我们对函数非常熟悉,我们可以通过同构来继承任何函数实例。(Index3 ->)Representable

这条规则是通过它的函数表示来封装的Co,你可以列出所有可能的实例给我们( )V3: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) 
Run Code Online (Sandbox Code Playgroud)

所以我们可以推导ApplicativeMonad至少从中可以得出,如果Index3是一个 Monoid,我们可以定义一个Comonad

  deriving (Applicative, Monad)
  via Co V3
Run Code Online (Sandbox Code Playgroud)

我们还看到很多实例都是它们自己的代数的提升版本。

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
Run Code Online (Sandbox Code Playgroud)

这可以概括为Ap V3 a允许在任何Applicative.

  deriving (Num, Semigroup, Monoid)
  via Ap V3 a
Run Code Online (Sandbox Code Playgroud)

我已经彻底描述了这个简单类型的特征,即使我以前从未见过这个数据类型,对我来说也会很清楚。实例(和非实例)赋予它特征,而派生就像一个执行摘要,以完全正确的细节级别告诉您正在发生的事情。

定义后,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) 

Run Code Online (Sandbox Code Playgroud)

整个故事重演,TicTacToe现在是 a Monad,数字和幺半群运算可以在它之上解除。

  • 很好的答案。(虽然在我看来,“线性”库是使用 Haskell 类型系统表达数学概念的最大失败之一。特别是,在基础层面植入基础表示完全错过了向量空间的几何本质。而可表示函子本身在我看来,相反的做法更有意义——从函数开始,然后构建 [MemoTries](https://hackage.haskell.org/package/MemoTrie-0.6.10/docs/Data- MemoTrie.html).) (2认同)