// 查找指定 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)