不知道大家有沒有看過有一本書叫做 seven languages in seven weeks,這本書是希望能夠讓你用七週的時間,每個禮拜都能夠去認識一門新的語言。當然要學習一個語言並且精通,肯定是不可能只需要一週,但是至少可以認識這個語言的特性以及他的長處。而我最近剛好看到 Hackerrank 有一個專欄叫做 "30 Days of Code",於是我就想說,何不把這兩個概念結合在一起,來挑戰看看30天內用四個語言,也就是四週四,來完成這個專欄呢?因此就有這個 "30 天的四週四大挑戰" 計畫啦!
而我選擇的語言分別是 Scala、Python、Golang、Rust。Scala 是我最為熟悉的語言,Python 則是工作上最近接觸到。至於 Golang 和 Rust 則是我一直想要學學看的語言。相信如果大家從今天開始每天關注 (督促?) 我的話,並且也能夠看看我寫的內容,30天過後我們應該都能一起有所成長囉!那就開始吧!
Scala 吸收了各語言精華,是個多範式語言,既支援 OOP 也支援 Functional Programming (因此撰寫平行化計算相當容易)。寫出來的程式有時候可以像動態語言一樣簡潔,但是 Scala 本身又是很嚴謹的靜態語言。Scala 本身執行在 JVM 上,所以可以和 Java 許多成熟的 Library 直接互通。因為上述優點,也讓 Scala 的學習曲線較為陡峭。目前較為知名的專案有 Spark, Kafka 等等。
Python 以它的語法簡潔,易讀易懂,而受到大家的喜愛,也是初學者相當好入門的一個語言。其歷史悠久,資源豐富,有許多 Library 幫助加速開發,因此也是個通用型的語言。Python 為直譯式物件導向語言,執行方便且跨平台,但執行速度會比編譯式語言來得慢,Debug 也會稍微麻煩。目前較為知名的專案有 TensorFlow, Django 等等。
Golang 是 Google 所推出的語言,是個通用型語言。其基礎語法與 C 語言類似,但語法更加簡潔,學習上也更容易上手。而其語言架構設計單純,又是編譯式語言,因此效能比起像是 Python, Java 來得好,另外在並行計算這方面的撰寫也相當容易。目前較為知名的專案有 Prometheus, etcd 等等。
Rust 是由 Mozilla 主導開發的通用型語言,其是編譯式語言。設計準則為 "安全、並行、實用",也支援多範式的風格。其試圖改進 C/C++ 的一些缺點,例如讓記憶體管理更加安全,很適合拿來作為撰寫系統程式。但這不代表 Rust 並不能撰寫一般應用程式。Rust 已經連續三四年在 Stack Overflow 上獲選為開發者最喜愛語言第一名。
將 User 從 Standard Input 輸入的內容存入一個變數並且透過 Standard Output 印出。
今天的挑戰比較是讓我們暖暖身,主要是讓我們能夠練習各語言如何從 Standard Input 得到 User 所輸入的值,並且印出來囉!
object Solution {
def main(args: Array[String]) {
println("Hello, World.")
val s = scala.io.StdIn.readLine()
println(s)
}
}
在 Scala 的世界,任何的東西都是 Object,也就是說 Scala 是個純 OO 的語言,而主程式我們要用一個 Top level 的 Object 來把我們的程式進入點,也就是 main
這個 method 給定義出來。當程式在執行的時候,就會從 main
開始執行。
def main(args: Array[String])
是 Scala 定義 Method 的方式, args
是這個 method 的參數,而冒號後面是 args 的 Type。在這裡是一個 Array of String。這裡我們可以了解到 Scala 是一個 statically-typed language,也就是每個變數或是表達式都有它的 Type,甚至 Function 也是一種 Type。
println("Hello, World.")
會印出 "Hello World."。 val
是 value 的意思,不同於其他語言所謂的變數, val
是 Immutable 的,也就是賦值沒不能再改變。當然 Scala 也有類似其他語言的一般變數,是用 var
來宣告。這可能會跟你以前所學不大一樣,但因為 Scala 本身提供了許多 functional programming 的 API 來幫助我們用 functional 的方式來撰寫程式,所以其實 Scala 寫久了會發現好像也可以不用 var
,好處是可以避免很多 Side effect。
scala.io.StdIn
是 Scala 的標準函式庫,可以幫我們從 Standard Input 讀取 User 所輸入的內容,這裡 invoke readLine
這個 Method 來從 Standard input 讀取,當 User 輸入完並按下 Enter 後,這個 Method 就會 Return,並存入 s
,然後我們就可以透過 println
把 s
給印出來囉!
input_string = input()
print('Hello, World.')
print(input_string)
x = 1
再 x = '123'
是可以被允許的唷!input_string = input()
是透過 Python 的內置函數 input
將 User 在 Standard Input 的輸入讀入並且存入 input_string
。接著透過 print
將結果給印出來。package main
import (
"fmt"
"bufio"
"os"
)
func main() {
inputreader := bufio.NewReader(os.Stdin)
input, _ := inputreader.ReadString('\n')
fmt.Println("Hello, World.")
fmt.Println(input)
}
package main
,在 Golang 當我們要 Compile 一個執行檔的時候,你必須要定義一個 package
叫做 main
,而程式的進入點則是在其中定義的 func main()
。import
宣告說我們需要哪些額外的 Package,而每個不同的 Package 裡面就會有不同的 Function 可以讓我們去引用。fmt
這個 package 可以讓我們格式化地去執行讀取寫入 I/O ,像是將字串印在 Standard Output,像是 fmt.Println("Hello, World.")
。 bufio
則是 buffered I/O 相關的 package。(何謂 Buffered I/O 可參考 這裡)bufio.NewReader(os.Stdin)
是透過 NewReader
這個 Function 建立一個讀取器的指標,並且和 Standard input 綁定在一起,也就是這裡的 os.Stdin
,讓我們能讀取 Standard input 的資料。而我們用這讀取器的 ReadString
方法並且指定 delimiter 為 \n
,也就是指定 \n
作為斷行的依據。最後就是透過 fmt.Println(...)
來把結果印出來囉!而 input, _
是因為 ReadString
會 Return 兩個值,分別是 string
跟 error
,而這裡因為我們不處理 error
,所以用了 _
表示我們忽略這個回傳值。而 Return error 這是 Golang 常見的 Error handling 的方式。Golang 並沒有 try…catch
,而是把錯誤用回傳值的方式,讓 invoke 的人自己去接續處理。通常我們會判斷 error
這個回傳值是不是 nil
來決定要不要處理。( nil
就是 Golang 中某些 Type 像是 pointers、interfaces, maps, slices, channels 的 zero value):=
是宣告加賦值,而 =
只是賦値而已。一般來說,宣告一個變數會像 var foo int = 10
,而這相當於 foo := 10
,當使用 :=
的時候,連 Type 都可以自動被 inferred,算是一個常見的方式囉!use std::io::stdin;
fn main(){
let mut input = String::new();
stdin().read_line(&mut input);
println!("Hello, World.\n{}", input);
}
use
來將其他的 Module的 Function 引入,讓我們在使用 Function 時可以不用將整個路徑寫出來。在 Rust 定義 Function 的關鍵字是 fn
。let
是做變數綁定,為什麼我特別說是變數綁定而不是傳統的變數賦值?這裡就要講到 Rust 為了記憶體的安全性,而有一個很重要的觀念就是 Ownership (所有權)。如果有學過 C/C++,相信對 Pointer 一定不陌生 (或是頭痛?),而 C/C++ 因為讓你有很高的自由度可以操作 Pointer,所以就會有機會產生 Dangling pointer,也就是假設 A 和 B 都是指向同一個記憶體區塊的 Pointer,但是當 A 執行釋放記憶體後,想再用 B 去存取就會產生錯誤。而 Rust 則是引入了 Ownership 的觀念來解決這個問題。簡單來說,每份資料同一時間只會有一個擁有者,因此假使今天 A 是一個 Reference (可想成是 Pointer),而我們 let B = A
,此時所有權就從 A 轉移到了 B 身上,A 就不再能夠存取對應的資料了。但如果 A 是例如一個整數,就不會有這個問題,因為此時的 let B = A
,Rust 會拷貝一份資料給 B,所以 A 還是可以存取,與 Reference 的情況不同。回到 A 是 Reference 的情況,假使把 A 傳入某個 Function 作為參數,那麼當該 Function 執行完之後,A 也不能存取了,因為在傳入的那刻,所有權就轉移到了該 Function 身上。為了解決這個問題,在 Rust 又有一個 Borrowing 的概念,也就是把所有權暫時借給該 Function,等到 Function 返回之後,所有權又回到 A 的身上,而實務上就是在傳入的時候加上stdin().read_line(&mut input)
的 &
囉! 最後就是 &mut
的 mut
,因為在 Rust,如果要讓轉移後可以有權去修改,那也要明確指出,所以 &mut
就是說明可以修改囉!(昏頭了嗎?哈!可以參考 這裡 有更多的說明)!String::new()
的結果,會是一個新的 Empty string ,並且綁定在 input 這個變數。接著 stdin()
會回傳一個 stdin 的 struct,並且我們 invoke 其方法 read_line
,而這裡餵入的參數就是上面說明的 &mut input
,因為 input 這個變數必須要讓 read_line
這個 Function 拿到所有權並且修改,最後就是透過 println!
印出來囉!println!
出現了一個驚嘆號,這代表說這不是一般的函式,而是 Rust 中的巨集,至於 Rust 的巨集又是什麼呢?與像是 C 的 Macro 不同,Rust 的 Macro 不僅僅是文字替換。在 Rust 中, Macro 是用來讓 Compiler 替我們生出程式,Compiler 會先將 Macro 展開,生成程式,再進行編譯。而且 Macro 不會受到一般函數的限制,甚至也不一定要用 ()
來放入參數,可以使用像是 []
(像是 vec![1,2,3]
) 或是 {}
都可以。在 Rust 中,一般函數是無法接受任意數量的參數的,也因此 println!
勢必要是個 Macro 囉!雖然第一天的挑戰看似很簡單,但是我們也探討了很多語言特性,希望大家持續關注這個挑戰,那我們明天見囉!
(今日內容 6288 個字)