选项的功能组成

发布于 2025-02-08 14:20:59 字数 831 浏览 2 评论 0原文

我有两个我想组合的选项(或可能的对象),以便得到以下结果:

                ||       first operand 
    second      ++-------------+-------------
    operand     ||    empty    | optional(x)
    ============||=============|=============
    empty       ||    empty    | optional(x)
    ------------++-------------+-------------
    optional(y) || optional(y) |optional(x+y)

换句话说,非空的可选始终替换/覆盖一个空的一个空,并且根据两个非空选项组合。一些+功能。

最初,我假设标准的Monadic flatmap方法可以解决问题,但是(至少在Java中)空(我不确定是否有其他实施符合Monad法律)。

然后,由于这两个操作数都以相同的单声道类型包裹,所以我认为这对于应用函数可能是一项好工作。我尝试了几个不同的功能库,但是我无法使用我尝试过的任何zip/ap方法来实现所需的行为。

在我看来,我想做的事情似乎是一个相当普遍的操作,它可能会使用选项进行,我意识到我只能用所需的行为编写自己的操作员。尽管如此,我想知道功能编程中是否有标准函数/方法可以实现此通用操作?

更新:我删除了java标签,因为我很好奇其他语言如何处理这种情况

I have 2 Optionals (or Maybe objects) that I would like to combine so that I get the following results:

                ||       first operand 
    second      ++-------------+-------------
    operand     ||    empty    | optional(x)
    ============||=============|=============
    empty       ||    empty    | optional(x)
    ------------++-------------+-------------
    optional(y) || optional(y) |optional(x+y)

In other words, a non-empty Optional always replaces/overwrites an empty one, and two non-empty Optionals are combined according to some + function.

