c# - 为什么无论发生什么情况,我的 Either-return 方法总是返回 Left?

标签 c# functional-programming language-ext

此处的用例是,我向 Entity Framework DbContext 添加了一个方法,该方法在保存之前执行一些额外的工作,然后根据结果返回 Either 。一个非常简化的版本看起来像这样......

static async Task<Either<string, Unit>> SaveChangesAsyncEither(string userId) {
  // In reality, this would do some auditing, check for concurrency issues, and
  // attempt to save the changes. It would return unit if all went OK, 
  // or an error message if not. Simple check for demonstrating the issue...
  if (userId == "jim") {
    return "We don't like Jim";
  }
  return unit;
}

这个方法在我的解决方案中的很多地方都使用了,而且效果很好。

在一个最小的 API 项目中,我有一种如下所示的方法(再次高度简化)...

static async Task<Either<DeviceApiResponseStates, DeviceApiResponseStates>> CreateDevice(string userId) {
  // In reality we would have more parameters, create a device, etc before
  // calling SaveChangesAsyncEither
  return (await SaveChangesAsyncEither(userId))
      .Match(() => Right<DeviceApiResponseStates, DeviceApiResponseStates>(DeviceApiResponseStates.OK),
        _ => Left(DeviceApiResponseStates.Nah));
}

...其中 DeviceApiResponseStates 是一个 enum。为简单起见,您可以想象它看起来像这样......

enum DeviceApiResponseStates {
  OK,
  Nah
}

这三个代码块是我的问题的完整示例。

我希望如果我调用 CreateDevice("jim"),那么我会得到一个值为 NahLeft,如果我用任何其他字符串值调用它,我会得到一个值为 OK 的 Right

但是,当我尝试这个时...

Console.WriteLine((await CreateDevice(""))
  .Match(_ => "Yeah", ex => $"Exception: {ex}"));

...无论字符串值如何,我都会得到“Nah”。

谁能解释一下我做错了什么?

最佳答案

TL;DR:三键编辑:

static async Task<Either<DeviceApiResponseStates, DeviceApiResponseStates>> CreateDevice(string userId)
{
    // In reality we would have more parameters, create a device, etc before
    // calling SaveChangesAsyncEither
    return (await SaveChangesAsyncEither(userId))
        .Match(_ => Right<DeviceApiResponseStates, DeviceApiResponseStates>(DeviceApiResponseStates.OK),
            _ => Left(DeviceApiResponseStates.Nah));
}

请注意,我已将第一个 Match 参数中的输入参数从 () 更改为 _

...但是为什么?!

确实,这花了我一些时间才弄清楚。

预先说明以下内容可能很重要,因为我不知道您是否熟悉其他函数式语言 - 特别是 F#,还有 Haskell:

在 C# 中,lambda 表达式 () => x 的类型为 Func<T> ,而不是 Func<Unit, T> 。 (F# 更优雅,因为 fun () -> x 确实 指示函数 unit -> 'a ,因为在 F# 中, () 的类型为 unit 。在 C# 中则不然,其中 lambda 表达式中的输入“无数据”具有特殊的含义语法 () => ,并且输出具有特殊关键字 void 。因此,为了说明这一点,C# lambda 表达式 x => {} 具有类型 Action<T> ,而在 F# fun x -> () 中将具有类型 'a -> unit

所有这些意味着,当您编写 .Match(() => ... 时,C# 编译器会将该 lambda 表达式解释为 Func<Either<DeviceApiResponseStates, DeviceApiResponseStates>> ,即一个不接受输入的函数。 C# 重载解析器会查找采用 Func<T>(不是 Func<Unit, T>))作为输入的方法或扩展方法,并找到以下方法:

public static B Match<A, B>(this IEnumerable<A> list, Func<B> Empty, Func<Seq<A>, B> More)
{
    return Prelude.toSeq(list).Match(Empty, More);
}

在 LanguageExt 的 ListExtensions 中。请注意 Func<B> Empty 参数。

这是 IEnumerable<T> 而不是 Either<L, R> 上的扩展方法。为什么会编译?

嗯,它可以编译,因为 Either<L, R> 实现了 IEnumerable<T> :

public readonly struct Either<L, R> : IEnumerable<EitherData<L, R>>, IEnumerable, ...

通过使用 _ 而不是 () ,您可以告诉编译器您希望某个值作为 lambda 表达式的输入,而不是不输入。碰巧您期望的输入是 Unit 值。

现在排除了 IEnumerable<T> 扩展方法,而是使编译器能够在 Match 上找到正确的 Either<L, R> 方法。

关于c# - 为什么无论发生什么情况,我的 Either-return 方法总是返回 Left?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74591448/

相关文章:

c# - 如何在后面的代码中读取 WPF 发布版本号

algorithm - Haskell 中的 N 皇后没有列表遍历

C# 数组合并不重复

c# - WP SQLite 查询执行器

bash - 仅打印其参数的函数是纯函数吗?

haskell - 计算列表中满足给定谓词的元素数量

C# LanguageExt - 将多个异步调用组合成一个分组调用

c# - 如何使用 Language-Ext 将 Task 转换为 Task<Unit>

c# - 在 Outlook 中获取电子邮件的链接/路径?