F# Мета-программирование: возможно ли сделать синтаксис IF X = 1 или 2?

Я хочу упростить выражение if(x == 1 || x == 2),
Я хотел бы написать if(x == 1 or 2) но для этого нет синтаксиса.
Другой возможностью является использование метода Contains или Any, например: if([1,2].Contains(x)) но это включает в себя ненужный вызов.

Могу ли я создать какой-нибудь оператор, который позволит мне это сделать?

На немерльском языке я могу написать макрос:

macro @|||(left, right)
  match (left)
    | <[ $x == $y ]> => <[ $x == $y || $x == $right ]>
    | _ => Message.Error("Error"); <[ ]>

И тогда использование:

if (x == 1 ||| 2) { .. }

Могу ли я создать оператор таким образом в F#?

5 ответов

Решение

Вы могли бы использовать |> чтобы добиться этого, заимствуя из общего использования один из экземпляров моноидов haskell.

let appendResults f g = (fun x -> f(x) || g(x))
let f x = x=1
let g x = x=2
let inline (>>||) x y = (appendResults f g) x y
let x = 1
if(x |> (1 >>|| 2)) then printfn "true" else printfn "false"

Для произвольного числа аргументов, просто имитировать соответствующие mconcat Метод из haskell для того же эффекта, возможно, так:

let rec concatResults = function
| [] -> (fun x -> false)
| (x:xs) -> appendResults x (concatResults xs)

Честно говоря, вы можете использовать Contains. Если есть какие-то особые накладные расходы, я сомневаюсь, что это действительно имеет значение.

Я согласен с комментарием Брайана о том, что создание макроса для сохранения трех символов, вероятно, не очень хорошая идея. Это только сделает программу более трудной для чтения (для тех, кто не знает ваших пользовательских макросов или изменил значение операторов).

Более того, вполне вероятно, что вы могли бы написать ту же логику более кратким способом, используя стандартные конструкции F#, такие как сопоставление с образцом. Например:

match x with
| 1 | 2 -> printfn "yes"
| _     -> printfn "no"

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

Я согласен с Брайаном и Томасом; имеет немного практического смысла придумывать свои собственные макросы, которые могут быть использованы всего несколько раз.
Однако я нахожу это очень интересным с точки зрения изучения внутренних функций функциональных языков.
Учти это:

// Generic
let inline mOp1<'a> op sample x = op sample x, sample
let inline mOp2<'a> op1 op2 (b, sample) x = op1 b (op2 sample x), sample

// Implementation for (=) and (||)
let (==) = mOp1 (=)
let (|=) = mOp2 (||) (=)

// Use
let ret1 = x == 1 |= 2 |> fst

Вы можете найти более подробную информацию, других операторов и измерения производительности здесь: /questions/43618583/est-li-sposob-sdelat-sravnenie-neskolkih-znachenij-v-stroke/43618597#43618597

Это немного хакерски, но это работает

let x = 1
let inline (|||) a b = [a;b]
let inline (==) a b = b |> List.exists (fun t -> t=a)

if x == (1 ||| 2) then printfn "true" else printfn "false"

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

Конечно, если вам нужно только 2 номера, вы можете сделать

let x = 1
let inline (|||) a b = (a,b)
let inline (==) a (c,d) = a=c ||a=d

if x == (1 ||| 2) then printfn "true" else printfn "false"

Это работает путем преобразования набора в массив, поэтому не ожидайте лучшей производительности.

let inline (==) a b = 
    Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields(b) 
        |> Array.exists((=) a)

Пример:

3 == (1,2)      // false
3 == (1,2,3)    // true
Другие вопросы по тегам