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

// 查找指定 Id 的用户是否存在
Result<int, string> GetUserById(int id)
{
    if(id % 7 == 0)
    {
        // 正常的用户
        return Result<int,string>.OK(id);
    }
    if(id % 2 == 0)
    {
        return Result<int, string>.Error("用户已被冻结");
    }
    return Result<int, string>.Error("用户不存在");
}

// 查找指���用户的余额
Money GetMoneyFromUser(int id)
{
    if (id >= 35)
    {
        return Money.OK(id * 10);
    }
    return Money.Error("穷逼用户不参与这次活动");
}

// 给用户转账
Money Transfer(double money, double amount)
{
    return  from canTransfer in CheckForTransfer(money, amount)
            select canTransfer ? money + amount : money;
}

// 检查用户是否满足转账条件,如果转账后的余额超过了 600 元,则终止转账
Result<bool, string> CheckForTransfer(double a, double b)
{
    if (a + b >= 600) {
        return Result<bool,string>.Error("超出余额限制");
    }
    return Result<bool,string>.OK(true);
}

Money SendGift(int userId)
{
    return  // 查询用户信息
            from user in GetUserById(userId)
            // 获取该用户的余额
            from money in GetMoneyFromUser(user)
            // 给这个用户转账
            from transfer in Transfer(money, money * 0.1)
            // 获取结果
            select transfer;
}

SendGift(42)
// Success: 462

SendGift(56)
// Error: 超出余额限制

SendGift(1)
// Error: 用户不存在

SendGift(14)
// Error: 穷逼用户不参与这次活动

SendGift(16)
// Error: 用户已被冻结

可以看到,使用 Result 能够让我们更加清晰地用代码描述业务逻辑,而且如果我们需要向现有流程中添加新的验证逻辑,只需要在合适地地方插入 from result in validate(xxx) 就可以了,换句话说,我们的代码变得更加“声明式”了。

函数式编程

细心的你可能已经发现了,不管是 LINQ to Task 还是 LINQ to Result,我们都使用了某种特殊的类型(如:Task,Result)对值进行了包装,然后编写了特定的拓展方法 —— SelectMany,为这种类型定义了一个重要的基本操作。在函数式编程的里面,我们把这种特殊的类型统称为“Monad”,所谓“Monad”,不过是自函子范畴上的半幺群而已。

范畴(Category)与函子(Functor)

在高中数学,我们学习了一个概念——集合,这是范畴的一种。

对于我们程序员来说,int 类型的全部实例构成了一个集合(范畴),如果我们为其定义了一些函数,而且它们之间的复合运算满足结合律的话,我们就可以把这种函数叫做 int 类型范畴上的“态射”,态射讲的是范畴内部元素间的映射关系,例如:

// f(x) = x * 2
Func<int, int> f = (int x) => x * 2;
// g(x) = x + 1
Func<int, int> g = (int x) => x + 1;
// h(x) = x + 10
Func<int, int> h = (int x) => x + 10;

// 将函数 g 与 f 复合,(g ∘ f)(x) = g(f(x))
Func<X, Z> Compose<X, Y, Z>(Func<Y, Z> g, Func<X, Y> f) => (X x) => g(f(x));

Compose(h, Compose(g, f))(42) == Compose(Compose(h, g), f)(42)
// true

f,g,h 都是 int 类型范畴上的态射,因为函数的复合运算是满足结合律的。

我们还可以定义一种范畴间进行元素映射的函数,例如:

Func<int, double> ToDouble = x => Convert.ToDouble(x);

这里的函数 Select 实现了 int 范畴到 double 范畴的一个映射,不过光映射元素是不够的,要是有一种方法能够帮我们把 int 中的态射(f,g,h),映射到 double 范畴中,那该多好。那么下面的函数 F 就帮助我们实现了这了功能。

// 为了方便使用 Compose 进行演示,故定义了一个比较函数式的 ToInt 函数
Func<double, int> ToInt = x => Convert.ToInt32(x);
// 一个将 int -> int 转换为 double -> double 的函数
Func<double, double> F(Func<int, int> selector) => x => Compose(Compose(ToDouble, selector), ToInt)(x);

// 在范畴间映射 f
var Ff = F(f);
Ff(42.0);
// 84.00

// 在范畴间映射 g
var Fg = F(g);
Fg(42.0);
// 43.00

// 在范畴间映射 h
var Fh = F(h);
Fh(42.0);
// 52.00

// Ff, Fg, Fh 之间仍然保持结合律,因为他们是 `double` 范畴上的态射
Compose(Fh, Compose(Fg, Ff))(42) == Compose(Compose(Fh, Fg), Ff)(42)

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

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