英文:
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
来实现这个函数。不过有趣的是,你也可以使用 map2
和 orElse
来实现相同的行为(我认为这种方式可能不太易读,而且可能更慢):
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}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论