【问题标题】:Is my implementation comparable/related to (state) monads?我的实现是否与(状态)单子相当/相关?
【发布时间】:2015-03-02 16:15:39
【问题描述】:

序幕:

几年前,我在阅读“LYAH”时曾涉足 Haskell。再加上使用了一点Mathematica,我想说我掌握了(或多或少)一些函数式编程的基本思想,例如不变性、函数组合、currying、maps、folds、惰性求值等。

我认为我从 LYAH 读到的最后一件事是函子(我的记忆模糊不清),这意味着我在“可怕”的单子中停了下来(不是因为对它们的一些非理性的恐惧,而是我无法保持由于多种原因,我当时对 Haskell 的研究有所增加。)我时不时地提到了 monad,但我对它们的看法相当模糊。

现在我在玩 Swift,它对函数式范式有一些支持。 我的主要问题是了解我对下面指定问题的解决方案与 monad 有什么关系(如果有的话)。 如果不出意外,它可能会给我重新学习纯函数式编程的动力哈斯克尔。


所以这是一般形式的问题:我有一堆T -> T 类型的函数,我希望能够以这样的方式“链接”它们,当我应用类型参数时T,我不仅在参数上得到了这些函数组成对应的值,还得到了中间值的序列(每个函数的返回值在前一个函数的返回值所具有的序列中)作为参数应用)在一个数组中。

现在我知道 Swift 对从函数内部修改全局变量(可用于存储状态)毫无疑虑,但我想以一种功能上“更纯粹”的方式来处理它。我的第一个想法是将我的函数修改为具有[T] -> [T] 类型,这样每个函数都会使用一个包含所有先前状态的数组,并返回一个带有最后附加返回值的数组。但显然这不是一个简洁的解决方案,它掩盖了这样一个事实,即我的函数的计算实际上并不依赖于过去的值历史。

所以这是我想出的解决方案:

infix operator << {
    associativity right
}


func wrap<T>(f: T -> T) -> (T -> (T, [T])) {
    return { x in       
        (f(x), [x]) }
}


func <<<T>(left: T -> T, right: T -> (T, [T])) -> T -> (T, [T]) {
    return { x in
        let r = right(x)
        let lr0 = left(r.0)
        return (lr0, r.1 + [r.0]) }
}

func <<<T>(left: T -> T, right: T -> T) -> T -> (T, [T]) {
    return left << wrap(right)
}

// Simple example; Int -> Int functions:
let addOne: Int -> Int = { x in x + 1 }
let mulTwo: Int -> Int = { x in 2 * x }
let subThree: Int -> Int = { x in x - 3 }

let seqOfOps = addOne << mulTwo << addOne << subThree
seqOfOps(10) // (.0 17, [10, 7, 8, 16]) // final value and history respectively

(我没有费心解释它,因为我希望能够解决我的问题的人应该能够跟进。)

那么我的想法是否与 monad 概念有关,如果是,那么如何(以及如何不)?如果不是,我的解决方案将如何修改为“适当的”一元解决方案?

我意识到,通过定义 protocol 并使用类型约束,我应该能够概括“状态”的概念,如果这是考虑的一点的话。

【问题讨论】:

  • 我认为这实际上可能与 Writer monad 有关。它的“经典”目的是聚合中间内容(如日志记录)。 &lt;&lt; 函数在某种程度上看起来像带有纯函数的 Kleisli 组合。
  • “Kleisli 组合” - 很酷的名字。我得查一下。

标签: swift functional-programming monads


【解决方案1】:

使用一元方法

它与Writer 类型结合State 类型有关。这种组合可以通过使用WriterT monad 转换器来实现。

Writer 提供结果值的累积,State 类型提供对当前值的访问。转换器允许我们使用与这两种类型相关的方法。

关于 monad 的旁白

实际上,较弱的Applicative几乎足以定义所有这些。我们只需要Monad 就可以将get 的结果传递给performOp 中的tell。一个重要的收获是,这种关系更多地与特定的WriterState 类型相关,它们恰好有Monad 实例,而不是monad 的概念。我通常喜欢“Writer 类型”而不是“Writer monad”的原因是这些类型的 Monad 实例只是它们的一个方面。其他属性非常重要,它们使这些类型与这个特定问题最相关。

真的,唯一与“monad”本身的特定概念密切相关的情况是,当您拥有对所有 monad 具有多态性的东西时。例如,内置的 Haskell 函数filterM 将适用于Monad任何 实例。

Haskell 实现

这是在 Haskell 中使用这些类型编写的方式:

