【问题标题】:F# Meta-programming: is it possible to make IF X = 1 OR 2 syntax?F# 元编程:是否可以使 IF X = 1 OR 2 语法?
【发布时间】:2012-08-27 10:12:01
【问题描述】:

我想简化表达式if(x == 1 || x == 2)
我希望我可以写 if(x == 1 or 2) 但没有语法。
其他可能性是使用 Contains 或 Any 方法,例如:if([1,2].Contains(x)),但这涉及不必要的调用。

我可以创建一些允许我这样做的运算符吗?

我可以用 Nemerle 语言编写宏:

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

然后用法:

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

我可以在 F# 中以这种方式创建运算符吗?

【问题讨论】:

  • 我不明白为什么人们要发明复杂的新宏/构造/函数来节省三个字符的代码(“简化”)。在我看来,代码稍微长一点和“标准”会更好,而不是稍微短一点并使用作者创建的一些一次性抽象,我必须去查找它的定义。
  • 无论如何,“包含”方法更通用,因为它不需要您提前知道操作数的数量。在提供用于测试列表成员资格的运算符的语言中,以下习语很常见(Python 中的示例):if x in [1,2]

标签: f# programming-languages metaprogramming nemerle


【解决方案1】:

我同意 Brian 的评论,即构建一个宏来保存三个字符可能不是一个好主意。这只会使程序更难阅读(对于那些不知道您的自定义宏或更改运算符含义的人)。

此外,您很可能可以使用标准 F# 结构(如模式匹配)以更简洁的方式编写相同的逻辑。例如:

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

惯用的解决方案将取决于具体案例,从您给出的示例中很难判断。

【讨论】:

  • 在 Nemerle 中,'||'运算符本身是标准库中的一个宏。您可以重新定义它以处理这两种用法,从而避免自定义运算符问题。 ^_^
  • +1 我认为这是 F# 中解决问题的最明确/惯用的方法。
  • @DonReba 是的,我知道 Nemerle 宏系统比 F# 中的运算符重载更强大。这非常简洁(即您可以定义诸如 F# 计算表达式或查询之类的东西,而无需等待新版本的编译器)。另一方面,这很可怕,因为人们可能会扭曲语言并编写只有他们才能理解的代码。难以权衡!
  • 所以这个观点是可以理解的,但我认为宏系统是元编程的一部分,而不是“更难阅读”。例如 Nemerle 语言本身就充满了宏,它不会让开发者感到困惑。
【解决方案2】:

您可以使用|&gt; 来完成此操作,借用一个haskell monoid 实例的常见用法。

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"

对于任意数量的参数,只需模仿 haskell 中相关的 mconcat 方法即可获得相同的效果,可能是这样的:

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

老实说,您也可以使用 Contains。如果有任何特殊的开销,我怀疑这真的很重要。

【讨论】:

  • 不,你不需要知道任何关于 monoids 的知识来写一个琐碎的if 条件。
  • 你没有。然而,这是您使用(Monoid b) =&gt; Monoid (a -&gt; b) 实例的常见方式。例如,你可以实现一个非常不切实际的素数测试,比如isPrime = getAny . mconcat(map (Any.(==)) primes),它对于非素数来说是最低的,而对于素数来说是真的。方法的实现见haskell.org/ghc/docs/7.4.2/html/libraries/base/src/…
【解决方案3】:

我同意 Brian 和 Tomas 的观点;发明自己可能只使用几次的宏有点实用意义。
但是,我确实发现从研究函数式语言的内部原理来看非常有趣。
考虑一下:

// 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

您可以在此处找到更多详细信息、其他运算符和性能测量:https://stackoverflow.com/a/11552429/974789

【讨论】:

    【解决方案4】:

    这有点骇人听闻,但确实有效

    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"
    

    【讨论】:

    • 很好,但它也覆盖了 =,有趣的部分将在宏示例中没有覆盖 = 类似
    • @МарияШарапова - 我认为这会很棘手 - 编译器推断 = 具有类型签名 'a -&gt; 'a -&gt; bool 并且大多数解决方案需要两种不同的类型
    【解决方案5】:

    这是通过将元组转换为数组来实现的,所以不要期望最好的性能。

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

    例子:

    3 == (1,2)      // false
    3 == (1,2,3)    // true
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-19
      • 2014-08-19
      • 1970-01-01
      • 2018-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-20
      相关资源
      最近更新 更多