Сопоставление с шаблоном с защитой против конструкции if/else в F#

В языках семейства ML люди предпочитают if/else построить. В F# использование защит в рамках сопоставления с образцом может легко заменить if/else во многих случаях.

Например, простой delete1 функция может быть переписана без использования if/else (увидеть delete2):

let rec delete1 (a, xs) =
    match xs with
    | [] -> []
    | x::xs' -> if x = a then xs' else x::delete1(a, xs') 

let rec delete2 (a, xs) =
    match xs with
    | [] -> []
    | x::xs' when x = a -> xs'
    | x::xs' -> x::delete2(a, xs') 

Другой пример - решение квадратичных функций:

type Solution =
    | NoRoot
    | OneRoot of float
    | TwoRoots of float * float

let solve1 (a,b,c) = 
    let delta = b*b-4.0*a*c
    if delta < 0.0 || a = 0.0 then NoRoot 
    elif delta = 0.0 then OneRoot (-b/(2.0*a))
    else 
        TwoRoots ((-b + sqrt(delta))/(2.0*a), (-b - sqrt(delta))/(2.0*a))

let solve2 (a,b,c) = 
    match a, b*b-4.0*a*c with
    | 0.0, _  -> NoRoot
    | _, delta when delta < 0.0 -> NoRoot
    | _, 0.0 -> OneRoot (-b/(2.0*a))
    | _, delta -> TwoRoots((-b + sqrt(delta))/(2.0*a),(-b - sqrt(delta))/(2.0*a))

Должны ли мы использовать сопоставление с охранниками, чтобы игнорировать уродливые if/else построить?

Есть ли какое-то влияние на производительность при использовании сопоставления с паттернами? У меня сложилось впечатление, что это кажется медленным, потому что сопоставление с образцом было проверено во время выполнения.

4 ответа

Решение

Правильный ответ, вероятно, зависит, но я предполагаю, что в большинстве случаев скомпилированное представление одинаково. В качестве примера

let f b =
  match b with
  | true -> 1
  | false -> 0

а также

let f b =
  if b then 1
  else 0

оба переводят на

public static int f(bool b)
{
    if (!b)
    {
        return 0;
    }
    return 1;
}

Учитывая это, это в основном вопрос стиля. Лично я предпочитаю сопоставление с образцом, потому что случаи всегда выровнены, что делает его более читаемым. Кроме того, их (возможно) легче расширить позже, чтобы обрабатывать больше случаев. Я считаю, что образец соответствует эволюции if/then/else,

Также нет дополнительных затрат времени выполнения для сопоставления с образцом, с защитой или без нее.

Оба имеют свое место. Люди более привыкли к конструкции If/else для проверки значения, где сопоставление с образцом похоже на If/else на стероидах. Сопоставление с образцом позволяет вам сравнивать decomposed структура данных вместе с использованием gaurds для указания некоторых дополнительных условий на части разложенных данных или некоторого другого значения (особенно в случае рекурсивных структур данных или так называемых различающихся объединений в F#).

Лично я предпочитаю использовать if/else для простых сравнений значений (true/false, ints и т. Д.), Но в случае, если у вас есть рекурсивная структура данных или что-то, что вам нужно сравнить с разложенным значением, нет ничего лучше, чем сопоставление с образцом.

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

Согласитесь с @Daniel, что сопоставление с образцом обычно более гибкое. Проверьте эту реализацию:

type Solution = | Identity | Roots of float list

let quadraticEquation x =

    let rec removeZeros list =
        match list with
        | 0.0::rest -> removeZeros rest
        | _ -> list
    let x = removeZeros x

    match x with
    | [] -> Identity // zero constant
    | [_] -> Roots [] // non-zero constant
    | [a;b] -> Roots [ -b/a ] // linear equation
    | [a;b;c] ->
        let delta = b*b - 4.0*a*c
        match delta with
        | delta when delta < 0.0 -> 
            Roots [] // no real roots
        | _ ->
            let d = sqrt delta
            let x1 = (-b-d) / (2.0*a)
            let x2 = (-b+d) / (2.0*a)
            Roots [x1; x2]
    | _ -> failwithf "equation is bigger than quadratic: %A" x

Также обратите внимание на https://fsharpforfunandprofit.com/learning-fsharp/ что не рекомендуется использовать if-else. Это считается ставкой менее функциональным.

Я провел некоторое тестирование на самописанном генераторе простых чисел, и, насколько я могу сказать, "если тогда еще" значительно медленнее, чем сопоставление с образцом, я не могу объяснить почему, но я постарался проверить императив. часть F# имеет более медленное время выполнения, чем рекурсивный функциональный стиль, когда дело доходит до оптимальных алгоритмов.

Другие вопросы по тегам