switch这个语法,相信所有碰过编程的小伙伴都不会陌生。是在众多老旧语言中,switch都有着惊人的相似实现。但笔者一直觉得它的存在多少有点鸡肋,让我们先来看看Java中的switch

public class SwitchDemo {
    public static void main(String[] args) {

        int score = 5;
        String scoreLabel;
        switch (score) {
            case 1:
                scoreLabel = "差评";
                break;
            case 2:
                scoreLabel = "不满意";
                break;
            case 3:
                scoreLabel = "一般";
                break;
            case 4:
                scoreLabel = "还不错";
                break;
            case 5:
                scoreLabel = "非常满意";
                break;
            default:
                scoreLabel = "无效得分";
                break;
        }
        System.out.println(scoreLabel);
    }
}

Java的这段switch应该还是很有代表性的,就是从C延续下来的写法,很多语言都是这么设计的。说实话,这一水的break,看着还是有点难受,也不能说break的设计完全没用,但是如果大部分情况下break都是不能省的(省了逻辑就变了),那这个设计就值得商榷了。Java程序员忍了多少年,情况终于在2019年发生了一点改变,以下是Java12带来的新的语法:

String scoreLabel = switch (score) {
    case 1 -> "差评";
    case 2 -> "不满意";
    case 3 -> "一般";
    case 4 -> "还不错";
    case 5 -> "非常满意";
    default -> "无效得分";
};

这样写的确清爽很多,我们再来看看同样的问题,在Swift中怎么实现:

let score = 5
var scoreLabel = ""
switch score{
    case 1:
        scoreLabel = "差评"
    case 2:
        scoreLabel = "不满意"
    case 3:
        scoreLabel = "一般"
    case 4:
        scoreLabel = "还不错"
    case 5:
        scoreLabel = "非常满意";
    default:
        scoreLabel = "无效得分";
}
print(scoreLabel)

猛一看,Swift这版代码多了好多行,感觉没有Java12这版优雅啊。这是因为Swift里的switch不能当作一个具备返回值的表达式放在等号的右边。但其实我并不觉得这是Swift的缺点。因为在强类型语言中,任何数据的类型都是非常重要的,不可以朦胧不清。在Java12的示例中,scoreLabel是一个String,但是等号右边的switch如何保证自己的返回是一个String呢?作为一个人类,纯看代码,其实并不能明确地看出来。当然,作为一个机器,编译器还是能看出来,比如我们稍微改下Java12这版代码:

int score = 5;
String scoreLabel = switch (score) {
    case 1 -> "差评";
    case 2 -> "不满意";
    case 3 -> "一般";
    case 4 -> 3;
    case 5 -> "非常满意";
    default -> "无效得分";
};
System.out.println(scoreLabel);

在编译代码阶段,我们就会遇到一个报错提示:

java: 不兼容的类型: switch 表达式中的类型错误
    int无法转换为java.lang.String

所以,不能说Java12的设计有什么问题,反正编译器这边是清醒的。但是我个人总觉得,把等号左边的变量类型通过switch穿透到其内部,来约束switch里面的事情,有越俎代庖之嫌。

下面我们来改进Swift的版本,看看能否得到近似于Java12这般优雅的方式。

let score = 5
let scoreLabel = {()->String in
    switch (score) {
    case 1 :  return "差评"
    case 2 :  return "不满意"
    case 3 :  return "一般"
    case 4 :  return "还不错"
    case 5 :  return "非常满意"
    default : return "无效得分"
    }
}()

print(scoreLabel)

我这个写法对于Swift新手来说,稍微有点超纲了。等号右边是一个立即调用函数表达式,类似于JavaScript里面的这种写法:

var result = (function () {
    var name = "Barry";
    return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"

我们说回Swift,你大概能明白scoreLabel的值就是等号右边函数执行过后的返回值。你可能会诧异为什么我写的函数没有名字,因为这里是用Closure(闭包)写成的匿名函数,被花括号包围的整个区域(包括花括号),也就是:{()->String in …… }都是这个函数的定义,其中单词in左侧的String代表了这个函数的返回值,而in之后的内容就是函数的逻辑代码了。在函数定义的}之后,我放一对小括号(),也就是这对小括号产生了神奇的效果,让函数获得了立即执行的魔力。

如果你不小心漏写了最后的小括号,比如这样:

let score = 5
let scoreLabel = {()->String in
    switch (score) {
    case 1 :  return "差评"
    case 2 :  return "不满意"
    case 3 :  return "一般"
    case 4 :  return "还不错"
    case 5 :  return "非常满意"
    default : return "无效得分"
    }
}

print(scoreLabel)

你的得到的结果将会是:

(Function)

也就是说scoreLabel这个常量存储的内容是个函数。而有了小括号,scoreLabel2里面存的就是函数的返回值了,你将会看到如下输出:

非常满意

现在回过头来对比下SwiftJava12的两个实现版本,不难看出Swift中通过一次函数返回值的明确声明,锁定了其内部的return语句必须返回String,不失为一个优雅的解决方案,与Java12相比,可谓各有特色。但是考虑下面这种场景,Swift交出来的答卷就有意思了:

score = 3
let scoreLabel3 = {()->String in
    switch (score) {
    case 1 ... 2 :  return "差评"
    case 3 ... 5 :  return "好评"
    default : return "无效得分"
    }
}()

print(scoreLabel3)

如果我们只想给得分来个二分类,1到2分是差评、3到5分是好评。用上面的代码就会非常直观。并且case 1 ... 2 :在此也可以写为case 1 ..< 3 :,同样具有不错的表现力。

写到这一步,Java其实就已经力不从心了。不过下面的代码才是Swift独享的炫技时刻:

let vegetable = "red pepper"
switch vegetable {
    case "celery":
        print("Add some raisins and make ants on a log.")
    case "cucumber", "watercress":
        print("That would make a good tea sandwich.")
    case let x where x.hasSuffix("pepper"):
        print("Is it a spicy \(x)?")
    default:
        print("Everything tastes good in soup.")
}

这里是一位小伙伴在表达自己对各种蔬菜看法。我们先抛开他的个人喜好,注意代码中的第三段case。它专门用来判断vegetable是否以pepper字样结尾,在本示例中,最终输出的代码为:

Is it a spicy red pepper?

通过这段输出,你应该已经明白x的内容就是red pepper,也就是说x即是vegetable。并且这里的x仅仅是个代号,你可以换成任何其他你愿意使用的变量名。也就是说,case后面的这个let,就像魔术师的手,响指一打,你就会获得需要做switch的判断的那个值。有了这个值,我们就可以基于它构建一个能够计算出布尔值的表达式,并把这个表达式放在where之后。这样,当表达式的结果为true时,这条case语句也就命中了。并且不要忘记,刚才魔术师帮你拿到的x,是可以在case语句执行逻辑的区域内使用的。

显然swift的这种switch用法,给我们增加了很多的想象力。不过也许有人会说,这样的switch是不是与if的功能重叠有点多了?我觉得确实是这样,最早switch被设计成一种if的简化版本,主要用来针对等值判断的,但是显然程序员们的愿望不止如此。所以我们才会在本世纪的一些新鲜语言里,看到更为激进的表现形式。

不过,语言层面提供的这些东西,就像佐料,至于真正做菜的时候加什么,放多少,还得是你这位程序员说的算。

阅读相关文章:Swift初体验(1)