C# 函数式编程:LINQ(2)

结果比 LINQ 还多调用了两次 Select。仔细看的话,就会发现,我们所写的第二个 Select 其实就是 SelectMany,的第二个参数,而对于第一个 Select 来说,因为 b 是一个 Task,所以 b.Select(xxx) 的返回值肯定是一个 Task,而这又恰好符合 SelectMany 函数的第一个参数的特征。

有了上面的经验,我们不难推断出,当 from x in y 语句的个数超过 2 个的时候,LINQ 仍然会只使用 SelectMany 来进行翻译。因为 SelectMany 可以被看作为把两层 Task 转换成单层 Task,例如:

var taskA = Task.FromResult(12);
var taskB = Task.FromResult(12);
var taskC = Task.FromResult(12);
PrintExpr((int _) =>
    from a in taskA
    from b in taskB
    from c in taskC
    select a * b + c
    );

// 我的推断:
var r = taskA.SelectMany(a => taskB, (a, b) => new {a, b}).SelectMany(temp => taskC, (temp, c) => temp.a * temp.b + c);

// 实际的输出:
// _ => taskA.SelectMany(a => taskB, (a, b) => new <>f__AnonymousType0#1`2(a = a, b = b)).SelectMany(<>h__TransparentIdentifier0 => taskC, (<>h__TransparentIdentifier0, c) => ((<>h__TransparentIdentifier0.a * <>h__TransparentIdentifier0.b) + c))

这里 LINQ 为第一个 SelectMany 的结果生成了一个匿名的中间类型,将 taskA 跟 taskB 的结果组合成了 Task<{a, b}>,方便在第二个 SelectMany 中使用。

至此,一个非常简单的 LINQ to Task 就完成了,通过这个小工具,我们可以实现不使用 async/await 就对类型进行操作。然而这并没有什么卵用,因为 async/await 确实要比 from x in y 这种语法要来的更加简单。不过举一反三,我们可以根据上面的经验来实现一个更加使用的小功能。

LINQ to Result

在一些比较函数式的语言(如 F#,Rust)中,会使用一种叫做 Result<TValue, TError> 的类型来进行异常处理。这个类型通常用来描述一个操作结果以及错误信息,帮助我们远离 Exception 的同时,还能保证我们全面的处理可能出现的错误。如果使用 C# 实现的话,一个 Result 类型可以被这么来定义:

class Result<TValue, TError>
{
    public TValue Value {get; private set;}
    public TError ErrorMsg {get; private set;}
    public bool IsSuccess {get; private set;}
    public override string ToString()
    {
        if(this.IsSuccess)
            return "Success: " + Value.ToString();
        return "Error: " + ErrorMsg.ToString();
    }

public static Result<TValue, TError> OK(TValue value)
    {
        return new Result<TValue, TError> {Value = value, ErrorMsg = default(TError), IsSuccess = true};
    }

public static Result<TValue, TError> Error(TError error)
    {
        return new Result<TValue, TError> {Value = default(TValue), ErrorMsg = error, IsSuccess = false};
    }
}

接着仿照上面为 Task 定义 LINQ 拓展方法,为了 Result 设计 Select 跟 SelectMany:

static Result<TR, TE> Select<TV,TR, TE>(this Result<TV, TE> result, Func<TV, TR> selector) =>
    result.IsSuccess
    ? Result<TR, TE>.OK(selector(result.Value))
    : Result<TR, TE>.Error(result.ErrorMsg);

static Result<TR, TE> SelectMany<TV, TS, TR, TE>(this Result<TV, TE> result, Func<TV, Result<TS, TE>> selector, Func<TV, TS, TR> projector){
    if (result.IsSuccess)
    {
        var tempResult = selector(result.Value);
        if (tempResult.IsSuccess)
        {
            return Result<TR, TE>.OK(projector(tempResult.Value, tempResult.Value));
        }
        return Result<TR, TE>.Error(tempResult.ErrorMsg);
    }
    return Result<TR, TE>.Error(result.ErrorMsg);
}

那么 LINQ to Result 在实际中的应用是什么样子的呢,接下来我用一个小例子来说明:
某公司为感谢广大新老用户对 “5 元 30 M”流量包的支持,准备给余额在 350 元用户的以上的用户送 10% 话费。但是呢,如果用户在收到赠送的话费后余额会超出 600 元,就不送话费了。

using Money = Result<double, string>;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f5cc450b18cf816a73d43c0544f93dd5.html