Инспекторы и деконструкции на профсоюзы в список


Я еще новичок в функциональное программирование и нашли себя, пытаясь filter и map список дискриминацию профсоюзов. Я могу ошибаться, но он начал чувствовать себя очень неестественно для этого. Я просто не мог выяснить, как получить рабочий процесс, который я хотела CreateThing метод.

В IsTrue/False и Value/Error методы очень полезны, но их реализаций кажутся неуклюжими. Но они-то, что позволяет мне работать с ООС в виде списка.

Есть ли более элегантный/естественное решение для этого?

module Test 

open System.Collections.Generic

// some borrowed finaglery from s.o.
module Seq =
    // borrowed from: https://stackoverflow.com/a/12564899
    // takes until the condition is true plus one more
    let takeUntil predicate (s:seq<_>) = 
        let rec loop (en:IEnumerator<_>) = seq {
            if en.MoveNext() then
                yield en.Current
            if predicate en.Current then
                yield! loop en 
            }

        seq { use en = s.GetEnumerator()
            yield! loop en 
            }

// the union in question
type Valid<'a> = 
    | Valid of 'a 
    | Invalid of string

// the inspectors and destructors in question
module Valid =

    let IsTrue v = match v with |Valid _-> true |_-> false
    let IsFalse v = match v with |Valid _-> false |_-> true
    let Value v = match v with |Valid v -> v | Invalid _ -> failwith "no value"
    let Error v = match v with |Valid _ -> failwith "no error" | Invalid i -> i

module UseCase = 

    type Thing = {
        Values : int list
    }

    let ValidateNumber (i : int) : Valid<int> = 
        if i = 3 then Invalid "no threes yo" else Valid i

    let CreateThing (inputs : int seq) : Valid<Thing> =
        // pull items through the seq until i hit a problem
        let worked = inputs |> Seq.map ValidateNumber |> Seq.takeUntil Valid.IsFalse |> Seq.toList

        // see if it all worked out or not
        let success = List.last worked |> Valid.IsTrue

        if success then
            // deconstruct my 'valids' back to primitive ints
            Valid {Thing.Values = worked |> List.map Valid.Value}
        else
            // deconstruct my last error into a new error
            Invalid (List.last worked |> Valid.Error)


100
1
задан 20 февраля 2018 в 02:02 Источник Поделиться
Комментарии
1 ответ

Принципиально, для работы с ООС, нет другого пути. Дю представляет ценности, что может быть "или-или". Это означает, что, когда ты смотришь на одно из этих значений, код должен обрабатывать все возможные варианты того, что это значение может быть, в противном случае ваша программа просто не знаю, что делать в некоторых случаях во время выполнения. Нет никакого способа вокруг него.

Что сказал, Вы можете выразить основную идею в разных вариантах.

Во-первых, вы можете использовать function сайта, чтобы сократить шаблон матч немного. let f = function это просто сокращение для let f x = match x with:

let IsTrue = function Valid _ -> true | _ -> false

Во-вторых, вы можете выразить IsFalse как отрицание IsTrue:

let IsFalse = not << IsTrue

В качестве альтернативы, вы могли бы извлечь шаблону участие в функции (обычно называемый "catamorphism"):

let inline validCata ifValue ifError = function Value x -> ifValue x | Error x -> ifError x

а затем выразить другие функции с точки зрения его:

let inline IsTrue x = validCata (ct true) (ct false) x
let inline IsFalse x = validCata (ct false) (ct true) x
let inline Value x = validCata id (fun _ -> failwith "") x
let inline Error x = validCata (fun _ -> failwith "") id x

(где ct это тривиально-постоянная функция, которой, к сожалению, отсутствует в F# стандартной библиотеки)

Что касается CreateThing функция, позвольте мне увидеть, если я понял цель правильно: вы пытаетесь проверить последовательность цифр и залог при первой же ошибке, возвращая его; или возвращает список чисел, если все они являются допустимыми.

Если это правильное намерение, вам не нужно обернуть каждое число в Valid на всех. Просто запустите функцию проверки на них, пока он не возвращает ошибку, а если это не так, возврата к исходному списку. Для запуска функции на последовательности и останавливается, когда она попадает в первый раз, имеется удобная библиотека функции Seq.tryPick:

let errorFor i =
if i = 3 then Some "no threes yo" else None

let createThing inputs =
let firstError = Seq.tryPick errorFor inputs
match firstError with
| Some err -> Invalid err
| None -> Valid { Thing.Values = List.ofSeq inputs }

Если вы столкнулись с этой проблемы часто, и очень хочется использовать Valid как механизм общего назначения для решения с ошибками, вы можете извлечь "запуск последовательности до ошибки" часть как многоразовая вещь:

let validSeq (s: Valid<'a> seq) : Valid<'a list> =
let getError = function Valid _ -> None | Error err -> Some err
let getValue = function Valid x -> x | Error _ -> Unchecked.defaultOf<'a>
let lst = List.ofSeq s
match List.tryPick getError lst with
| Some err -> Error err
| None -> lst |> List.map getValue |> Valid

let validMap f = function
| Value v -> Valid (f v)
| Error err -> Error err

let createThing inputs =
inputs
|> Seq.map ValidateNumber
|> validSeq
|> validMap (fun vs -> { Thing.Values = vs })

(обратите внимание, что здесь я также извлекается validMap как многоразовая вещь)

Заключительное Примечание: Если вы на самом деле нажмите (и измеряется) проблемы с производительностью, не работают с последовательностями, работа со списками вместо. Поскольку F# - это не чисто, бег последовательности несколько раз, может быть дорогим и непредсказуемым.

2
ответ дан 20 февраля 2018 в 05:02 Источник Поделиться