avatarRyan Yang

总结

这篇文章是关于作者30天内使用四种不同的编程语言(Scala、Python、Golang、Rust)来完成“30 Days of Code”挑战的计划,并分享学习过程中的知识点和语言特性。

摘要

文章标题为“[Day 0] 30天的四週四语言大挑战 - 最经典的起手式!”,作者受到《Seven Languages in Seven Weeks》和Hackerrank的“30 Days of Code”启发,提出了一个结合两者的挑战计划。作者选择了Scala、Python、Golang和Rust四种语言进行挑战,分别代表了作者熟悉的语言、工作中接触的语言以及想要学习的语言。作者希望通过每天的更新和分享,让读者一起成长。

挑战内容包括从标准输入读取用户输入并打印到标准输出。作者逐一用四种语言实现了这一功能,并分享了每种语言的特性和编程思想。例如,Scala是一种纯面向对象的语言,Python是一种脚本语言,Golang需要编译,而Rust强调内存安全和所有权概念。

作者还提供了每种语言的代码示例,并解释了代码的关键部分。在Scala中,使用scala.io.StdIn从标准输入读取数据;Python使用input()函数;Golang通过bufio.NewReader建立一个读取器并使用ReadString方法;Rust则通过stdin().read_line(&mut input)读取输入,并强调了所有权和借用的概念。

最后,作者鼓励读者持续关注这个挑战,并在Medium上点赞和追蹤,以及在文章底部的按钮上点赞以支持作者。

观点

  • Scala:作为一种纯面向对象的语言,Scala的程序入口在一个顶层的Object中定义,其中包含main方法。Scala是静态类型语言,强调函数式编程,鼓励使用不可变变量(val)。
  • Python:作为一种脚本语言,Python不需要编译,直接由解释器逐行执行。Python的变量没有类型限制,可以随意赋值不同类型的值。
  • Golang:需要编译,使用package mainfunc main()定义程序的入口点。Golang的错误处理通过函数返回值来实现,而不是使用传统的try...catch机制。
  • Rust:强调内存安全,引入了所有权和借用的概念。Rust使用use关键字引入其他模块的函数,并且使用fn定义函数。Rust的println!是一个宏,而不是普通函数,可以接受任意数量的参数。

作者认为,即使第一天的挑战看似简单,也能探讨和学习到各种语言的特性,从而促进读者的成长。作者期待着读者对这个挑战的持续关注和支持。

[Day 0] 30 天的四週四語言大挑戰 - 最經典的起手式 !

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

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

挑戰內容

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

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

Scala

  • 在 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 所輸入的內容,這裡 invokereadLine 這個 Method 來從 Standard input 讀取,當 User 輸入完並按下 Enter 後,這個 Method 就會 Return,並存入 s ,然後我們就可以透過 printlns 給印出來囉!

Python 3

  • 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

  • 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 可參考 https://www.quora.com/In-C-what-does-buffering-I-O-or-buffered-I-O-mean/answer/Robert-Love-1)
  • 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)
  • 這邊有一個地方稍微再解釋一下,在 golang, := 是宣告加賦值,而 = 只是賦値而已。一般來說,宣告一個變數會像 var foo int = 10 ,而這相當於 foo := 10 ,當使用 :=的時候,連 Type 都可以自動被 inferred,算是一個常見的方式囉!

Rust

  • 首先我們可以先發現在 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 就是說明可以修改囉!(昏頭了嗎?哈!可以參考 https://michaelchen.tech/rust-prog/ownership/ 有更多的說明)!
  • 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 囉!

結語

雖然第一天的挑戰看似很簡單,但是我們也探討了很多語言特性,希望大家持續關注這個挑戰,那我們明天見囉!

如果覺得這篇還滿實用的,可以在 Medium 拍手和追蹤支持我繼續寫下去,然後也在下面登入按個讚支持一下囉!謝謝!

Scala
Python
Go
Rust
Programming
Recommended from ReadMedium