制作 Applicative 的实例

Making instance of Applicative

仍然不能百分百确定如何创建更复杂类型的实例。有这个:

data CouldBe a = Is a | Lost deriving (Show, Ord) 

创建了一个Functor的实例,以Maybe为例:

instance Functor CouldBe where 
  fmap f (Is x) = Is (f x) 
  fmap f Lost   = Lost 

做这样的事情:

tupleCouldBe :: CouldBe a -> CouldBe b -> CouldBe (a,b)
tupleCouldBe x y = (,) <$> x <*> y

CouldBe 需要成为 Applicative 的一个实例,但是您会怎么做呢?当然我可以查找并复制它,但我想了解它背后的过程并最终以 instance CouldBe 的声明结束。

你直接写出来,类型如下:

instance Applicative CouldBe where
   {- 
        <b>Minimal complete definition:</b>
          pure, ((<*>) | liftA2)

      pure :: a -> f a 
      pure :: a -> CouldBe a

      liftA2 :: (a -> b -> c) -> f a -> f b -> f c 
      liftA2 :: (a -> b -> c) -> CouldBe a -> CouldBe b -> CouldBe c 
   -}
    pure a = fa
        where
        fa = ....

    liftA2 abc fa fb = fc
        where
        fc = ....

根据

data CouldBe a = Is a | Lost

我们的工具集是

Is   :: a -> CouldBe a
Lost :: CouldBe a

但我们也可以使用模式匹配,例如

couldBe   is   lost  (Is a)    = is a
couldBe   is   lost  (Lost)    = lost
couldBe :: ? -> ? -> CouldBe a -> b
couldBe :: ? -> b -> CouldBe a -> b
couldBe :: (a -> b) -> b -> CouldBe a -> b

所以,

    -- pure :: a -> f a 
    pure :: a -> CouldBe a     

匹配

    Is   :: a -> CouldBe a

所以我们定义

    pure a = Is a

然后,对于liftA2,我们遵循数据案例:

    -- liftA2 :: (a -> b -> c) -> f a -> f b -> f c 
    -- liftA2 :: (a -> b -> c) -> CouldBe a -> CouldBe b -> CouldBe c
    liftA2 abc Lost    _     = ...
    liftA2 abc  _     Lost   = ...
    liftA2 abc (Is a) (Is b) = fc
        where
        c = abc a b
        fc = ....     -- create an `f c` from `c`: 
                      -- do we have a `c -> CouldBe c` ?
                      -- do we have an `a -> CouldBe a` ? (it's the same type)

但在前两种情况下,我们没有 ab;所以我们必须凭空想出一个CouldBe c。我们的 我们的工具集 中也有此工具。

完成所有缺失的部分后,我们可以将表达式直接代入定义,消除所有不需要的中间值/变量。

使用我的新 idiomatic 包,您可以导出 Applicative 求和。

{-# Language DataKinds                #-}
{-# Language DeriveGeneric            #-}
{-# Language DerivingVia              #-}
{-# Language StandaloneKindSignatures #-}

import Data.Kind
import GHC.Generics
import Generic.Applicative

type CouldBe :: Type -> Type
data CouldBe a = Is a | Lost
  deriving
  stock (Eq, Ord, Show, Generic1)

  -- > pure @CouldBe 10
  -- Is 10
  -- > liftA2 (+) (Is 1) Lost
  -- Lost
  -- > liftA2 (+) Lost (Is 10)
  -- Lost
  deriving (Functor, Applicative)
  via Idiomatically CouldBe '[LeftBias Terminal]

-- > tupleCouldBe Lost Lost
-- Lost
-- > tupleCouldBe (Is 1) Lost
-- Lost
-- > tupleCouldBe Lost (Is 20)
-- Lost
-- > tupleCouldBe (Is 1) (Is 20)
-- Is (1,20)
tupleCouldBe :: CouldBe a -> CouldBe b -> CouldBe (a, b)
tupleCouldBe = liftA2 (,)

为什么这行得通? left-biased 意味着我们选择 Is 构造函数作为 pure 构造函数。

这意味着当提升不同的构造函数时,我们从那个构造函数“背离”。

Terminal 描述了我们如何将任何 Applicative 转换为 Const mempty

data Terminal

instance (Applicative f, Monoid m) => Idiom Terminal f (Const m) where
  idiom :: f ~> Const m
  idiom = mempty

在这种情况下丢弃 Is 映射到 Lost 的参数。

请注意,无法定义 CouldBe 的 right-biased 定义,因为它需要一个从无到有产生 a 的应用态射

  via Idiomatically CouldBe '[RightBias ..]
  idiom :: Const () ~> Identity
  idiom (Const ()) = Identity ??