【问题标题】:Is it possible to represent mutability without using mutability?是否可以在不使用可变性的情况下表示可变性?
【发布时间】:2015-10-30 04:10:28
【问题描述】:

在阅读this excellent series 上的状态单子(和其他……东西)后,我尝试在不使用可变变量的情况下重现以下场景(改编自一个计算点击次数的简单 UI):

let mutable count = 1

let increment () = count <- count + 1

我想不出办法。

是否可以在不使用可变变量的情况下在 F# 中进行处理?其他根本不允许不变性的函数式语言如何处理它?还是我问错了问题?

【问题讨论】:

  • “我不成功”是什么意思?发生了什么,或者您遇到了什么错误?
  • 只是一个简短的评论:我知道的所有实用相关语言(当然包括 Haskell)都将允许您在某些时候改变某些状态(并更新您的数据库)纯语言的想法。是将它尽可能地推到应用程序的边界(在 Haskell 中,这可能是 main 中臭名昭著的 IO 类型) - F# 不是纯的,通常被认为是好的 - 在你的例子中大多数 F#ers 不会使用 StateMonad(因为你无法在没有 man 的情况下构建 MonadStacks。无论如何,大多数时候重写这不是一个选项)

标签: f# functional-programming immutability monads


【解决方案1】:

改变变量的函数替代方法是从函数返回更新的值。有状态计算可以通过函数来​​建模,这些函数采用状态的当前值并返回包含结果和状态更新值的对。您可以在 F# 中将这样的类型定义为

type State<'s, 'a> = S of ('s -> ('s * 'a))

它只是包装了一个有状态的函数。然后你可以定义一些函数来操作有状态的函数:

let returnState x = S (fun s -> (s, x))
let runState (S(f)) s = f s
let putState s = S (fun _ -> (s, ()))
let getState<'s, 'a> = S (fun (s: 's) -> (s, s))

returnState 创建一个忽略状态并返回给定值的有状态计算。 runState 使用给定的初始状态运行有状态计算。 putState 是一个有状态计算,它获取当前状态,而putState 更新当前状态并返回一个虚拟值。

然后您可以将有状态计算与以下函数结合起来:

let bindState<'s, 'a, 'b> (S(sf): State<'s, 'a>) (f : 'a -> State<'s, 'b>) = S (fun s ->
        let (s', r) = sf s
        let (S(sf')) = f r
        sf' s')

这需要一个有状态的计算和一个函数来构造一个新的计算,给定第一个结果值。然后它构造一个复合计算,该计算将运行第一个计算,使用该值构造下一个计算,然后使用第一个计算返回的中间状态运行给定的计算。

这形成了一个 monad,因此您可以创建一个计算表达式并使用它以更方便的语法进行有状态计算:

type StateBuilder() = 
    member this.Return(x) = returnState x
    member this.Bind(s, f) = bindState s f
    member this.ReturnFrom(s: State<_,_>) = s

let state = StateBuilder()

let modifyState f = state {
    let! s = getState
    let s' = f s
    return! (putState s')
}

modifyState 采用函数a -&gt; a 来转换计算中的状态值。然后,您可以编写一个函数来增加有状态计算中计数器的值:

let incState = modifyState ((+)1)

【讨论】:

  • 如何将其合并到我的原始示例中,单击按钮将修改状态?
【解决方案2】:

如果你不使用可变变量,你可以使用 ref 变量。有了它,您的代码将如下所示

let count = ref 1
let increment() = count := !count + 1

虽然我使用 mutable 比 ref 更频繁,但我并不特别关注这两个。找到了这个stackoverflow link,这也很好地了解了两者之间的差异。

【讨论】:

  • ref 在语义上具有相同的效果;编译器对它的处理方式略有不同(在 F# 4 中不再需要)。致反对者:至少没有解释原因的反对票是极其无益和毫无意义的。只是不要那样做。
  • 在帖子中添加了一个链接,说明它们之间的区别,即使它们之间有很多相似之处。这是否值得投反对票?
  • 我认为反对意见来自这样一个事实,即在函数式编程的上下文中理解可变性,而不是特定的 mutable 关键字。所以这个答案非常具有误导性,因为它可能会让一些人相信ref 是避免可变性的正确方法。
  • @TeaDrivenDev 虽然我基本上同意你应该给出一些提示,说明你为什么不赞成它是这里的 meta,但你不必这样做 - 事实上它的设计是为了让你可以私下投反对票和赞成票 - 前往 META 并讨论(再次;))如果你愿意(并且需要一些反对票)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-29
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
相关资源
最近更新 更多