有些程式語言,會提供名為tuple的資料型態,表面上,這看來像是list之類的結構,然而,會有一些list所沒有的限制,但有些開發者面對tuple時,會仍以list角度看待,導致難以理解其作用,因而list與tuple的差別,經常是程式語言討論區的FAQ。

Python的tuple

例如,在Python當中使用的tuple,許多開發者只將它看成是不可變動(immutable)的list,令其形同雞肋,或者僅將其用於需要不可變動的場合,像是作為不可變動的引數傳遞給函式,或者解釋一下因為不可變動,有時建立tuple會比list來得經濟。

有時,則會在示範Python中x, y = y, x可以交換變數值時,解釋一下tuple,因為y, x會建立tuple,x, y=這個指定,會對建立的tuple進行解構,因而構成了交換變數值的效果,由於可以對tuple解構,在函式需要傳回多個結果時,不少文件也談到可以使用tuple。

其實解構的另一面,代表著tuple有著固定結構,就Python而言,tuple可以表達的結構資訊,包含資料的欄位順序、長度,而不可變動代表一個tuple記錄了唯一的狀態,沒有變動為其他狀態的可能性,也就是不會有隱藏的狀態。

什麼樣的資料,會需要記錄欄位順序、長度且不可變動呢?

最簡單易懂的例子就是座標點,像是:(10,)代表一維座標,(20,30)代表著二維座標,若是笛卡兒座標系,20代表x座標,30代表y座標;若是極座標系,兩個欄位就分別代表半徑與角度;必要時,我們也可以使用更多的元素來表示更高維度,例如四元數。

也就是說,如果我們想要將資料欄位順序、長度組成,視為一種資料集合(例如笛卡兒座標系的點集合),此時,我們可以使用簡單的解構語法就能處理資料,若不想大費周章地定義一個型態時,就可以使用tuple。

TypeScript的tuple

在TypeScript中,也可以建立tuple,方式是宣告型態時,依序宣告各欄位的型態,也就是TypeScript的tuple除了順序、長度,型態也被明確揭露。

例如,let org:[number,number]=[0,0]建立了tuple,代表著座標原點,如果試圖將['a','b']指定給org,會引發編譯錯誤,這代表[number,number],也構成了一種匿名形態。

回頭看看Python,雖然身為動態定型語言,然而其tuple也隱含著型態資訊,畢竟面對一個tuple,還是必須知道tuple的各欄位型態是什麼,不然後續處理就可能引發TypeError,也就是說,無論是Python或TypeScript,tuple將元素型態及順序構成了一種新型態,因為型態與順序固定了,同一型態的tuple,就能以可預期的模式解構其中的元素。

只不過,在TypeScript當中,為何我們不去使用{x:number,y:number}來標註org,而將特性名稱也納入結構考量呢?如果我們想要揭露的資料,其特性名稱也算是結構一部份的話,當然可以使用物件結構,而不是tuple。

就作用而言,這有點像在Python中進一步使用dataclass,而不只是tuple(或是named tuple之類),只不過就TypeScript而言,以物件結構來定義型態會失去欄位的順序資訊,從而在解構物件的語法上會麻煩一些,相較而言,由於Python 3.10有了match-case特性,dataclass的欄位順序也是結構的一部份,可便於進行模式比對(pattern match)。

嗯?TypeScript不是可以替tuple標示出名稱嗎?雖然這看來有點像是Python的namedtuple,但是,實際上,那只是為了增加可讀性,不能像Python的namedtuple直接透過特性名稱來取得值。

而且,我們還要留意,TypeScript的tuple是可變動的,例如方才的org,可以用org.push(1,1)增加元素,但這就怪了,畢竟型態是[number, number],元素卻是[0,0,1,1]?若想避免這類問題,我們可以標註org為Readonly<[number, string]>,如此一來,就不能透過org來變動元素內容了。

來自函數式的tuple

不可變動?欄位順序?長度?型態?揭露資料的結構?解構?模式比對?如果對函數式設計稍有涉獵,你看到這邊可能就會觸及敏感神經了,想詢問tuple的概念是否來自函數式設計呢?

的確在函數式語言中,tuple是經常使用的資料結構,除了不具備欄位名稱,tuple完全揭露了資料在結構上的相關資訊,因為結構公開,以模式比對來處理tuple是自然而簡單的方式。

在函數式語言中,tuple多半會被實作為product型態(可參考先前專欄〈重構與代數資料型態〉),具體而言,就是欄位型態的組合,這將會構成一種新型態,在我們需要臨時建個結構代表某個資料,又不想大費周章地正式定義某型態時,這就是很方便的作法。

不過,要注意的正是tuple的欄位型態組合為匿名,這表示相同型態組合下,就算是代表不同資料集合,語法上也會過關。

例如,方才TypeScript中的org,若意圖上想代表笛卡兒座標,然而,有個(10,30)資料其實代表極座標,當其被指定給org時,此時,在TypeScript中並不會有任何錯誤。

然而,就算你使用type為[number,number]定義了別名Cartesian,也只是個別名而已,這就像是在Python中,使用了namedtuple,若有Point與Pt兩個namedtuple,雖然彼此的名稱不同,但本質上還是個tuple,因此,在相等比較時,就只會針對欄位來判斷而已。

如果在模式比對時,你需要具體的整體型態名稱,或者更進一步地,需要將欄位名稱納入結構考量,那麼,在Python中需要是dataclass。

因為Python 3.10以後的match-case特性,可以針對型態名稱進行比對,若從這個角度來看,tuple這東西的用途就很明確了,那就是作為一種「簡便的資料載體」。

簡便的資料載體

先前專欄〈不只是語法糖的記錄類別〉為了介紹Java 16以後的記錄類別新特性及語義,我曾不斷強調「資料載體」這個字眼,並且談到作為一個資料載體,封裝的邊界就是單純地將一組資料聚合在一起,並且揭露資料的一切。

若將Java中記錄類別的種種限制,視為實現資料載體時必要的考量,並將之拿到Python、TypeScript等語言,看待其中tuple的特性,我們就會發現:tuple正適合作為一個資料載體。

然而,在Java中,並沒有tuple,不過,我們可以看看Haskell,例如,在《HASKELL趣學指南》的〈Type and Typeclasses〉曾經談到,每個tuple都是獨立的型態,而在〈Data Classes and Sealed Types for Java〉當中,就談到Java的記錄類別,就是有名稱的tuple(nominal tuple)。

現在你應該知道Python、TypeScript或其他具備tuple的語言中,怎麼使用tuple了吧!如果你有一筆資料,我們會知道tuple可作為簡便的資料載體,用來揭露該資料的結構(雖然沒有名稱),以便客戶端有足夠的彈性,並且可以針對資料增加新的處理函式,搭配模式匹配來處理資料(至於模式比對的優缺點,可參考先前專欄〈模式比對與多型〉)!

專欄作者

熱門新聞

Advertisement