第七章 函子与单子

  • 函子(Functor) :对容器中的(Map)
  • 应用函子(Applicative Functor) :将包裹在容器中的函数(Apply)包裹在容器中的值
  • 单子(Monad)链式操作

函子

一个函数,输入输出类型相同的函数

函子需要遵从的定律

  • 恒等律
    相同输出->相同输出
  • 结合律
    组合两个函子map box = 两个函子分别map box

函子的好处

  • 错误处理
  • 链式操作
  • 好看
  • 鲁棒

应用函子(Applicative Functors)

四大定律

  • 同态律(Homomorphism law)
  • 交换律(Interchange law)
  • 组合律

同态律

$$
pure(x).map(f)=pure(f(x))
$$

交换律

$$
pure(x).apply(pure(f))=pure(f(x))
$$

结合律

$$
pure(x).apply(pure(g∘f))=pure(x).apply(pure(f)).apply(pure(g))
$$

pure: 把一个值装进盒子中

apply:把一个函数装进盒子中

(g∘f): g(f)

代码解释

同态律

// 定义 pure 函数
Result<int, string> Pure(int x) => Result<int, string>.Success(x);

// 定义普通函数
Func<int, int> f = x => x + 10;

// 左边:pure(x).map(f)
var left = Pure(5).Map(f).Dump();

// 右边:pure(f(x))
var right = Pure(f(5)).Dump();

