F # Железнодорожное программирование, эта инициализация может быть улучшена? - PullRequest
3 голосов
/ 01 апреля 2019

Я изучаю F #, и мне интересно, можно ли улучшить реализацию этого метода, которая инициализирует первые N элементов массива. На данный момент работает отлично. Я имею в виду, что если он потерпит неудачу, когда попытается инициализировать 2-й элемент, выполнив 2-й вызов фабрики, то if вызовет отмену для первого успешного результата. Единственная незначительная проблема - это не чистить элементы в массиве в случае ошибки, но я не беспокоюсь об этом. Все, что я волнуюсь, это то, что он должен отменить первые успешные результаты, если он потерпит неудачу 2-го или 3-го или позже. Если это удастся, то результат успеха должен иметь в списке отмен все функторы, которые нужно отменить.

Дело в том, что я хотел бы избежать рекурсии и использовать что-то вроде Linq для итерации и выполнения чего-либо, но не совсем ясно, как в этом случае делать 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 ()

1 Ответ

5 голосов
/ 01 апреля 2019

Если вы добавите еще несколько операций к вашему построителю выражений вычислений, вы сможете использовать конструкцию 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!, что в любом случае является правильным способом выполнения действий), а затем мне пришлось добавить Zero, Combine и For:

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 ()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...