第七章 函子与单子
函子(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)
代码解释 同态律 Result<int , string > Pure (int x ) => Result<int , string >.Success(x);Func<int , int > f = x => x + 10 ; var left = Pure(5 ).Map(f).Dump();var right = Pure(f(5 )).Dump();(left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
交换律
Result<int , string > Pure (int x ) => Result<int , string >.Success(x);Result<Func<int , int >, string > PureF(Func<int , int > func) => Result<Func<int , int >, string >.Success(func); Func<int , int > f = x => x * 2 ; var left = Pure(7 ).Apply(PureF(f)).Dump();var right = Pure(f(7 )).Dump();(left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
结合律
Result<int , string > Pure (int x ) => Result<int , string >.Success(x);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 ; Func<int , int > composed = x => g(f(x)); var left = Pure(4 ) .Apply(PureF(composed)).Dump(); var right = Pure(4 ) .Apply(PureF(f)) .Apply(PureF(g)).Dump(); (left.IsSuccess && right.IsSuccess && left.Value == right.Value).Dump();
代码示例 恒等律 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 } }; var sequentialApplicationResult = books .Select(book => AddPages(book, 50 )) .Select(book => AppendSubtitle(book, "Updated Edition" )); 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)));
拓展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" );var successAfterIdentity = success.Map(identity);var errorAfterIdentity = error.Map(identity);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);var stepwiseComposition = success .Map(book => AddPages(book, 50 )) .Map(book => AppendSubtitle(book, "Updated Edition" ));
单子 (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);
Right Identity(右相等) 包装一个值 = Bind里包装一个值
var manuscriptResult = Result<Manuscript, string >.Success(new Manuscript());var rightIdentity = manuscriptResult.Bind(manuscript => Result<Manuscript, string >.Success(manuscript));
结合律 var associativity1 = FetchManuscript(manuscriptId) .Bind(EditManuscript) .Bind(FormatManuscript); var associativity2 = FetchManuscript(manuscriptId) .Bind(manuscript => EditManuscript(manuscript) .Bind(FormatManuscript) );
Exercises