zoukankan      html  css  js  c++  java
  • 初识函数式编程的一点漫谈

    昨天看一些函数式编程、Haskell、Scala的书和文章,到半夜还莫名其妙:

    「好像除了函数是一等公民外,没发现什么好处呀……很多人说学函数式能改变思维方式,又不肯说这改变具体是什么。」

    「javascript里函数也是一等公民,用着也很爽,函数式比之,还有什么好处?」

    很不满意地躺下,用手机继续google之,突然发现一篇文章,醍醐灌顶,立刻爬起来仔细阅读,摸黑记录(兴奋得没工夫开灯),发微博致谢。

    「函数式编程的关键是不定义变量」,这至为重要的一点,之前看的书和文章竟然都没强调——说是说过了,却没指出这是重点。

    就连忙现想个功能,尝试不定义变量地实现它,发现当有了这个勒定,函数语言自然会被「倒逼」着具备lambda等特性,因为我发现只用老java,不用java8语法,很难在「不定义变量」的约束下实现之,不知道是不是不可能实现,我的数学能力不足以证明之。

    今天早上起来,赶快翻开《写给大忙人看的java8》,使用java8语法完成程序。完成后重构一下,发现果然不出所料,最终代码的结构,是函数复函数,函数套函数,主函数是超级大函数,而这些函数的分隔和关联,着眼点就在其参数和返回值上。

    数学里,函数的意义是定义域到值域的映射,代码里写的函数,其意义是参数集合到返回值集合的映射,为了达成这从集合到集合的映射,在不许定义变量的情况下,只有借助java8的lambda和stream才能实现。

    每个函数都是从一个集合,变换到另一个集合,这样经过若干步变换,就实现了从程序的总参数,到程序的总结果的变换。

    程序的意义本来就是从输入到输出的变换,输入是用户操作、网络请求、定时器等事件引起的中断,及存储器某个地方现有的数据,输出则是将一些数据写到存储器中,包括写磁盘、写显存以让屏幕显示某些图画、写数据库、发网络请求调远处接口等。可以把程序的「总输入参数」看做一个集合,「总输出结果」看做一个集合,从参数集合到结果集合的映射,就是一个「大函数」,这与函数式编程的「主函数是超级大函数」是一致的。

    其实「网站」的「主函数」又何尝不是「超级大函数」呢,本质就是socket服务端的主循环,通过很复杂的策略,把「总输入」包成request对象,把「总输出」准备用response对象处置,然后路由到具体的程序上,那程序可能进一步「分治」,用框架把request的参数绑到具体处理方法的入参上,把该方法的出参用框架给到response返回,然后通过路径匹配,把各个请求给到适当的处理方法上。只是,很多时候,我们在太高层、太狭窄的上下文中工作得太久,以至于忘了自己身处一个「超级大函数」之中。

    视线更大一点,计算机又何尝不是「超级大函数」呢,一通电,CPU即不停运转,这就是最大的函数,「总输入」是电能,「总输出」是电路使硬件发生的变化;在这之上一层,操作系统的主循环是次一级的「超级大函数」,「总输入」是随时监听到的硬件中断;再之上一层,才是某个进程的主循环这「大函数」,「总输入」是操作系统分配来的事件,上段说的网站服务器的socket主循环就是这种「大函数」;在这函数调用之下,Filter和Servlet这些组件的生命周期才得以运转,然后进一步才是Spring等框架的初始化,Struts或Spring等框架表现层组件的初始化和响应事件方法执行,再下一步,才是业务层,持久层,读写磁盘,读写数据库,读写API这些具体的「小函数」,小函数的输出,再经过层层包裹,成为「超级大函数」的输出的一部分,即对某处的硬件产生效果。

    视线再大一点,就是宇宙大爆炸是「究极大函数」,那个瞬间一切原子的位置,是否已决定了之后的一切,这已是哲学的问题了。这个「洪荒之力」,过于究极,我们也只是这个大函数中极为渺小的一些数据啊,缘聚,就有了我,缘散,就没了我,像一滴水消失在水中,像计算机断电后,内存中的010101化为乌有,人生天地间,忽如远行客。

    说远了。函数想从输入变换到输出,处理方法是当然要有的,这个方法,命令式语言着眼于「参数怎样算出结果」的流程,而函数式语言着眼于「参数与结果的对应」的关系,一个着眼于过程,一个着眼于结果,一个在厚重的书本中寻找来龙去脉,一个用搜索引擎来直指目的讯息,一个运筹帷幄,一个应变随机,一个深厚,一个扁平,一个动,一个静,一个重步骤,一个重设置,若说没奇缘,java偏又升到8,若说有奇缘,为何阵营终分化。

    映射这件事,java程序员也早就熟悉啊,不论是web.xml,还是struts.xml,还是spring中bean的id到实体,还是mybatis中「函数」的id到语句,hibernate?它叫做O-R什么来着?服务器这「超级大函数」接收到的各路神仙,给分化瓦解,分而治之,你虽一路来,我却N路去,这是但凡程序员都知道的「分治」原则。顺序选择循环,顺序循环可以咬牙不要,不用选择还想达到目的的,没听说过,即使没听过「函数式」的程序员,也每天都在做把「大函数」的任务视情况分配到「小函数」来处理的工作。

    那「不定义变量」的意义是什么,它的意义是,函数是纯逻辑——当然,是大逻辑套小逻辑的复杂逻辑。它不需要「知识」,即存储在存储单元中的数据。就好比形式逻辑,A为真,B为真,A且B则一定为真,至于A和B是什么,不用想。「不定义变量」从根本上避免了来自知识的污染,对程序来说,参数即是全部知识,外部知识不需要,我也没兴趣向外写,我只以这点有限的参数知识为准,返回适当的结果知识。

    这个切割非常漂亮,能让程序员专注于对局部的考察,只关注当前函数这「茅庐」即可,外面「三分天下」的洪水滔天都不在意(毕竟它们渗不进来)。显然,这阅读代码、单元测试起来,都太方便了,有限知识的纯逻辑推演,正是数学和逻辑学「天然真理」在程序世界中的表现,天然真理是干净的,不需要被证明,不可能被证伪。

    但科学是「有待」的,商业程序也是「有待」的,商业程序里可能没有用全局变量装状态,但最大的状态都在用N个数据库服务器或缓存服务器的集群装着呢。数据就是知识,知识就是力量,阿尔法打败了李世石,在这真正的洪荒之力面前,纯逻辑的推演显得力不从心,互联网时代,记忆力和计算力都外包,计算力这方面函数式干净利落,但记忆力即「状态」的价值同样举足轻重啊。科学知识,早晚要被证伪,并非天理,但能指导生活;商业程序,其中含有状态,并不干净,但能服务人类。函数式的思维,应该作为一种武器,纳入我们的兵器谱,在适当的时候取出来,破军杀敌。而不是奉为至宝,顶礼膜拜,一言不合,出言不逊。这才是一个客观务实的态度。

    以上是初识函数式编程的一点漫谈,没有限于问题本身,「漫溢」了,不过作为散文,而非论文,「通感移觉」这种「使用知识」的修辞,很有效用,虽然形散,神却不散,不知看到这里(感谢)的你,对这会点头否?雾里看花,见识有限,全是个人思考和领悟的产物,恐怕错误不少,请多指正。

    最后把开头说的用java尝试函数式编程的代码贴上来吧,java8是刚学,代码挺笨的,请指教。

    public static void main(String[] args) {
        //把给定的一些字符串,统计字母出现次数,结果为:
        //a:1 b:4 k:3 o:6
        System.out.println(strs2SumGroupStr("booka", "bookb", "koob"));
    }
    
    //booka,bookb... -> 'a:1 b:4..'
    public static String strs2SumGroupStr(String... strs){
        return stream2SumGroupStr(strs2Stream(strs));
    }
    
    //booka,bookb... -> b,o,o,k,a,b...
    public static IntStream strs2Stream(String... strs){
        return String.join("", strs).chars();
    }
    
    //b,o,o,k,a,b... -> 'a:1 b:4..'
    public static String stream2SumGroupStr(IntStream is){
        return groupMap2SumGroupStr(stream2GroupMap(is));
    }
    
    //b,o,o,k,a,b... -> {a:*,b:*..}
    public static Map<Integer,IntSummaryStatistics> stream2GroupMap(IntStream is){
        return is.boxed().collect(Collectors.groupingBy(i->i, Collectors.summarizingInt(i->i)));
    }
    
    //{a:*,b:*..} -> 'a:1 b:4..'
    public static String groupMap2SumGroupStr(Map<Integer,IntSummaryStatistics> map){
        return groupSet2SumGroupStr(groupMap2GroupSet(map));
    }
    
    //{a:*,b:*..} -> ('a:1','b:4'..)
    public static Set<String> groupMap2GroupSet(Map<Integer,IntSummaryStatistics> map){
        return map
                .keySet()
                .stream()
                .map(k->(char)k.intValue()+":"+map.get(k).getCount())
                .collect(Collectors.toSet());
    }
    
    //('a:1','b:4'..) -> 'a:1 b:4..'
    public static String groupSet2SumGroupStr(Set<String> set){
        return set
                .stream()
                .reduce((x,y) -> x + " " + y)
                .get();
    }
  • 相关阅读:
    python 执行sql得到字典格式数据
    python爬虫 url链接编码成gbk2312格式
    windows环境下elasticsearch安装教程(单节点)
    python SQLServer 存储图片
    爬虫的本质是和分布式爬虫的关系
    requests form data 请求 爬虫
    mysql 删除 binlog 日志文件
    查看mysql数据表的大小
    xshell 连接报错 Disconnected from remote host
    centos 7.3 安装 mysqldb 报错 EnvironmentError: mysql_config not found ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
  • 原文地址:https://www.cnblogs.com/zidafone/p/5779097.html
Copyright © 2011-2022 走看看