Initially, I assumed that the standard monadic flatMap method would do the trick, but (at least in Java) Optional.flatMap always returns an empty optional when the original Optional was already empty (and I'm not sure if any other implementation would comply with the Monad Laws).

Then, as both operands are wrapped in the same monadic type, I figured that this might be a good job for an Applicative Functor. I tried a couple different functional libraries, but I couldn't implement the desired behavior with any of the zip/ap methods that I tried.

What I'm trying to do seems to me a fairly common operation that one might do with Optionals, and I realize that I could just write my own operator with the desired behavior. Still, I am wondering if there is a standard function/method in functional programming to achieve this common operation?

Update: I removed the java tag, as I'm curious how other languages handle this situation

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

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

发布评论

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

评论(7

給妳壹絲溫柔 2025-02-15 14:20:59

在功能性语言中,您将使用模式匹配来执行此操作,例如(Haskell):

combine :: Maybe t -> Maybe t -> (t -> t -> t) -> Maybe t
combine (Some x) (Some y) f = Some (f x y)
combine (Some x) _ _ = (Some x)
combine _ (Some y) _ = (Some y)
combine None None _ = None

还有其他写作方法,但是您基本上是在情况下匹配的。请注意,这仍然涉及“解开”选项,但是由于它内置在语言中,因此不太明显。

In a functional language, you'd do this with pattern matching, such as (Haskell):

combine :: Maybe t -> Maybe t -> (t -> t -> t) -> Maybe t
combine (Some x) (Some y) f = Some (f x y)
combine (Some x) _ _ = (Some x)
combine _ (Some y) _ = (Some y)
combine None None _ = None

There are other ways to write it, but you are basically pattern matching on the cases. Note that this still involves "unpacking" the optionals, but because its built into the language, it is less obvious.

谁对谁错谁最难过 2025-02-15 14:20:59

在Haskell中,您可以通过包装任何可能的semigroup。具体而言,如果您想将数字添加在一起:

Prelude> import Data.Semigroup
Prelude Data.Semigroup> Just (Sum 1) <> Just (Sum 2)
Just (Sum {getSum = 3})
Prelude Data.Semigroup> Nothing <> Just (Sum 2)
Just (Sum {getSum = 2})
Prelude Data.Semigroup> Just (Sum 1) <> Nothing
Just (Sum {getSum = 1})
Prelude Data.Semigroup> Nothing <> Nothing
Nothing

上面的链接文章包含更多解释,以及一些C#示例。

In Haskell you can do this by wrapping any semigroup in a Maybe. Specifically, if you want to add numbers together:

Prelude> import Data.Semigroup
Prelude Data.Semigroup> Just (Sum 1) <> Just (Sum 2)
Just (Sum {getSum = 3})
Prelude Data.Semigroup> Nothing <> Just (Sum 2)
Just (Sum {getSum = 2})
Prelude Data.Semigroup> Just (Sum 1) <> Nothing
Just (Sum {getSum = 1})
Prelude Data.Semigroup> Nothing <> Nothing
Nothing

The above linked article contains more explanations, and also some C# examples.

灼痛 2025-02-15 14:20:59

这是一种方法:

a.map(x -> b.map(y -> x + y).orElse(x)).or(() -> b)

ideone demo

Here's one way:

a.map(x -> b.map(y -> x + y).orElse(x)).or(() -> b)

Ideone Demo

金兰素衣 2025-02-15 14:20:59

不可能在不“解开”它们的情况下组合可选对象。

我不知道您案件的细节。对我来说,创建这样的逻辑只是为了融合这两个选项是过度杀伤。

但是,尽管如此,流也有一个解决方案。

我认为您不会将可选对象作为参数(因为不建议这样做)。因此,有两种虚拟方法返回可选&lt; t&gt;

方法combine()期望二进制操作器&lt; t&gt;作为参数,并通过串联从getx(getx(getx)返回的每个可选对象中产生的singleton-streams创建流( )gety()

redair(binaryOperator) 将产生可选的结果。

public static <T> Optional<T> getX(Class<T> t) {
    return // something
}

public static <T> Optional<T> getY(Class<T> t) {
    return // something
}

public static <T> Optional<T> combine(BinaryOperator<T> combiner, 
                                      Class<T> t) {
    
    return Stream.concat(getX(t).stream(), getY(t).stream())
        .reduce(combiner);
}

如果我们将问题推广到“如何组合 n 可选对象” ,则可以这样解决:

@SafeVarargs
public static <T> Optional<T> combine(BinaryOperator<T> combiner,
                                      Supplier<Optional<T>>... suppliers) {
    
    return Arrays.stream(suppliers)
        .map(Supplier::get)           // fetching Optional<T>
        .filter(Optional::isPresent)  // filtering optionals that contain results to avoid NoSuchElementException while invoking `get()`
        .map(Optional::get)           // "unpacking" optionals
        .reduce(combiner);
}

It's not possible to combine optional objects without "unpacking" them.

I don't know the specifics of your case. For me, creating such a logic just in order to fuse the two optionals is an overkill.

But nevertheless, there's a possible solution with streams.

I assume that you're not going to pass optional objects as arguments (because such practice is discouraged). Therefore, there are two dummy methods returning Optional<T>.

Method combine() expects a BinaryOperator<T> as an argument and creates a stream by concatenating singleton-streams produced from each of the optional objects returned by getX() and getY().

The flavor of reduce(BinaryOperator) will produce an optional result.

public static <T> Optional<T> getX(Class<T> t) {
    return // something
}

public static <T> Optional<T> getY(Class<T> t) {
    return // something
}

public static <T> Optional<T> combine(BinaryOperator<T> combiner, 
                                      Class<T> t) {
    
    return Stream.concat(getX(t).stream(), getY(t).stream())
        .reduce(combiner);
}

If we generalize the problem to "how to combine N optional objects" then it can be solved like this:

@SafeVarargs
public static <T> Optional<T> combine(BinaryOperator<T> combiner,
                                      Supplier<Optional<T>>... suppliers) {
    
    return Arrays.stream(suppliers)
        .map(Supplier::get)           // fetching Optional<T>
        .filter(Optional::isPresent)  // filtering optionals that contain results to avoid NoSuchElementException while invoking `get()`
        .map(Optional::get)           // "unpacking" optionals
        .reduce(combiner);
}
美人骨 2025-02-15 14:20:59
OptionalInt x = ...
OptionalInt y = ...

OptionalInt sum = IntStream.concat(x.stream(), y.stream())
    .reduce(OptionalInt.empty(),
        (opt, z) -> OptionalInt.of(z + opt.orElse(0)));

由于Java 9,您可以将可选的流变成流。
使用Concat,您将获得0、1或2个元素的流。

将其简化为empty在0个元素时将其添加到上一个可选的intionalInt,默认为0。

由于需要而不是很直(.sum())对于空()

OptionalInt x = ...
OptionalInt y = ...

OptionalInt sum = IntStream.concat(x.stream(), y.stream())
    .reduce(OptionalInt.empty(),
        (opt, z) -> OptionalInt.of(z + opt.orElse(0)));

Since java 9 you can turn an Optional into a Stream.
With concat you get a Stream of 0, 1 or 2 elements.

Reduce it to an empty when 0 elements,and for more add it to the previous OptionalInt, defaulting to 0.

Not very straight (.sum()) because of the need for an empty().

爱你不解释 2025-02-15 14:20:59

您可以通过组合flatmapmap

optA.flatMap(a -> optB.map(b -> a + b));

更通用的示例:

public static void main(String[] args) {
    test(Optional.empty(), Optional.empty());
    test(Optional.of(3), Optional.empty());
    test(Optional.empty(), Optional.of(4));
    test(Optional.of(3), Optional.of(4));
}

static void test(Optional<Integer> optX, Optional<Integer> optY) {
    final Optional<Integer> optSum = apply(Integer::sum, optX, optY);
    System.out.println(optX + " + " + optY + " = " + optSum);
}

static <A, B, C> Optional<C> apply(BiFunction<A, B, C> fAB, Optional<A> optA, Optional<B> optB) {
    return optA.flatMap(a -> optB.map(b -> fAB.apply(a, b)));
}

由于Flatmap和Map是可选/也许(通常(通常是单案类型)),因此方法应使用任何其他语言(尽管大多数FP语言都具有更简洁的解决方案)。例如在Haskell中:

combine ma mb = do a <- ma ; b <- mb ;  return (a + b)

You can implement your function in Java by combining flatMap and map:

optA.flatMap(a -> optB.map(b -> a + b));

More general example:

public static void main(String[] args) {
    test(Optional.empty(), Optional.empty());
    test(Optional.of(3), Optional.empty());
    test(Optional.empty(), Optional.of(4));
    test(Optional.of(3), Optional.of(4));
}

static void test(Optional<Integer> optX, Optional<Integer> optY) {
    final Optional<Integer> optSum = apply(Integer::sum, optX, optY);
    System.out.println(optX + " + " + optY + " = " + optSum);
}

static <A, B, C> Optional<C> apply(BiFunction<A, B, C> fAB, Optional<A> optA, Optional<B> optB) {
    return optA.flatMap(a -> optB.map(b -> fAB.apply(a, b)));
}

Since flatMap and map are standard functions for Optional/Maybe (and monad types generally), this approach should work in any other language (though most FP languages will have a more concise solution). E.g. in Haskell:

combine ma mb = do a <- ma ; b <- mb ;  return (a + b)
金兰素衣 2025-02-15 14:20:59

在f#中,我称此逻辑redail

原因:

  • 该函数必须是类型'a - &gt; 'a - &gt; 'a,因为它只能结合相等类型的思想。

  • 类似于其他降低操作,例如在列表上,您始终需要至少一个值,否则会失败。

使用选项和其中两个,您只需要介绍四个情况即可。在f#中,它将以这种方式写。

(* Signature: ('a -> 'a -> 'a) -> option<'a> -> option<'a> -> option<'a> *)
let reduce fn x y =
    match x,y with
    | Some x, Some y -> Some (fn x y)
    | Some x, None   -> Some x
    | None  , Some y -> Some y
    | None  , None   -> None

printfn "%A" (reduce (+) (Some 3) (Some 7)) // Some 10
printfn "%A" (reduce (+) (None)   (Some 7)) // Some 7
printfn "%A" (reduce (+) (Some 3) (None))   // Some 3
printfn "%A" (reduce (+) (None)   (None))   // None

另一个可以说,像伪式的C#语言一样,看起来像。

Option<A> Reduce(Action<A,A,A> fn, Option<A> x, Option<A> y) {
    if ( x.isSome ) {
        if ( y.isSome ) {
            return Option.Some(fn(x.Value, y.Value));
        }
        else {
            return x;
        }
    }
    else {
        if ( y.isSome ) {
            return y;
        }
        else {
            return Option.None;
        }
    }
}

In F#, i would call this logic reduce.

Reason:

  • The function must be of type 'a -> 'a -> 'a as it only can combine thinks of equal type.

  • Like other reduce operations, like on list, you always need at least one value, otherwise it fails.

With a option and two of them, you just need to cover four cases. In F# it will be written this way.

(* Signature: ('a -> 'a -> 'a) -> option<'a> -> option<'a> -> option<'a> *)
let reduce fn x y =
    match x,y with
    | Some x, Some y -> Some (fn x y)
    | Some x, None   -> Some x
    | None  , Some y -> Some y
    | None  , None   -> None

printfn "%A" (reduce (+) (Some 3) (Some 7)) // Some 10
printfn "%A" (reduce (+) (None)   (Some 7)) // Some 7
printfn "%A" (reduce (+) (Some 3) (None))   // Some 3
printfn "%A" (reduce (+) (None)   (None))   // None

In another lets say Pseudo-like C# language, it would look like.

Option<A> Reduce(Action<A,A,A> fn, Option<A> x, Option<A> y) {
    if ( x.isSome ) {
        if ( y.isSome ) {
            return Option.Some(fn(x.Value, y.Value));
        }
        else {
            return x;
        }
    }
    else {
        if ( y.isSome ) {
            return y;
        }
        else {
            return Option.None;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文