是否可以在我的计算表达式中提供 setter 函数?

Posted

技术标签:

【中文标题】是否可以在我的计算表达式中提供 setter 函数?【英文标题】:Is it possible to provide a setter function in my computational expression? 【发布时间】:2021-10-10 11:36:06 【问题描述】:

我正在尝试编写一个 F# 计算表达式,该表达式仅允许从临界区读取和写入线程安全变量。

我有一个类型 ThreadSafeVar<'t> 包装了一个值 CriticalSection<'t> 和一个计算表达式构建器 LockContext,如下所示:


// wraps a value and restricts access to it
type ThreadSafeVar<'t> (value: 't) =
  member val internal Value = value with get, set

// Encapsulates a critical section
type CriticalSection<'t> =
  private
     LockObj: obj
      fn: unit -> 't 
    
  static member Lock(lc: CriticalSection<'t>) = lock lc.LockObj lc.fn

// Expression builder for a locked context
type LockContext () =
  member internal this.SyncRoot = obj()
  member this.Return(value: 'v) = value
  member this.ReturnFrom(value: ThreadSafeVar<'t>) = value.Value
  member __.Bind(value: ThreadSafeVar<'t>, fn: 't -> 'u) = fn value.Value
  // returns a CriticalSection
  member this.Run(fn : unit -> 'u) =  LockObj = this.SyncRoot
                                       fn=fn 
  .
  .
  .


感谢Bind,从锁上下文中读取线程安全值非常简单。例如


  let lockedInt = ThreadSafeVar(1) // create a thread-safe variable
  let context = LockContext()
  let wrapperVal = context 
                     let! i = lockedInt // get the wrapper value inside lockedInt 
                     return i 
                    |> CriticalSection.Lock


但我很难理解如何实现从 LockContext 实例中设置值的方法。我因此采取的方法是实现一个custom operation,例如,称为setVal。到目前为止,我已经将我的尝试包括在内,但我担心它们只会让水变得浑浊。似乎自定义操作对迄今为止在表达式中构建的计算进行操作,编码为元组,但我认为在我的情况下不需要这样做。

任何提示、指向资源或直接帮助将不胜感激。

【问题讨论】:

【参考方案1】:

我完全不确定这是否明智,但我根据State monad 提出了一些可能对您有用的方法。首先,将“有状态”函数定义为接受ThreadSafeVar 并返回某种结果的函数:

ThreadSafeVar<'state> -> 'result

然后我们将该签名放入表示有状态计算的类型中:

type Stateful<'state, 'result> =
    MkStateful of (ThreadSafeVar<'state> -> 'result)

现在我们需要一种方法来使用给定的 TSV 安全地运行这样的计算:

let run (tsv : ThreadSafeVar<_>) (MkStateful f) =
    lock tsv (fun () -> f tsv)

请注意,我已经摆脱了您的 CriticalSection 类型,而只是锁定了 TSV 本身。

接下来,我们需要一种将纯值提升为有状态计算的方法:

let lift value =
    MkStateful (fun _ -> value)

还有一种将两个有状态计算绑定在一起的方法:

let bind binder stateful =
    MkStateful (fun tsv ->
        run tsv stateful
            |> binder
            |> run tsv)

然后定义构建器就很简单了:

type LockContext () =
    member __.Return(value) = lift value
    member __.Bind(stateful, binder) = bind binder stateful

let context = LockContext()

我们还需要辅助计算来安全地设置和获取值:

let getValue =
    MkStateful (fun tsv ->
        tsv.Value)

let setValue value =
    MkStateful (fun tsv ->
        tsv.Value <- value)

综上所述,我们可以定义一个增加 TSV 值的计算:

let comp =
    context 
        let! oldValue = getValue
        let newValue = oldValue + 1
        do! setValue newValue
        return newValue
    

我们可以这样运行它:

let lockedInt = ThreadSafeVar(1)
let result = comp |> run lockedInt
printfn "%A" result   // output is: 2

您可以查看完整的解决方案并自己尝试here。

【讨论】:

以上是关于是否可以在我的计算表达式中提供 setter 函数?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从为 getter-setter 方法创建的类中获取数据

03.《Vue.js》charp-03 计算属性

用 mapState 和 mapMutations 替换计算的 getter/setter

是否可以在 Jasper ReportServer 中使用 SQL 窗口函数?

是否可以在扩展中使用非只读计算属性?

计算属性的getter和setter