iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 1
4

不知道大家有沒有看過有一本書叫做 seven languages in seven weeks,這本書是希望能夠讓你用七週的時間,每個禮拜都能夠去認識一門新的語言。當然要學習一個語言並且精通,肯定是不可能只需要一週,但是至少可以認識這個語言的特性以及他的長處。而我最近剛好看到 Hackerrank 有一個專欄叫做 "30 Days of Code",於是我就想說,何不把這兩個概念結合在一起,來挑戰看看30天內用四個語言,也就是四週四,來完成這個專欄呢?因此就有這個 "30 天的四週四大挑戰" 計畫啦!

而我選擇的語言分別是 Scala、Python、Golang、Rust。Scala 是我最為熟悉的語言,Python 則是工作上最近接觸到。至於 Golang 和 Rust 則是我一直想要學學看的語言。相信如果大家從今天開始每天關注 (督促?) 我的話,並且也能夠看看我寫的內容,30天過後我們應該都能一起有所成長囉!那就開始吧!

四語言小簡介

Scala:

Scala 吸收了各語言精華,是個多範式語言,既支援 OOP 也支援 Functional Programming (因此撰寫平行化計算相當容易)。寫出來的程式有時候可以像動態語言一樣簡潔,但是 Scala 本身又是很嚴謹的靜態語言。Scala 本身執行在 JVM 上,所以可以和 Java 許多成熟的 Library 直接互通。因為上述優點,也讓 Scala 的學習曲線較為陡峭。目前較為知名的專案有 Spark, Kafka 等等。

Python:

Python 以它的語法簡潔,易讀易懂,而受到大家的喜愛,也是初學者相當好入門的一個語言。其歷史悠久,資源豐富,有許多 Library 幫助加速開發,因此也是個通用型的語言。Python 為直譯式物件導向語言,執行方便且跨平台,但執行速度會比編譯式語言來得慢,Debug 也會稍微麻煩。目前較為知名的專案有 TensorFlow, Django 等等。

Golang:

Golang 是 Google 所推出的語言,是個通用型語言。其基礎語法與 C 語言類似,但語法更加簡潔,學習上也更容易上手。而其語言架構設計單純,又是編譯式語言,因此效能比起像是 Python, Java 來得好,另外在並行計算這方面的撰寫也相當容易。目前較為知名的專案有 Prometheus, etcd 等等。

Rust:

Rust 是由 Mozilla 主導開發的通用型語言,其是編譯式語言。設計準則為 "安全、並行、實用",也支援多範式的風格。其試圖改進 C/C++ 的一些缺點,例如讓記憶體管理更加安全,很適合拿來作為撰寫系統程式。但這不代表 Rust 並不能撰寫一般應用程式。Rust 已經連續三四年在 Stack Overflow 上獲選為開發者最喜愛語言第一名。

正式開始我們的旅程吧!


今日挑戰內容

將 User 從 Standard Input 輸入的內容存入一個變數並且透過 Standard Output 印出。

今天的挑戰比較是讓我們暖暖身,主要是讓我們能夠練習各語言如何從 Standard Input 得到 User 所輸入的值,並且印出來囉!

Scala

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 的意思,不同於其他語言所謂的變數, valImmutable 的,也就是賦值沒不能再改變。當然 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 ,然後我們就可以透過 printlns 給印出來囉!

Python 3

input_string = input()
print('Hello, World.')
print(input_string)
  • Python 是 Scripting language,也就是說,不需要 Compile 這個階段,你寫的程式是在啟動後由所謂的 Interpreter 逐行執行。好處是少了 Compile 的時間,壞處是有些程式的錯誤必須要等到執行到該部分時才會被發現,要是該部分幾乎很少被執行到,那可能就會有 Bug 在裡面卻渾然不覺了。
  • Python 的值是有 Type 的,但是變數本身並沒有 Type 的限制,所以可以隨意將不同 Type 的值賦予給同一個變數。像是先 x = 1x = '123' 是可以被允許的唷!
  • input_string = input() 是透過 Python 的內置函數 input 將 User 在 Standard Input 的輸入讀入並且存入 input_string 。接著透過 print 將結果給印出來。

Golang

package main
import (
    "fmt"
    "bufio"
    "os"
)
func main() {
    inputreader := bufio.NewReader(os.Stdin)
    input, _ := inputreader.ReadString('\n')
    fmt.Println("Hello, World.")
    fmt.Println(input)
}
  • Golang 是需要 Compile 後才能執行的語言。在這裡我們先看到 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 兩個值,分別是 stringerror,而這裡因為我們不處理 error,所以用了 _ 表示我們忽略這個回傳值。而 Return error 這是 Golang 常見的 Error handling 的方式。Golang 並沒有 try…catch,而是把錯誤用回傳值的方式,讓 invoke 的人自己去接續處理。通常我們會判斷 error 這個回傳值是不是 nil 來決定要不要處理。( nil 就是 Golang 中某些 Type 像是 pointers、interfaces, maps, slices, channels 的 zero value)
  • 這邊有一個地方稍微再解釋一下,在 golang,:= 是宣告加賦值,而 = 只是賦値而已。一般來說,宣告一個變數會像 var foo int = 10 ,而這相當於 foo := 10 ,當使用 := 的時候,連 Type 都可以自動被 inferred,算是一個常見的方式囉!

Rust

use std::io::stdin;
fn main(){
    let mut input = String::new();
    stdin().read_line(&mut input);
    println!("Hello, World.\n{}", input);
}
  • 首先我們可以先發現在 Rust 裡頭,分號是必須的唷!這點跟很多 Modern languages 不同,你可以說有點麻煩,但好處是讓可讀性能夠增加,表示一個表達式的結束,因為 Rust 是 expression based (雖然我已經習慣沒有打分號的日子)。
  • Rust 透過 use 來將其他的 Module的 Function 引入,讓我們在使用 Function 時可以不用將整個路徑寫出來。在 Rust 定義 Function 的關鍵字是 fnlet 是做變數綁定,為什麼我特別說是變數綁定而不是傳統的變數賦值?這裡就要講到 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)& 囉! 最後就是 &mutmut ,因為在 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 個字)


下一篇
[Day 1] 資料型態不無聊!
系列文
30 天把自己榨好榨滿的四週四語言大挑戰!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言