zoukankan      html  css  js  c++  java
  • 拿 C# 搞函数式编程

    最近闲下来了,准备出一个 C# 搞 FP 的合集。本合集所有代码均以 C# 8 为示例。

    可能你说,为什么要这么做呢?回答:为了好玩。另外,意义党们请 gun cu ke!

     

    C# 有委托,而且有 Func<> 和 Action<>,可以说函数被视为一等功名,跟 int、bool 等类型并没有什么区别。那么很多事情就简单了。

    纯函数

    什么是纯函数呢?纯函数就是 f(x),它们接收参数,得到结果,并且相同的参数得到的结果一定是相同的,用映射来说,它是满射的。另外这个函数不会改变任何的状态值,它是无副作用的。

    柯里化

    首先,有一个东西让我觉得不爽,那就是一般来说 C# 里的函数调用不是柯里化的,这也就意味着我没法一个一个传参数进去,也没法把传了一部分参数的调用作为一个新函数拿去给别的地方用,那要怎么办呢?

    自己动手,丰衣足食!

    一个标准的加法函数可以这么写:

    var function = new Func<int, int, int>
        ((x, y) => x + y);
    function(1, 2); // returns 3

    如果我们想以柯里化形式调用的话,理想状态是这么个样子的:

    function 1 2

    但是这个括号我们是省不了的,所以这样也是可以接受的:

    function(1)(2);

    我们看一下这个调用形式,不就是 Func<int, Func<int, int>> 嘛!so easy~

    我们只需要把 Func<int, int, int> 转化为 Func<int, Func<int, int>>:

    Func<int, Func<int, int>> Currying(Func<int, int, int> f) 
        => x => y => f(x, y);

    这样写就 ok 啦。进一步改造成扩展方法:

    public static class CurryingExtensions
    {
        public static Func<int, Func<int, int>> 
            Currying(this Func<int, int, int> f) 
                => x => y => f(x, y);
    }

    于是我们只需要:

    var function = new Func<int, int, int>
        ((x, y) => x + y)
        .Currying();
    function(1)(2); // returns 3

    就可以采用柯里化形式调用该函数啦。

    进一步我们用泛型改造,让柯里化适用于任何类型:

    public static class CurryingExtensions
    {
        public static Func<T1, Func<T2, TOutput>> 
            Currying<T1, T2, TOutput>(this Func<T1, T2, TOuput> f)
                => x => y => f(x, y);
    }

    如果遇到更多参数,我们只需要给这个静态类里面再加一个扩展方法即可。

    那 Action<> 呢?这个东西在我看来完全就是副作用,具体下方有讲,我们不用他(逃

    Unit

    什么是 Unit 呢?Unit 就是任何函数调用后如果没有结果,就会返回的一个东西。

    可能你说,void 不就可以了?

    但是如果一个纯函数,它没有返回值(即 Action<>),意味着这个函数它有输入没输出,那这个函数除了能用来产生副作用之外,就什么都干不了了。这不清真!

    因此我们需要一个 Unit 来代替 void,偷个懒,这个 Unit 就用 ulong 来代替吧。

    高阶函数

    什么叫做高阶函数,把函数当作参数传给另一个函数,接收这个函数参数的函数就叫做高阶函数。

    举个例子:f(g(x)),f 即高阶函数。

    假设我们现在要开一个超市,超市有很多的产品,每种产品价格不同,不同产品可能还有各自的折扣。我们有很多种快乐水,每种快乐水价格不一样,可口快乐水 3.5 块,百事快乐水 3 块,麦当劳快乐水 9 块,快乐水价格计算函数:

    var happyWater = new Func<float, int, float>
        ((float price, int number) => number * price)
        .Currying();
    // 调用:happyWater(快乐水单价)(快乐水件数);
    
    var cocaHappyWater = happyWater(3.5f);
    var pepsiHappyWater = happyWater(3);
    var mcdHappyWater = happyWater(9);

    超市可能有折扣,A 超市不打折,B 超市打八折,计算价格函数:

    var calcPrice = new Func<Func<int, float>, float, int, float>
        ((calc, discount, number) => discount * calc(number))
        .Currying();
    // 调用:calcPrice(快乐水价格计算函数)(超市折扣)(快乐水件数);

    现在我们分别在 A 超市买百事快乐水、B 超市买可口快乐水,麦当劳的太贵了我们不买,价格计算函数为:

    var pepsiPriceCalc = calcPrice(pepsiHappyWater);
    var cocaPriceCalc = calcPrice(cocaHappyWater);
    
    var priceCalcA = pepsiPriceCalc(1); // A 超市
    var priceCalcB = cocaPriceCalc(0.8f); // B 超市

    最后我们在 A 超市买了 3 瓶百事快乐水,B 超市买了 5 瓶可口快乐水,计算总价:

    var priceA = priceCalcA(3);
    var priceB = priceCalcB(5);
    var total = priceA + priceB;

    最后得到 total = 23 元。

    可以看到这些函数都是可拆卸并且可以随意组合的,而且满足 f(g(x)) = g(f(x))。

    贴上完整代码示例:

    using System;
    
    namespace ColaMarket
    {
        static class CurryingExtensions
        {
            public static Func<T1, Func<T2, TOutput>>
                Currying<T1, T2, TOutput>(this Func<T1, T2, TOutput> f)
                    => x => y => f(x, y);
    
            public static Func<T1, Func<T2, Func<T3, TOutput>>>
                Currying<T1, T2, T3, TOutput>(this Func<T1, T2, T3, TOutput> f)
                    => x => y => z => f(x, y, z);
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                var happyWater = new Func<float, int, float>
                    ((float price, int number) => number * price)
                    .Currying();
    
                var cocaHappyWater = happyWater(3.5f);
                var pepsiHappyWater = happyWater(3);
                var mcdHappyWater = happyWater(9);
    
                var calcPrice = new Func<Func<int, float>, float, int, float>
                    ((calc, discount, number) => discount * calc(number))
                    .Currying();
    
                var pepsiPriceCalc = calcPrice(pepsiHappyWater);
                var cocaPriceCalc = calcPrice(cocaHappyWater);
    
                var priceCalcA = pepsiPriceCalc(1);
                var priceCalcB = cocaPriceCalc(0.8f);
    
                var priceA = priceCalcA(3);
                var priceB = priceCalcB(5);
                var total = priceA + priceB;
    
                Console.WriteLine(total);
            }
        }
    }

    下一篇将会讲更多的东西,如 Functor、Applicative 和 Monad 等等。

  • 相关阅读:
    从点滴看管理之新生代员工培养方式的思考
    手机应用PC端演示工具介绍
    Python Day 66 Django框架、Auth认证模块、Auth模块常用方法、扩展默认的auth_user表、补充orm 模型类中releatename属性和 自关联
    Python Day 65 Django框架、Django生命周期、Django中间件、中间件执行流程、Django中MTV模式 和 MVC模式
    Python Day 64 Django框架、cookie和session、Django中Session相关方法、Django中支持Session5种存储介质
    Python Day 63 Django框架、Django模板系统(渲染页面的作用)
    Python Day 62 Django框架、Django框架中分页 、 网页攻击
    Python Day 61 Django框架、Django框架ORM一对一表操作、Django列类型(重点)、自定义列类型、Django-amdin自带管理后台
    Python Day 60 Django框架、ORM高级查询、级联删除、增加多条数据、Django中Xss攻击、事务
    Python Day 59 Django框架、Django中ORM多对多表操作(联合唯一索引)
  • 原文地址:https://www.cnblogs.com/hez2010/p/11487006.html
Copyright © 2011-2022 走看看