F#/OCaml:如何避免重复的模式匹配?

发布于 2024-11-26 12:54:26 字数 1352 浏览 2 评论 0原文

看一下这段 F#/OCaml 代码:

type AllPossible =
    | A of int
    | B of int*int
    | ...
    | Z of ...

let foo x =
    ....
    match x with
    | A(value) | B(value,_) ->                   (* LINE 1 *)
        (* do something with the first (or only, in the case of A) value *)
        ...
        (* now do something that is different in the case of B *)
        let possibleData = 
            match x with
            | A(a) -> bar1(a)
            | B(a,b) -> bar2(a+b)
            | _ -> raise Exception    (* the problem - read below *)
        (* work with possibleData *)
    ...
    | Z -> ...

那么问题是什么? 在函数 foo 中,我们针对大量类型进行模式匹配。 有些类型共享功能 - 例如它们具有共同的 有工作要做,所以我们使用“|A | B ->”在上面的第 1 行。 我们读取唯一的整数(在 A 的情况下),或者第一个整数 (在 B 的情况下)并用它做一些事情。

接下来,我们想做一些完全不同的事情,具体取决于 关于我们是否在 A 或 B 上工作(即调用 bar1 或 bar2)。 现在我们必须再次进行模式匹配,这就是问题所在: 嵌套模式匹配,除非我们添加“catchAll”规则(即“_”), 编译器抱怨我们缺少案例 - 即它没有 考虑到这里只有 A 和 B 可能发生。

但如果我们添加 catchAll 规则,那么我们就会遇到更糟糕的问题: 如果在某个时候我们在 LINE1 列表中添加更多类型 (即在“|A | B ->”行中...那么编译器将不会帮助 我们在嵌套匹配中 - '_' 会捕获它们,并且会出现错误 在运行时被检测到。最重要的权力之一 模式匹配(即在编译时检测此类错误)丢失了。

有没有更好的方法来编写这种代码,而无需 在两个单独的项目中重复 A 和 B 之间共享的任何工作 A 和 B 的规则? (或者将 A 和 B 的共同工作放在一个函数中 仅仅为了 A 和 B 之间的“本地代码共享”而创建?)

编辑:请注意,在这种情况下,人们可能会认为 F# 编译器的行为存在错误 - 它应该能够检测到除了 A 和 B 之外不需要匹配 在嵌套匹配中。

Have a look at this F#/OCaml code:

type AllPossible =
    | A of int
    | B of int*int
    | ...
    | Z of ...

let foo x =
    ....
    match x with
    | A(value) | B(value,_) ->                   (* LINE 1 *)
        (* do something with the first (or only, in the case of A) value *)
        ...
        (* now do something that is different in the case of B *)
        let possibleData = 
            match x with
            | A(a) -> bar1(a)
            | B(a,b) -> bar2(a+b)
            | _ -> raise Exception    (* the problem - read below *)
        (* work with possibleData *)
    ...
    | Z -> ...

So what is the problem?
In function foo, we pattern match against a big list of types.
Some of the types share functionality - e.g. they have common
work to do, so we use "|A | B ->" in LINE 1, above.
We read the only integer (in the case of A), or the first integer
(in the case of B) and do something with it.

Next, we want to do something that is completely different, depending
on whether we work on A or B (i.e. call bar1 or bar2).
We now have to pattern match again, and here's the problem: In this
nested pattern match, unless we add a 'catchAll' rule (i.e. '_'),
the compiler complains that we are missing cases - i.e. it doesn't
take into account that only A and B can happen here.

But if we add the catchAll rule, then we have a far worse problem:
if at some point we add more types in the list of LINE1
(i.e. in the line '|A | B ->' ... then the compiler will NOT help
us in the nested match - the '_' will catch them, and a bug will
be detected at RUNTIME. One of the most important powers of
pattern matching - i.e. detecting such errors at compile-time - is lost.

Is there a better way to write this kind of code, without having
to repeat whatever work is shared amongst A and B in two separate
rules for A and B? (or putting the A-and-B common work in a function
solely created for the purpose of "local code sharing" between A and B?)

EDIT: Note that one could argue that the F# compiler's behaviour is buggy in this case -
it should be able to detect that there's no need for matching beyond A and B
in the nested match.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最后的乘客 2024-12-03 12:54:26

如果数据类型是一成不变的 - 我也更喜欢本地函数。

否则,在 OCaml 中您还可以享受开放(又名多态)变体:

type t = [`A | `B | `C]
let f = function
| (`A | `B as x) ->
  let s = match x with `A -> "a" | `B -> "b" in
  print_endline s
| `C -> print_endline "ugh"

If the datatype is set in stone - I would also prefer local function.

Otherwise, in OCaml you could also enjoy open (aka polymorphic) variants :

type t = [`A | `B | `C]
let f = function
| (`A | `B as x) ->
  let s = match x with `A -> "a" | `B -> "b" in
  print_endline s
| `C -> print_endline "ugh"
酒与心事 2024-12-03 12:54:26

我只是将通用逻辑放在本地函数中,应该更快且更具可读性。以这种方式嵌套的匹配非常难以理解,并且将通用逻辑放入本地函数中可以让您放弃额外的匹配,转而使用无论如何都会内联的内容。

I would just put the common logic in a local function, should be both faster and more readable. Matches nested that way is pretty hard to follow, and putting the common logic in a local function allows you to ditch the extra matching in favour of something that'll get inlined anyway.

心的憧憬 2024-12-03 12:54:26

嗯,看起来您需要稍微不同地设计数据类型,例如:

type AorB = 
    | A of int
    | B of int * int

type AllPossible =
    | AB of AorB
    | C of int
    .... other values

let foo x =
    match x with
    | AB(v) -> 
        match v with
        | A(i) -> () //Do whatever need for A
        | B(i,v) -> () // Do whatever need for B
    | _ -> ()

Hmm looks like you need to design the data type a bit differently such as:

type AorB = 
    | A of int
    | B of int * int

type AllPossible =
    | AB of AorB
    | C of int
    .... other values

let foo x =
    match x with
    | AB(v) -> 
        match v with
        | A(i) -> () //Do whatever need for A
        | B(i,v) -> () // Do whatever need for B
    | _ -> ()
甜心 2024-12-03 12:54:26

也许更好的解决方案是,

type All =
    |A of int
    |B of int*int

如果

type All = 
    |AorB of int * (int Option)

您稍后以不同的方式绑定数据,您可能最好使用活动模式而不是类型,但结果基本上是相同的

Perhaps the better solution is that rather than

type All =
    |A of int
    |B of int*int

you have

type All = 
    |AorB of int * (int Option)

If you bind the data in different ways later on you might be better off using an active pattern rather than a type, but the result would be basically the same

怎言笑 2024-12-03 12:54:26

我不太同意这应该被视为一个错误 - 尽管如果由编译器处理这种情况肯定会很方便。

C# 编译器不会抱怨以下情况,您也不会期望它会抱怨:

var b = true;

if (b)
    if (!b)
       Console.WriteLine("Can never be reached");

I don't really agree that this should be seen as a bug - although it would definitely be convenient if the case was handled by the compiler.

The C# compiler doesn't complain to the following and you wouldn't expect it to:

var b = true;

if (b)
    if (!b)
       Console.WriteLine("Can never be reached");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文