Haskell中单体的还原

Haskell中单体的还原,haskell,monads,monad-transformers,Haskell,Monads,Monad Transformers,假设我有一个类型定义为: data Node = forall a b. Node (SimpleWire a b) SimpleWire是一个monad,其中a表示输入,b表示输出。我可以在那个单子上做函数交换。因此,假设我有SimpleWire A B类型的wireA,和SimpleWire B C类型的wireB,执行wireA。wireB会给我一个C的类型 现在,我想折叠该单子的列表(对于本例,其类型为[Node])。比如: buildGraph :: [Node] -> (Si

假设我有一个类型定义为:

data Node = forall a b. Node (SimpleWire a b)
SimpleWire
是一个monad,其中
a
表示输入,
b
表示输出。我可以在那个单子上做函数交换。因此,假设我有
SimpleWire A B
类型的
wireA
,和
SimpleWire B C
类型的
wireB
,执行
wireA。wireB
会给我一个C的类型

现在,我想折叠该单子的列表(对于本例,其类型为
[Node]
)。比如:

buildGraph :: [Node] -> (SimpleWire a b)
buildGraph (Node h):t = h . (buildGraph t)

如何使此代码在Haskell的类型系统中工作?

我们不能用建议的类型组合
[Node]
。这是因为否则我们会

sw1 :: SimpleWire A B
sw2 :: SimpleWire C D
buildGraph :: [Node] -> (SimpleWire a b)
buildGraph [ sw1, sw2 ] :: SimpleWire E F
这太强烈了。我们能够组合任意的、不兼容的类型(错误),然后在最后随机写入(错误)

问题是我们丢失了
[Node]
类型中的所有类型信息。我们需要记住一些,即:

  • 第一个和最后一个导线类型已知(中间导线类型未知)
  • 在列表中,每个相邻节点都是可组合的
  • 因此,我们得到了一个自定义的GADT列表类型

    data NodeList a b where
       Nil  :: NodeList a a
       Cons :: Node a b -> NodeList b c -> NodeList a c
    
    然后

    buildGraph :: NodeList a b -> SimpleWire a b
    buildGraph Nil = id  
    buildGraph (Cons (Node h) t) = h . buildGraph t
    

    我将假设以下故事:

    你可能用过那种类型

    data Node = forall a b. Node (SimpleWire a b)
    
    而不仅仅是
    SimpleWire a b
    ,因为您想要一个
    SimpleWire
    的列表,其中
    a
    b
    是不同的。特别是,作为
    buildGraph
    的参数,您真正希望的是(在pseudo Haskell中)

    但是,您无法用Haskell的标准同构
    []
    表达第一个列表,而是尝试使用通用量化类型来摆脱这种困境

    如果我说的是真的,你可能在寻找。特别是,您可以完全取消
    节点
    。A
    第三个(->)ab
    是函数列表
    A->a1
    a1->a2
    ,…,
    A->b
    。更一般地说,
    第三个fab
    f
    s
    faa1
    faa2
    ,…,
    fab
    的列表

    {-# LANGUAGE GADTs #-}
    import qualified Data.Thrist as DT
    
    -- Note that I'll be using (>>>) as a flipped form of (.), i.e. 
    -- (>>>) = flip (.)
    -- (>>>) is in fact an Arrow operation which is significantly more general
    -- than function composition. Indeed your `SimpleWire` type is almost
    -- definitely an arrow.
    import Control.Arrow ((>>>))
    
    -- A simple take on SimpleWire
    type SimpleWire = (->)
    
    -- Ugh a partial function that blows up if the thrist is empty
    unsafeBuildGraph :: DT.Thrist SimpleWire a b -> SimpleWire a b
    unsafeBuildGraph = DT.foldl1Thrist (>>>)
    
    -- Making it total
    buildGraph :: DT.Thrist SimpleWire a b -> Maybe (SimpleWire a b)
    buildGraph DT.Nil = Nothing
    buildGraph (wire `DT.Cons` rest) = Just $ DT.foldlThrist (>>>) wire rest
    
    -- For syntactic sugar
    (*::*) = DT.Cons
    infixr 6 *::*
    
    trivialExample :: DT.Thrist SimpleWire a a
    trivialExample = id *::* id *::* DT.Nil
    
    lessTrivialExample :: (Num a, Show a) => DT.Thrist SimpleWire a String
    lessTrivialExample = (+ 1) *::* (* 2) *::* show *::* DT.Nil
    
    -- result0 is "12"
    result0 = (unsafeBuildGraph lessTrivialExample) 5
    
    -- result1 is Just "12"
    result1 = fmap ($ 5) (buildGraph lessTrivialExample)
    
    旁注:

    尽管
    SimpleWire
    很可能是单子,但这可能不会直接帮助您。特别是当函数是单子时,您似乎关心的是对函数组合概念的概括,这是函数的用途(并且只与单子有间接关系)。我使用了
    >>
    ,并且
    Thrist
    有一个
    箭头
    实例,这一事实就暗示了这一点。正如我在代码注释中提到的,
    SimpleWire
    可能是一个
    箭头

    {-# LANGUAGE GADTs #-}
    import qualified Data.Thrist as DT
    
    -- Note that I'll be using (>>>) as a flipped form of (.), i.e. 
    -- (>>>) = flip (.)
    -- (>>>) is in fact an Arrow operation which is significantly more general
    -- than function composition. Indeed your `SimpleWire` type is almost
    -- definitely an arrow.
    import Control.Arrow ((>>>))
    
    -- A simple take on SimpleWire
    type SimpleWire = (->)
    
    -- Ugh a partial function that blows up if the thrist is empty
    unsafeBuildGraph :: DT.Thrist SimpleWire a b -> SimpleWire a b
    unsafeBuildGraph = DT.foldl1Thrist (>>>)
    
    -- Making it total
    buildGraph :: DT.Thrist SimpleWire a b -> Maybe (SimpleWire a b)
    buildGraph DT.Nil = Nothing
    buildGraph (wire `DT.Cons` rest) = Just $ DT.foldlThrist (>>>) wire rest
    
    -- For syntactic sugar
    (*::*) = DT.Cons
    infixr 6 *::*
    
    trivialExample :: DT.Thrist SimpleWire a a
    trivialExample = id *::* id *::* DT.Nil
    
    lessTrivialExample :: (Num a, Show a) => DT.Thrist SimpleWire a String
    lessTrivialExample = (+ 1) *::* (* 2) *::* show *::* DT.Nil
    
    -- result0 is "12"
    result0 = (unsafeBuildGraph lessTrivialExample) 5
    
    -- result1 is Just "12"
    result1 = fmap ($ 5) (buildGraph lessTrivialExample)