求助:在C#中实现Either Monad的Apply方法遇类型检查问题
如何为C#中的Either Monad实现Apply方法
我来帮你搞定这个Either的Apply方法问题!首先,咱们先拆解一下你遇到的问题,然后一步步实现正确的Apply逻辑,再修正你的测试代码。
问题分析
你代码里的Left(add).Apply(3)通不过类型检查,核心原因有两个:
- Either的语义理解偏差:
Left是用来承载错误/失败类型的,而函数作为"正常逻辑的值"应该放在Right里。你用Left(add)相当于把函数标记成了错误值,这完全不符合Either的设计意图。 - 缺少对应的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
相关产品推荐
相关产品推荐

