variable: Type 與 Type variable

lymxit發表於2018-01-22

目錄

[TOC]

參考資料:

Why does Scala choose to have the types after the variable names?

Why does Kotlin require type after variable, rather than before?

C#爭論:什麼時候應該使用var?

Eric Lippert's description of why using the C style syntax in C# was probably a mistake.

Kotlin 建立二維陣列、三維陣列

正文

之前一直使用 Java 作為主要語言,在學習 Kotlin 的時候,突然發現 Kotlin 定義一個變數的語法與之前不太一樣。

int a = 5; // java way
var a: Int = 5; // kotlin way
var a = 5  // also kotlin way
複製程式碼

為何 Kotlin 在全面相容 Java 的同時會選擇 Scala 的這種語法呢?

為什麼很多現代語言(21世紀之後出現)越來越青睞於型別放在變數名後面這種方式呢?

也就是 var + name + :Type 這種形式呢?

大概有這樣幾個好處:

1、強調命名的重要性

2、強迫對變數進行初始化(減少 NPE)

3、Type Inference(型別推斷)

4、規避 Java 陣列的一些困惑

JEP 286 :Java 10 也將 var 型別推導這種特性加入到 區域性變數 中

強調命名的重要性

我們閱讀程式碼的時候,都是從左到右讀下來的。

假如我們省略變數型別的話,就像 動態型別語言一樣了,所以如果變數的命名不能暗示它的型別資訊,那麼可能

我們還需要看一下這個變數屬於什麼型別的(當然,IDE can help us)

var testData = getTestList()  
var testDataList = getTestList() // better
複製程式碼

強迫對變數進行初始化

Kotlin 相比與 Java 中最大的改進應該是利用語法儘可能去減少 NPE :

class MainActivity : Activity() {
   var mButton : Button // error : Property must be initialized or be abstract
   var mButton : Button? = null //valid , instantiate with null
   lateinit var mButton : Button  // or we can use lateinit
}
複製程式碼

假設是 Java 的話,雖然有隱式初始化,但你在使用成員變數的時候,你還是不知道這個成員變數是否是賦值了,所以你需要防禦性程式設計:

class MainActivity extends Activity {
   Button mBtn;
   if (mBtn != null){
     // do something
   }
}
複製程式碼

為型別推斷(Type Inference)做準備

Larry Wall 曾在 《Programming Perl》中說過 "程式設計師的3個美德" : 懶惰、不耐煩、驕傲。

型別推斷其實就體現程式設計師的"懶惰",其實我們在 Java 中就曾經見到過這種特性:

HashMap<String,String> map = new HashMap<String,String>();
// IDE can inference the generic
HashMap<String,String> map = new HashMap<>();
// lambda
IntStream.of(1,2,3,4,5)
         .forEach(i -> System.out.println(i)); // (int i) -> sout(i)
複製程式碼

我們知道 Kotlin 中可以省略型別,IDE 會自動從賦值語句中推斷出型別資訊:

var str: String = ""  // same as Java: String str = "";
var string = ""  // you can skip the type
var char = 'a' // char
var int = 0 // int
var long = 0L
var float = 0F
var double = 0.0
var numArray = IntArray(3) 
// 特殊情況,如果賦值是 null 的話,它的型別並不是 Any 
var a = null  // a isn't Any,we can't use hashcode or toString
複製程式碼

但為何說這種 var + name + :Type 這種寫法有助於型別推導呢?

我們看下 C++ 的型別推導 auto,可以簡化很多程式碼 :

string search_item("Barth John");
for(std::pair<multimap<string,string>::iterator,multimap<string,string>::iterator> pos=authors.equal_range(search_item);pos.first!=pos.second;++pos.first)
    cout<<pos.first->second<<endl;
// use auto
for(auto pos=authors.equal_range(search_item);pos.first!=pos.second;++pos.first)
複製程式碼

但 Kotlin 中的 var 和 C# 中的 var (類似於 C++ 的 auto ) 又有什麼區別呢?

先看下 C# 的例子,出自於: Eric Lippert's description of why using the C style syntax in C# was probably a mistake.

C# 中的格式是:type identifier

Kotlin 中的格式是: var identifier : type