// 验证
(left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
// 输出:True

交换律

// pure(x)
Result<int, string> Pure(int x) => Result<int, string>.Success(x);
// pure(f)
Result<Func<int, int>, string> PureF(Func<int, int> func) => Result<Func<int, int>, string>.Success(func);

// 定义函数
Func<int, int> f = x => x * 2;

// 左边:pure(x).apply(pure(f))
var left = Pure(7).Apply(PureF(f)).Dump();

// 右边:pure(f(x))
var right = Pure(f(7)).Dump();

// 验证
(left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
// 输出:True

结合律

// pure(x)
Result<int, string> Pure(int x) => Result<int, string>.Success(x);
// pure(f)
Result<Func<int, int>, string> PureF(Func<int, int> func) => Result<Func<int, int>, string>.Success(func);

// 定义两个函数
Func<int, int> f = x => x + 2;
Func<int, int> g = x => x * 3;

// 函数组合:g ∘ f
Func<int, int> composed = x => g(f(x));

// 左边:pure(x).apply(pure(g∘f))
var left = Pure(4)
.Apply(PureF(composed)).Dump();

// 右边:pure(x).apply(pure(f)).apply(pure(g))
var right = Pure(4)
.Apply(PureF(f))
.Apply(PureF(g)).Dump();

// 验证
(left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
// 输出:True

代码示例

恒等律

Book AddPages(Book book, int pages) => new Book { Title = book.Title, Pages = book.Pages + pages };
Book AppendSubtitle(Book book, string subtitle) => new Book { Title = $"{book.Title}: {subtitle}", Pages = book.Pages };

List<Book> books = new()
{
new Book { Title = "C# Basics", Pages = 100 },
new Book { Title = "Advanced C#", Pages = 200 }
};

// 先对每本书应用 AddPages,然后应用 AppendSubtitle
var sequentialApplicationResult = books
.Select(book => AddPages(book, 50))
.Select(book => AppendSubtitle(book, "Updated Edition"));

// 将 AddPages 和 AppendSubtitle 组合应用于每本书
var combinedApplicationResult = books
.Select(book => AppendSubtitle(AddPages(book, 50), "Updated Edition"));

// 打印结果
Console.WriteLine("books.Select(AddPages).Select(AppendSubtitle): " +
string.Join(", ", sequentialApplicationResult.Select(b => b.Title)));

Console.WriteLine("books.Select(book => AppendSubtitle(AddPages(book, 50))): " +
string.Join(", ", combinedApplicationResult.Select(b => b.Title)));

// 输出:
// books.Select(AddPages).Select(AppendSubtitle): C# Basics: Updated Edition, Advanced C#: Updated Edition
// books.Select(book => AppendSubtitle(AddPages(book, 50))): C# Basics: Updated Edition, Advanced C#: Updated Edition

拓展Result类型为函子

public class Result<TValue, TError>
{
private TValue _value;
private TError _error;
public bool IsSuccess { get; private set; }

private Result(TValue value, TError error, bool isSuccess)
{
_value = value;
_error = error;
IsSuccess = isSuccess;
}

public TValue Value
{
get
{
if (!IsSuccess)
throw new InvalidOperationException("无法从失败结果中获取值。");
return _value;
}
}

public TError Error
{
get
{
if (IsSuccess)
throw new InvalidOperationException("无法从成功结果中获取错误。");
return _error;
}
}

public static Result<TValue, TError> Success(TValue value) =>
new Result<TValue, TError>(value, default, true);

public static Result<TValue, TError> Failure(TError error) =>
new Result<TValue, TError>(default, error, false);

public Result<TResult, TError> Map<TResult>(Func<TValue, TResult> mapper)
{
return IsSuccess
? Result<TResult, TError>.Success(mapper(_value!))
: Result<TResult, TError>.Failure(_error!);
}
public Result<TResult, TError> Apply<TResult>(Result<Func<TValue, TResult>, TError> resultFunc)
{
if (resultFunc.IsSuccess && this.IsSuccess)
{
var func = resultFunc.Value; // 取出函数
var value = this.Value; // 取出当前值
var result = func(value); // 应用函数到值
return Result<TResult, TError>.Success(result);
}
else
{
// 有一边失败,返回失败(优先返回函数容器的错误,否则返回自己的错误)
var error = resultFunc.IsSuccess ? this._error : resultFunc.Error;
return Result<TResult, TError>.Failure(error);
}
}

}

使用示例

Book AddPages(Book book, int pages) => new Book { Title = book.Title, Pages = book.Pages + pages };
Book AppendSubtitle(Book book, string subtitle) => new Book { Title = $"{book.Title}: {subtitle}", Pages = book.Pages };
Func<Book, Book> identity = book => book;

var success = Result<Book, string>.Success(new Book { Title = "C# Basics", Pages = 100 });
var error = Result<Book, string>.Failure("Error message");

// 恒等律(Identity law)
var successAfterIdentity = success.Map(identity);
// successAfterIdentity 应该持有 "C# Basics",100 页
var errorAfterIdentity = error.Map(identity);
// errorAfterIdentity 应该保持 "Error message" 错误

// 组合律(Composition law)
Func<Book, Book> composedFunction = book =>
AppendSubtitle(AddPages(book, 50), "Updated Edition");

var success = Result<Book, string>.Success(new Book { Title = "C# Basics", Pages = 100 });

// 直接应用组合函数
var directComposition = success.Map(composedFunction);
// directComposition 应该持有值 "C# Basics: Updated Edition",150 页

// 逐步应用函数
var stepwiseComposition = success
.Map(book => AddPages(book, 50))
.Map(book => AppendSubtitle(book, "Updated Edition"));
// stepwiseComposition 也应该持有值 "C# Basics: Updated Edition",150 页

单子 (Monads)

单子核心 - Bind

Bind: 返回一个扁平的结果,防止Result<Result<…>>

public Result<TResult, TError> Bind<TResult>
(Func<TValue, Result<TResult, TError>> func)
{
return IsSuccess ? func(_value!)
: Result<TResult, TError>.Failure(_error!);
}

使用示例:

Result<Manuscript, string> FetchManuscript(int manuscriptId) { ... }
Result<EditedManuscript, string> EditManuscript(Manuscript manuscript) { ... }
Result<FormattedManuscript, string> FormatManuscript(EditedManuscript edited) { ... }

var manuscriptId = 101;
var publishingPipeline = FetchManuscript(manuscriptId)
.Bind(EditManuscript)
.Bind(FormatManuscript);

单子定律

Left Identity (左相等)

包装一个函数Bind = 直接应用函数

Func<int, Result<double, string>> calculateRoyalties = 
sales => new Result<double, string>(sales * 0.15);

int bookSales = 100;
var leftIdentity = Result<int, string>.Success(bookSales).Bind(calculateRoyalties);
var directApplication = calculateRoyalties(bookSales);
// leftIdentity 应该等于 directApplication

Right Identity(右相等)

包装一个值 = Bind里包装一个值

var manuscriptResult = Result<Manuscript, string>.Success(new Manuscript());
var rightIdentity = manuscriptResult.Bind(manuscript => Result<Manuscript, string>.Success(manuscript));
// rightIdentity 应该等于 manuscriptResult

结合律

var associativity1 = FetchManuscript(manuscriptId)
.Bind(EditManuscript)
.Bind(FormatManuscript);

var associativity2 = FetchManuscript(manuscriptId)
.Bind(manuscript =>
EditManuscript(manuscript)
.Bind(FormatManuscript)
);
// associativity1 应该等于 associativity2

Exercises