如何“连接”两个选项

huangapple go评论74阅读模式
英文:

How to "concat" two options

问题

我在查看 F# 文档,对 Option 模块中的一个 "缺失" 函数感到好奇。有一个 map2 函数,它接受两个选项并似乎执行 逻辑与 操作。如果其中一个是 None,则结果也是 None。否则,如果你有 Some(a) 和 Some(b),它会返回 Some(f(a, b))。

那么 逻辑或 函数在哪里呢?我期望有一个类似的函数,如果只有一个是 Some,就返回它。但如果两者都是 Some,那么执行 Some(f(a, b)),就像逻辑与的情况一样。我可以手动编写这个函数,使用模式匹配没有问题。

let concat f a b =
    match a, b with
    | Some(a), Some(b) -> Some(f a b)
    | Some(a), None -> Some(a)
    | None, Some(b) -> Some(b)
    | _ -> None

实际上,这在处理可选数字列表并将它们相加时可能很有用。如果列表为空或只包含 None 值,答案将是 None。None 不会影响结果,就像零一样。或者处理一些可选字符串,如果你想要将它们连接在一起。

我假设由于 F# 标准库中似乎没有这个函数,我可能在考虑事情的方式上有误。或者是否有更简洁的方法来实现它?

英文:

I'm looking through the F# docs and am wondering about a "missing" function in the Option module. There is a map2 that takes two options and seems to do a logical AND on it. If either is None, the result is None. Otherwise if you've got a Some(a) and Some(b), it returns a Some(f(a,b)).

Where is the logical OR function? I would expect a similar function where if just one is Some, return it. But if both are Some, it would do the Some(f(a,b)) like for the logical AND case. I can write this out manually no problem with a pattern matching.

let concat f a b =
    match a, b with
    | Some(a), Some(b) -> Some(f a b)
    | Some(a), None -> Some(a)
    | None, Some(b) -> Some(b)
    | _ -> None

Practically this could be useful if you've got a list of optional numbers and you want to add them up. The answer is None if the list is empty or only contains None values. None doesn't affect the result, like a zero. Or a couple optional strings and you want to concatenate them.

I'm assuming since F# doesn't seem to have this function in the standard library, I must be thinking about things the wrong way. Or is there a more concise way of doing it?

答案1

得分: 5

你正在寻找的应该是以下类型的函数(请注意,这种类型与map2的类型不同,因为作为参数提供的两个选项需要具有相同的类型(以便在另一个不可用时可以返回其中一个):

('a -> 'a -> 'a) -> 'a option -> 'a option -> 'a option

据我所见,标准的 F# 库中并没有这个函数,但它似乎是一个潜在有用的函数。例如,在 Haskell 中存在名为 unionWith 的函数(参见 reducers 包中的 unionWith) - 当然,以更通用的形式存在。

我认为将这个函数的实现添加到你的 "utils" 库中或建议将其添加到 F# 核心库是一个非常合理的主意。我也会像你一样使用 match 来实现这个函数。不过有趣的是,你也可以使用 map2orElse 来实现相同的行为(我认为这种方式可能不太易读,而且可能更慢):

let unionWith f a b = 
  Option.map2 f a b |> Option.orElse a |> Option.orElse b
英文:

What you are looking for would be a function of the following type (Note that the type is different than that of map2 because the two options provided as argument need to have the same type (so that you can return one or the other when the other one is not available):

('a -> 'a -> 'a) -> 'a option -> 'a option -> 'a option

As far as I can see, this does not exist in the standard F# library, but it sounds like a potentially useful function. For example, this exists in Haskell as unionWith (see unionWith in the reducers package) - though, of course, in a more generalized form.

I think it is a very reasonable idea to add an implementation of this to your "utils" library or suggest an addition to the F# core libraries. I would also implement this using match as you did. Although it is perhaps interesting that you can also get this behaviour using map2 and orElse (which is, I think, less readable and probably slower):

let unionWith f a b = 
  Option.map2 f a b |> Option.orElse a |> Option.orElse b

答案2

得分: 4

以下是代码的翻译部分:

我认为你的实现是不错的尽管它可以进一步简化

let concat f a b =
    match a, b with
    | Some(a), Some(b) -> Some(f a b)
    | _, None -> a
    | None, _ -> b

这是一个关于"maybe monoid"的讨论。基本原则是任何半群都可以通过添加一个标识元素变成一个幺半群。你的 f 函数定义了半群,而 None 是标识元素。

更多讨论可以在这里找到。请查找标题为"A generic solution"的部分,其中有一个名为 optionAdd 的函数。


离题:Haskell 可以以不同的方式处理这个问题,而且方式很酷。不需要显式传递 f,它由类型类实例定义,然后可以使用 mappend 来组合值。例如:

λ mappend (Just "Fizz") (Just "Buzz")
Just "FizzBuzz"

λ mappend (Just "Fizz") Nothing
Just "Fizz"

λ mappend Nothing (Just "Buzz")
Just "Buzz"

现在让我们尝试用整数代替字符串:

λ mappend (Just 3) (Just 4)
No instance for (Show a0)
arising from a use of show_M316159813823190938125858
The type variable a0 is ambiguous

哎呀,发生了什么?问题在于没有唯一的方法来组合整数,因此我们必须通过一个包装类型更加具体:

λ mappend (Sum 3) (Sum 4)
Sum {getSum = 7}

λ mappend (Product 3) (Product 4)
Product {getProduct = 12}
英文:

I think your implementation is fine, although it can be simplified further:

let concat f a b =
    match a, b with
    | Some(a), Some(b) -> Some(f a b)
    | _, None -> a
    | None, _ -> b

Note that what you're describing is a "maybe monoid". The basic principle is that any semigroup can be turned into a monoid by adding an identity element. Your f function defines the semigroup, and None is the identity element.

Further discussion can be found here. Look for the function called optionAdd in the section titled "A generic solution".


Digression: Haskell can handle this differently, and in a cool way. Instead of passing f explicitly, it's defined by a typeclass instance, and you can then combine values using mappend. For example:

λ mappend (Just "Fizz") (Just "Buzz")
Just "FizzBuzz"

λ mappend (Just "Fizz") Nothing
Just "Fizz"

λ mappend Nothing (Just "Buzz")
Just "Buzz"

Now let's try it with integers instead of strings:

λ mappend (Just 3) (Just 4)
No instance for (Show a0)
arising from a use of show_M316159813823190938125858
The type variable a0 is ambiguous

Yikes - what happened? The problem is that there is no unique way to combine integers, so we have to be more specific via a wrapper type:

λ mappend (Sum 3) (Sum 4)
Sum {getSum = 7}

λ mappend (Product 3) (Product 4)
Product {getProduct = 12}

huangapple
  • 本文由 发表于 2023年3月9日 15:30:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75681578.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定