因此 C# 的設計者也很後悔沒有在 C# 1.0 的時候就使用 var identifier : type 的格式,導致程式語言中有很多 不一致

因為 var identifier : type 這種形式,在隱式推導流行起來的時候可以非常自然地進行過度:

var identifier : type ---> var identifier

而 C++ 和 C# 中,假設按照一樣的路數來,就變成了:

int a = 5;
b = 5; // skip type,but it isn't a declaration
複製程式碼

這顯然是不行的,因此需要 fake 出一種萬能型別來填充之前 type 所在的地方。

於是 C++ 有了 auto,C# 有了 var

但實際上這又是非常彆扭的,明明你不想寫型別資訊,但又自欺欺人一樣寫上了一個 萬能型別

所以 Go 語言在這裡處理的就好很多:

var name string = "tom"
var name = "tom"
name := "tom" 
複製程式碼

Java 現在怎麼使用這種形式呢? 其實也不是不行。

Lombok 是一個很好的專案,其中它就提供了這樣的型別推導

val example = new ArrayList<String>();
example.add("Hello, World!");
val map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
var x = "Hello";
複製程式碼

規避已有的一些語法問題

先看下 Java 的陣列定義:

int[] a; // valid suffix on type
int b[]; // C++ way , valid suffix on name
複製程式碼

好像還行,但如果我們把情況弄複雜一點:

int[] a,b; // all int[]
int c[],d; // c is int[],d is int
int[] e,f[]; // e is int[], f is int[][]
複製程式碼

Java 這種形式應該來說是為了相容 C++ 的語法(誰叫當初 Java 的目的就是取代 C++ 呢),但是這種

對於初學者來說其實是比較困惑的。

所以 Kotlin 一刀切,所有的陣列都變成了這樣:

Kotlin Java
IntArray int[]
FloatArray float[]
Array (不支援協變) T[](支援協變)

但這樣也有不足的地方,就是在表達多維陣列的時候比較複雜:

val array3d = Array<Array<Array<Int>>>(3){ Array<Array<Int>>(3){ Array<Int>(3){ it -> it} } }
// 但幸運的是我們可以縮寫
val array3d = Array(3){ Array(3){ Array(3){ it -> it} } }
複製程式碼

關於可讀性上的一些爭議

在 C# 引入 var 之後,就一直爭論不休,習慣於 C 語言式命名方式的程式設計玩家覺得 var 反而會降低程式碼的可讀性。

但在 Kotlin 的官方 FAQ 說了:

Why have type declarations on the right?

We believe it makes the code more readable. Besides, it enables some nice syntactic features. For instance, it is easy to leave type annotations out. Scala has also proven pretty well this is not a problem.

對於可讀性上的爭論,個人覺得見仁見智吧,如果真的完全濫用了 var 且忽略掉所有型別,加上如果程式碼寫的爛

一點,看上去基本上和動態語言貌似沒有什麼區別了(但 IDE 還是能為靜態語言提供很好的支援,比如說有哪些

函式、成員變數等)。

總結

Kotlin 作為一門 2010 年才誕生的語言,站在很多巨人的肩膀上去發展,吸收了 C#、Scala、Java等 的各種精華,也摒棄了這些語言的很多缺點。

variable: Type 這種形式最早可以追溯到 Pascal,主要目的是用於教學(然而國內基本都是C、C++、Java)。

而 C系列的 Type variable ,已經是工業界養成的習慣了,畢竟這種習慣上的哲學就類似於

位元組序上面的 大端和小端,不同習慣的人有不同的看法,這裡我們只能自己去適應語言。

但從整個程式語言的趨勢上來看,現代化的程式語言(Go、Rust、Kotlin、Swift)基本都選擇了 Pascal 的 variable: Type 形式,工業界也越來越願意接受 type inference ,作為習慣於C系列語言的程式設計師們,也要慢慢適應時代了。

最後再來看個 Scala 的程式碼:

val shapeInfo: HashMap[Shape, (String, String)] = makeInfo()

  • We define a value here, not a variable or method (val)
  • The name of the thing we define is shapeInfo
  • If you care about it, here's the type (HashMap[...])

相關文章