sequenceOpsW :: [a -> a] -> WriterT [a] (State a) a
sequenceOpsW []     = get -- This branch shouldn't be reached unless it is explicitly passed an empty list
sequenceOpsW [f]    = performOp f
sequenceOpsW (f:fs) = do
  performOp f
  sequenceOpsW fs

performOp :: (a -> a) -> WriterT [a] (State a) a
performOp f = do
  x <- get
  tell [x] -- Update the Writer layer with the "old" value

  modify f -- Update the State layer with the new value
  get


sequenceOps :: [a -> a] -> a -> (a, [a])
sequenceOps fs x
  = flip evalState x
  . runWriterT
  $ sequenceOpsW fs

您的示例可以运行为:

addOne, mulTwo, subThree :: Int -> Int
addOne   = (+        1)
mulTwo   = (*        2)
subThree = (subtract 3)

exampleResult :: (Int, [Int])
exampleResult = sequenceOps [subThree, addOne, mulTwo, addOne] 10

如果您想更好地理解这一点,一个很好的练习是编写自己的(受限)Writer/State 组合类型并为其创建适当的FunctorApplicativeMonad 实例.类型是这样的:

newtype StateWriter w a = StateWriter (a -> (a, w))

一种更简单的非单子方式

但是,除非我们有充分的理由这样做,否则我宁愿直接编写它而不使用那些数据类型或任何单子绑定:

sequenceOps :: [a -> a] -> a -> (a, [a])
sequenceOps []     x = (x, [x])
sequenceOps (f:fs) x = fmap (x:) $ sequenceOps fs y
  where
    y = f x

(这里fmap的行为就像fmap f (x, y) = (x, f y)一样,因为我们使用的是(,) rFunctor实例。我只是为了方便而使用它,它对这个应用程序没有比这更深层次的意义)。

关于斯威夫特

我没有直接为您的 &lt;&lt; 编写 Haskell 翻译,因为这种多态性在 Haskell 中以完全不同的方式工作(您需要一个类型类,这会带来其自身的问题)。

我个人建议对我在第一个实现中给出的第二个实现进行 Swift 翻译。我不会太担心在 Swift 中使用纯实现。函数提供的interface在我看来更为重要,无论它如何实现,它都会提供一个纯接口。这就是 ST 类型在 Haskell 中运行良好的原因。另外,我相信 Swift 中的 [] 类型是一个数组,而不是一个链表,这会使纯实现变得复杂,并显着降低效率。

以下是我在 Swift 中的处理方式:

func sequenceOps<T>(fs : [T -> T], x : T) -> (T, [T]) {
    var result : [T] = []

    var curr = x

    for f in fs {
        result += [curr]
        curr = f(curr)
    }

    return (curr, result)
}

您在问题中提供的实现对我来说也很合理。

【讨论】:

  • 感谢您的全面回复!我仍然需要使用其他资源来提醒自己 Applicatives 等实际上是什么,然后才能更好地理解它,但希望您的回答能提供一个很好的查询途径。
  • "我认为函数提供的接口更重要,不管怎么实现,它都会提供一个纯接口。"这也是我想到的一个很好的观点,事实上,你最后建议的 Swift 实现对我来说(由于比函数式编程经验更具命令性),是“显而易见的”。我在这里所做的实际上更像是一种锻炼。
【解决方案2】:

昨晚我实现了 Haskell State Monad 的不同功能。 尽管泛型类型别名会有所帮助,但不会涉及太多代码混乱。

解释:https://www.kohn.io/blog/2015/04/18/swift-state-monad/

要点:https://gist.github.com/ankoh/533620a40c1e900e1082

您可以将 Gist 复制并粘贴到您的 Playground 并尝试一下。

【讨论】:

    【解决方案3】:

    这里是相同代码的 Swift 4 版本,以防有人想在 Playground 中尝试一下:

    precedencegroup WriterPrecedence {
        associativity: right
        higherThan: DefaultPrecedence
    }
    infix operator <<: WriterPrecedence
    typealias Operator<T> = (T) -> T
    typealias Accumulator<T> = (T, [T])
    typealias Generator<T> = (T) -> Accumulator<T>
    
    func wrap<T>(f: @escaping Operator<T>) -> Generator<T> {
        return { (x) in
            (f(x), [x]) }
    }
    
    func <<<T>(left: @escaping Operator<T>, right: @escaping Generator<T>) -> Generator<T> {
        return { (x) in
            let r = right(x)
            let lr0 = left(r.0)
            return (lr0, r.1 + [r.0]) }
    }
    
    func <<<T>(left: @escaping Operator<T>, right: @escaping Operator<T>) -> Generator<T> {
        return left << wrap(f: right)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-19
      • 1970-01-01
      • 1970-01-01
      • 2022-07-08
      • 2019-07-23
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多