Как написать более понятный код в функциональном стиле? - PullRequest
0 голосов
/ 15 февраля 2019

Все еще в процессе превращения моего кода во все более функциональный стиль и внешний вид.

Здесь у меня есть функция, которую я стараюсь сделать как можно более общей, передавая функцию фильтра ифункция вычисления в качестве параметров.

let calcError filter (fcalc:'a -> float) (arr:'a array) =
        arr |> Array.filter filter
            |> Array.map fcalc
            |> Array.average

Подпись:

val calcError : filter:('a -> bool) -> fcalc:('a -> float) -> arr:'a array -> float

Я считаю, что это довольно стандартно, используя calcError с частичными приложениями.

Однако Array.Среднее значение будет вызывать исключения, если массив имеет размер 0 или равен нулю (что не произойдет в моем случае).

Не большой поклонник исключений в F #, я бы предпочел использовать либо (вывод с плавающей запятой), либоРезультат.

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

Спасибо всем

Решение, которое я имею в виду:

let calcError2 filter (fcalc:'a -> float) (arr:'a array) =
    let subarr = arr |> Array.filter filter
    match subarr.Length with
    | 0 -> Result.Error "array length 0"
    | _ -> subarr |> Array.map fcalc
                  |> Array.average
                  |> Result.Ok

Ответы [ 3 ]

0 голосов
/ 15 февраля 2019

Вот еще одна версия с вспомогательной функцией.

let calcError filter (fcalc:'a -> float) (arr:'a array) =
    let safeAverage ar = if Array.isEmpty ar then None else Some(Array.average ar)
    arr |> Array.filter filter
            |> Array.map fcalc
            |> safeAverage

Более того, вы можете преобразовать массив в опцию, чтобы использовать его с любой другой небезопасной функцией массива.

let nat arr = if Array.isEmpty arr then None else Some(arr)


let calcError filter (fcalc:'a -> float) (arr:'a array) =
        arr |> Array.filter filter
                |> Array.map fcalc
                |> nat
                |> Option.bind (Some << Array.average )

Вот более компактная и эффективная версия, использующая стиль без точек.

let calcError filter (fcalc:'a -> float)   =
       Option.bind (Some << (Array.averageBy fcalc)) << nat << Array.filter filter  

Мне потребовалось некоторое время, чтобы по-настоящему оценить ценность создания множества небольших функций.Надеюсь, поможет.

0 голосов
/ 17 февраля 2019

Ваш код выглядит хорошо для меня.Единственное, что я бы сделал по-другому, это то, что я бы не использовал match для проверки, является ли массив пустым - вы не привязываете какие-либо переменные и у вас есть только два случая, так что вы действительно можете просто использовать выражение ifВот.

Два других незначительных изменения в том, что я использую Array.isEmpty, чтобы увидеть, не является ли массив пустым (это, вероятно, не имеет никакого эффекта, но если бы вы использовали последовательности, это было бы быстрее, чем проверка длины)и я также использую averageBy вместо map, за которым следует average:

let calcError2 filter (fcalc:'a -> float) (arr:'a array) =
    let subarr = arr |> Array.filter filter
    if Array.isEmpty subarr then Result.Error "array length 0" 
    else subarr |> Array.averageBy fcalc |> Result.Ok
0 голосов
/ 15 февраля 2019

Это один из способов сделать это:

let tryCalcError filter (fcalc:'a -> float) (arr:'a array) =
    arr |> Array.filter filter
        |> Array.map fcalc
        |> function
        | [||] -> None
        | arr  -> Array.average arr |> Some

Это следует за соглашением префикса с try, чтобы указать, что возвращаемое значение является опцией.Вы можете увидеть это соглашение в нескольких Seq.try... функциях, таких как tryFind, tryHead, tryLast, tryItem, tryPick.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...