學習軌道
/
Julia
Julia
/
練習
/
評分大師
評分大師

評分大師

學習練習

簡介

到目前為止,課程大綱還沒有深入談到型別,但很明顯 Julia 裡確實有型別:

julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}

我們從未指定型別,但 Julia 還是自動幫它們指定了。

  1. Julia 有types,這是它設計上的核心。
  2. Julia 通常能「猜」出型別,靠的是Type Inference。

JIT 編譯器會檢視(所有)程式碼,看看變數如何被使用,然後_推斷_出一個適合該用途的預設型別。

型別指定

依賴型別推斷來解簡單的教學練習沒問題,但對於較大的程式,你很可能需要更精確的控制。

在多數現代處理器上,整數的預設型別是Int64。 我們在Numbers概念中看過,值可以轉換成特定的非預設型別。

julia> x = Int16(42)
42
julia> typeof(x)
Int16

不過,變數x仍然可以重新指定成不同的型別:

julia> x = "changed"
"changed"
julia> typeof(x)
String

這叫做type instability,它的特點是:

  • 在小型腳本裡非常方便。
  • 在較大的程式裡,對效能和可靠性都不好。

我們可以用::運算子來設定x的型別:

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.

現在y的型別是Int16,而且永遠都會是。 因此,編譯器知道要為它保留多少位元組,也能放心把其餘程式碼最佳化,依賴這個穩定的型別。 這樣一來,這個變數就和 C 這類靜態型別語言裡的變數差不多。

型別階層

Int64、Int16、String、Char:這些型別到底_從哪來的_。

在許多物件導向(OO)語言中,每個型別都是一個類別,子類別化把它們排進類別階層,而類別方法則定義了各種行為。

Java 和 Ruby 是這種模式的明顯例子,但就連 Python 在內部也類似。

Julia 沒有類別。

官方文件給的理由是,OO 功能會干擾 JIT 編譯器,並損害執行時的效能。

不過,看看這段程式碼:

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)

補充一些細節:

  • Int16是一種型別,我們可以建立這種型別的變數。
  • Int16是Signed的子型別,supertype()函式會告訴我們這件事。
  • 型別構成一組階層,往上經過Integer、Real和Number,最頂端是Any,而subtypes()會為我們列出階層的這個分支。

所有分支最後都終止於Any,它的獨特之處在於它是自己的父型別。

julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any

所以,Julia 沒有class階層,但它_確實_有type階層。

之後我們會試著拆解它的運作方式,但首先還有很多東西要探索。

測試型別

我們可以用typeof()依照平常的方式測試是否相等。

julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false

型別相等必須完全一致,因為這種比較方式並不理解型別階層。

更有彈性的是,isa會告訴我們某個值是否和比較對象同型別,或是它的子型別。 它可以用函式形式或中綴形式來寫。

julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false

注意,isa左邊要放的是value,不是type。

用這種方式比較兩個_型別_會得到意想不到的結果。 正確的運算子是<:,之後的概念我們還會經常看到它。

julia> Int64 isa Number  ## Don't do this!
false
julia> Int64 <: Number
true

抽象型別與具體型別

我們看到型別階層會形成樹狀結構(就電腦科學的意義來說,根在最上面)。

樹裡的每一項都是一個node,而它們可以分成幾類:

  1. 有子型別的節點叫做abstract。
  2. 沒有子型別的葉節點叫做concrete。
julia> subtypes(Integer)  # an abstract type
3-element Vector{Any}:
 Bool
 Signed
 Unsigned
julia> subtypes(Int64)  # a concrete type
Type[]

這是個重要的區別,因為只有具體型別可以instantiated成變數。

julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64

注意,嘗試使用抽象型別(在這個情況下)不會產生錯誤訊息,但編譯器會建立一個適當的具體型別:Int64,而不是Integer。

說明

在這道練習中,你會做一點資料工程,預先處理一些成績資料。 有一所學校,學生多得不得了(太多了嗎?),校方想對學生拿到的成績做些資料分析,而且希望做得有效率,因此重點是盡量精簡並整理這些資料。

評分等級通常是 0 到 10,所以每個成績只佔 8 位元的資料型態最有效率。

  • 有些教授用實數(因為他們對精確度很挑剔),你需要把這些數值轉成UInt8資料型態。
  • 有些教授則用帶負數的整數(用來懲罰特別不受教的學生),遇到這種情況,你需要把這些數值轉成Int8資料型態。

等你實作出轉換成績的函式之後,還要再寫一個函式來處理存放成績的集合,把成績降級,並以遞減順序回傳。

  • 有些教授用遞增排序的Vector來存放成績,因為他們對自己的資料很講究。
  • 有些教授則用未排序的Set來存放成績,因為他們比較懶一點。

這兩個函式中,你都需要處理無效的輸入,方法是丟出 MethodError。

Note

例外處理會在之後的概念單元中介紹,所以在這道練習裡,你可以使用以下語法:

throw(MethodError(f, args))

其中f是函式,args是傳入該函式的引數所組成的元組。 以demote(n)函式為例,f是demote,而args是(n,):

throw(MethodError(demote, (n,)))

1. 將成績降級

實作demote(n)方法。

  • 輸入為Float64時,應該四捨五入到最接近的整數,並回傳UInt8資料型態。
  • 輸入為任意的Integer時,應該以Int8資料型態回傳同一個整數。
  • 其他所有輸入都應該丟出 MethodError。
julia> demote(4.2)::UInt8
5

julia> demote(4)::Int8
4

julia> demote("hi")
MethodError: no method matching demote(::String)     #output truncated

2. 預先處理資料

實作preprocess(coll)方法。

  • 輸入為Vector時,應該將所有數字降級,並反轉該向量。
  • 輸入為Set時,應該將所有數字降級,並回傳依遞減順序排序的向量。
  • 其他所有輸入都應該丟出 MethodError。
julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
 3
 2
 1

julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
 6
 4
 3

julia> preprocess(42)
MethodError: no method matching preprocess(::Int64)     #output truncated
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Julia Exercism

準備好開始 評分大師 了嗎?

註冊 Exercism,透過 35 個概念128 個練習 和真人引導來學習並精通 Julia,全部免費。