【问题标题】:F# Railway Programming is this initialization can be improved?F#铁路编程这个初始化可以改进吗?
【发布时间】:2019-03-31 23:49:17
【问题描述】:

我正在学习 F#,我想知道这种初始化数组的前 N ​​个元素的方法实现是否可以改进。目前它工作得很好。我的意思是,如果它在尝试通过对工厂执行第二次调用来初始化第二个元素时失败,那么如果会为第一个成功结果调用 undo。唯一的小问题是,如果出错,它不会清理数组中的项目,但我不担心。我所担心的是,如果它在第 2 次或第 3 次或更晚时失败,它应该为第一个成功的结果撤消。如果成功,那么成功的结果应该在 Undo 列表中列出所有要撤消的函子。

问题是我想避免递归并使用 Linq 之类的东西来迭代和做一些事情,但在这种情况下如何用 bang(let!) 做 let 还不清楚

// val private initializeArrayInternal: 
//    arr    : 'a option [] ->
//    factory: unit -> RopWithUndo.Result<'a> ->
//    count  : int          ->
//    index  : int          
//          -> RopWithUndo.Result<'a option []>
let rec private initializeArrayInternal (arr: _ []) factory count index =
    if (arr.GetLength(0) < count) then 
        rwu.Failure "Count can not be greater than array length"
    else if (count = index ) then
        rwu.successNoUndo arr
    else 
        rwu.either {        
            let! element = factory()
            arr.[index] <- Some element 
            return (initializeArrayInternal arr factory count (index+1))
        }


// val initializeArray: 
//    arr    : 'a option [] ->
//    factory: unit -> RopWithUndo.Result<'a> ->
//    count  : int          
//          -> RopWithUndo.Result<'a option []>        
let rec initializeArray arr factory count =
    initializeArrayInternal arr factory count 0 

RopWinUndo

module RopWithUndo

type Undo = unit -> unit

type Result<'success> =
    | Success of 'success * Undo list
    | Failure of string

/// success with empty Undo list. It only applies to the curretn operation. The final list is concatenated of all list and no problem if some lists are empty.
let successNoUndo result =
    Success (result,[])

let doUndo undoList =
    undoList |> List.rev |> List.iter (fun undo -> undo())

let bind f x =
    match x with
    | Failure e -> Failure e 
    | Success (s1,undoList1) ->            
        try
            match f s1 with
            | Failure e ->
                // undo everything in reverse order 
                doUndo undoList1
                // return the error
                Failure e 
            | Success (s2,undoList2) ->
                // concatenate the undo lists
                Success (s2, undoList1 @ undoList2)
        with
        | _ -> 
            doUndo undoList1
            reraise()

type EitherBuilder () =
    member this.Bind(x, f) = bind f x

    member this.ReturnFrom x = x
    member this.Return x = x
    member this.Delay(f) = f()

let either = EitherBuilder ()

【问题讨论】:

    标签: f#


    【解决方案1】:

    如果您向计算表达式构建器添加更多操作,您将能够在计算中使用 for 构造,这样会更好:

    let initializeArray (arr:_[]) factory count =
        rwu.either {
          if (arr.GetLength(0) < count) then 
            return! rwu.Failure "Count can not be greater than array length"
          for index in 0 .. count - 1 do
            let! element = factory()
            arr.[index] <- Some element 
        }
    

    为此,我必须修改您的 Return 以将结果包装到 Success (在您的原始版本中,您需要将 return 更改为 return! 无论如何这是正确的做事方式)然后我必须添加ZeroCombineFor

    type EitherBuilder () =
      member this.Return x = Success(x, [])
      member this.Bind(x, f) = bind f x
    
      member this.ReturnFrom x = x
      member this.Delay(f) = f()
    
      member this.Zero() = this.Return ()
      member this.Combine(a, b) = this.Bind(a, fun () -> b)
      member this.For(s:seq<_>, b) = 
        let en = s.GetEnumerator()
        let rec loop () = 
          if en.MoveNext() then this.Bind(b en.Current, loop)
          else this.Zero()
        loop ()
    

    【讨论】:

    • 托马斯,非常感谢您的帮助。它工作得很好。一件小事是我将删除rec,因为它不再是递归函数
    • @user1325696 啊,是的!我将在我的答案中编辑代码。
    猜你喜欢
    • 2020-12-07
    • 2020-08-10
    • 1970-01-01
    • 2019-05-12
    • 2019-12-29
    • 2010-10-19
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多