• Desugar Scala(17) -- Option和for,以及脑子里发生的事情


    欢迎关注我的新博客地址:http://cuipengfei.me/blog/2014/08/30/options-for/

    Scala里的for关键字是个很有趣的东西。可以用来把多层嵌套for循环写成一层。比如这样:

    1
    
    for(i<-1 to 10;j<-1 to 10;k<-1 to 10) yield(s"$i $j $k")
    

    这行代码执行的结果是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    1 1 1
    1 1 2
    1 1 3
    1 1 4
    1 1 5
    1 1 6
    1 1 7
    1 1 8
    1 1 9
    1 1 10
    1 2 1
    1 2 2
    1 2 3
    1 2 4
    1 2 5
    1 2 6
    1 2 7
    1 2 8
    1 2 9
    1 2 10
    ......
    ......
    

    这样,就可以用一行代码写出三层循环的效果。代码看起来非常紧凑,噪音很少。

    但是今天主要要说的不是这种for,而是它和Option结合的写法。

    Option本身是一个抽象类,代表一个可能存在,也可能不存在的值(那谁谁的喵?)。它有两个实现类,分别是Some和None。顾名思义,Some代表有值,None代表没有。

    实际上,上面的说法不够准确,Some是一个实现类,而None实际是一个单例,不过这点对后面的内容没影响。

    现在设想一个很简单的场景,需要用单价和数量来算总价,而单价和数量未必拿得到,那代码大概会是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      def calculateTotal: Option[Int] = {
        val price: Option[Int] = getPrice
        val amount: Option[Int] = getAmount
    
        if (price.isEmpty || amount.isEmpty) {
          None
        } else {
          Some(price.get * amount.get)
        }
      }
    

    getPrice和getAmount都返回一个Option[Int],就类似Java中Integer可以为null一样。计算出来的总价也是一个Option[Int],说不定会有,也说不定没有。

    在这段代码中先检查单价和数量是否存在,如果二者中任意一个不存在,那就返回None,代表无法求得总价。如果二者都存在,那就将二者的乘积用Some包起来返回。

    这代码看起来还ok,很常规的写法,但是稍显啰嗦。如果用上for的话,可以大大简化这段代码:

    1
    2
    3
    
      def calculateTotalWithFor: Option[Int] = {
        for (price <- getPrice; amount <- getAmount) yield price * amount
      }
    

    这个方法体只有一行了,而它实现出来的行为和上面那段代码是完全一致的。

    这感觉好神奇啊,不用判断价格和数量是否存在,也不需要根据判断结果决定到底返回None还是Some。它是怎么搞的呢?

    看一下反编译的结果吧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public Option<Object> calculateTotalWithFor() {
        return getPrice().flatMap(new AbstractFunction1() {
            public final Option<Object> apply(final int price) {
                return OptionAndFor..MODULE$.account$of$OptionAndFor$$getAmount().map(new AbstractFunction1.mcII.sp() {
                    private final int price$1;
    
                    public final int apply(int amount) {
                        return apply$mcII$sp(amount);
                    }
    
                    public int apply$mcII$sp(int amount) {
                        return price * amount;
                    }
                });
            }
        });
    }
    

    这个反编译的结果很不好读,不过还是可以看出个大概。它先是对getPrice的返回值调用了flatMap,给其传入一个匿名函数(AbstractFunction1),在这个匿名函数里面又对getAmount的返回值调用了map,也给其传入了一个匿名函数,再在这第二层匿名函数里做了乘法运算。

    如果用Scala把它表达出来,是这样的:

    1
    2
    3
    
      def calculateTotalWithFlatMapAndMap: Option[Int] = {
        getPrice.flatMap(price => getAmount.map(amount => amount * price))
      }
    

    由此可见,上面使用for的代码的神奇之处在于它利用了Option的flatMap和map方法。

    这两个方法具有一个共同特征:如果被调用flatMap或者map的当前Option实例为None的话,则忽略传入的匿名函数,直接返回None。

    这很容易理解,要参与运算的成员之一已经是None了,那就不用管剩下的成员到底是啥了,它随便是啥,最终的计算结果都会是None。这和最初写出的用 || 运算符的代码的逻辑是一致的。

    到此为止,我们给Option和for的结合使用脱光了衣服,它就是利用Option的flatMap和map来实现紧凑的代码的。

    神奇之处不仅在于更短的代码,还在于它提高了信噪比,给我们提供了更加简化的思考模型

    最初那段用if else的代码,在写它或者读它的时候,我们的脑子里面发生了什么呢?

    1. 要获取价格和数量
    2. 要判断价格是否为空,要判断数量是否为空        (与业务关联较小,属于技术范畴)
    3. 如果任意一个为空,结果是空                 (与业务关联较小,属于技术范畴)
    4. 如果两个都不是空,再做乘法运算
    

    而在写或者读用for的那段代码的时候,脑子里又是怎么想的呢?

    1. 获取价格和数量
    2. 做乘法运算
    

    我们写这段代码的目的是要表述业务逻辑,是要给未来读代码的人传递和业务相关的信息。

    而空值判断是偏技术的,把这种代码消掉,我们传递给其他程序员的信息里就含有更少的与业务无关的噪音。而且我们自己写起来的时候,脑子里也不需要考虑那么多的东西。

    对自己,对他人都有利。这实在是一个美妙的语言特性。

  • 相关阅读:
    立即执行函数的装逼写法
    SublimeText个性化快捷键设置
    arguments的理解
    webpack详细配置讲解
    webpack配置命令
    组件(0)
    特殊符号 && 和 ||
    Sublime Text的常用插件
    如何清除夜神模拟器的Pin密码
    swagger--Failed to load API definition.
  • 原文地址:https://www.cnblogs.com/cuipengfei/p/3947117.html
Copyright © 2020-2023  润新知