You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:在C#中实现Either Monad的Apply方法遇类型检查问题

如何为C#中的Either Monad实现Apply方法

我来帮你搞定这个Either的Apply方法问题!首先,咱们先拆解一下你遇到的问题,然后一步步实现正确的Apply逻辑,再修正你的测试代码。

问题分析

你代码里的Left(add).Apply(3)通不过类型检查,核心原因有两个:

  1. Either的语义理解偏差:Left是用来承载错误/失败类型的,而函数作为"正常逻辑的值"应该放在Right里。你用Left(add)相当于把函数标记成了错误值,这完全不符合Either的设计意图。
  2. 缺少对应的Apply扩展方法:LaYumba.Functional库的Either默认可能没有提供你需要的Apply重载(尤其是支持直接传入普通值而非Either的重载),所以需要我们自己实现。

正确的Either Apply扩展方法实现

我们需要为Either实现两种常用的Apply重载:一种是接收Either<L, T>参数的纯函数式重载,另一种是支持直接传入普通值(自动包装为Right)的便捷重载,这样就能和Option的写法保持一致。

using LaYumba.Functional;
using static LaYumba.Functional.F;

public static class EitherExtensions
{
    // 基础重载:将Either中的函数应用到另一个Either的值上
    public static Either<L, R> Apply<T, R, L>(this Either<L, Func<T, R>> funcEither, Either<L, T> argEither)
    {
        return funcEither.Match(
            Left: error => Left<L, R>(error), // 如果函数是错误,直接返回错误
            Right: func => argEither.Match(
                Left: error => Left<L, R>(error), // 如果参数是错误,直接返回错误
                Right: arg => Right<L, R>(func(arg)) // 正常应用函数
            )
        );
    }

    // 便捷重载1:支持直接传入普通值,自动包装为Right<L, T>,处理二元函数的第一个参数
    public static Either<L, Func<T2, R>> Apply<T1, T2, R, L>(this Either<L, Func<T1, T2, R>> funcEither, T1 arg)
    {
        return funcEither.Match(
            Left: error => Left<L, Func<T2, R>>(error),
            Right: func => Right<L, Func<T2, R>>(y => func(arg, y)) // 将二元函数柯里化为一元函数
        );
    }

    // 便捷重载2:对柯里化后的一元函数应用普通值
    public static Either<L, R> Apply<T2, R, L>(this Either<L, Func<T2, R>> funcEither, T2 arg)
    {
        return funcEither.Apply(Right<L, T2>(arg));
    }
}

修正你的测试代码

现在我们可以用正确的方式写Either的测试了,注意把函数放在Right里,同时指定Left的错误类型(比如Exception或者string):

using System;
using Xunit;
using LaYumba.Functional;
using static LaYumba.Functional.F;

namespace ch8
{
    public class UnitTest1
    {
        [Fact]
        public void Ex1()
        {
            Func<int, int, int> add = (x, y) => x + y;
            
            // Option的代码没问题,保留
            var option = Some(add)
                .Apply(3)
                .Apply(2);
            Assert.Equal(Some(5), option);

            // Either的正确写法:函数放在Right中,指定错误类型为Exception
            var either = Right<Exception, Func<int, int, int>>(add)
                .Apply(3)
                .Apply(2);
            Assert.Equal(Right<Exception, int>(5), either);

            // 测试Left的情况:如果初始是错误,Apply后依然是错误
            var errorEither = Left<string, Func<int, int, int>>("计算出错")
                .Apply(3)
                .Apply(2);
            Assert.Equal(Left<string, int>("计算出错"), errorEither);
        }
    }
}

关键知识点

  • Either的核心语义:Either<L, R>中,Left<L>代表失败/错误,Right<R>代表成功/正常值。函数作为正常逻辑的一部分,必须放在Right里。
  • Apply的作用:Monad的Apply方法是用来将"包裹在Monad中的函数"应用到"包裹在Monad中的值"上,最终返回包裹了结果的Monad。对于Either来说,只要任何一步是Left,整个链式调用就会保留Left的值。
  • 柯里化的必要性:因为Apply是针对一元函数设计的,所以多元函数需要先柯里化(比如Func<int,int,int>变成Func<int, Func<int,int>>),我们的扩展方法已经帮你处理了这个转换。

内容的提问来源于stack exchange,提问作者Mike Harris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:21:44