1. 程式人生 > 實用技巧 >程式語言中的 6 個有趣特性

程式語言中的 6 個有趣特性

Java 是一門不斷髮展的語言,這是一件好事。然而,其他語言的一些特性也是值得研究的。語言的結構是人們思考問題的方式,也是人們設計解決方案的方式。學習或至少熟悉其他語言是借鑑其設計的好方法。

Java 是我學習的第一門語言並且是我專業使用的語言。它是我大約十五年以來的主要謀生手段。然而,它並不是我多年來學習和使用的唯一語言:例如,很久以前,我必須開發JavaScript程式碼來實現動態使用者介面。當時,它被稱為 Dhtml……幾年前,我還自學了 Kotlin,並且從未停止過使用它。去年,在一家新公司工作時,我嘗試了 Clojure,但沒有成功。

在上述所有場景中,Java 仍然是我學習和評判其他語言的基準。以下是一些有趣的語言特性,我認為這些特性對於來自 Java 背景的人都頗具思想挑戰性。

JavaScript:原型

JavaScript 是我和 Java 一起使用的第一種語言。儘管 JavaScript 已經發展這麼多年了,但它有一個實現起來非常奇怪的常見特性:新物件的例項化。

在 Java 中,首先建立要一個

publicclassPerson{

privatefinalStringname;
privatefinal LocalDate birthdate;

publicPerson(Stringname, LocalDate birthdate){
this.name = name;
this.birthdate = birthdate;
}

publicStringgetName(){
returnname;
}

publicLocalDategetBirthdate(){
returnbirthdate;
}
}

然後,就可以繼續建立該類的例項了:

varperson1 =newPerson("John Doe", LocalDate.now());
varperson2 =newPerson("Jane Doe", LocalDate.now());

JavaScript 與 Java 的語法非常相似:

classPerson{
constructor(name, birthdate) {
this.name = name;
this.birthdate = birthdate;
}
}

letperson1 =newPerson("John Doe",Date.now());
letperson2 =newPerson("Jane Doe",Date.now());

相似之處到此為止。由於 JavaScript 具有動態特性,所以可以向現有例項中新增屬性和函式。

person1.debug=function(){
console.debug(this);
}

person1.debug();

但是,這些只能新增到某個例項中。其他例項會缺少這些補充屬性或函式:

person2.debug();// Throws TypeError: person2.debug is not a function

要將函式(或屬性)新增到所有例項(無論是現在的還是將來的)中,都需要利用原型的概念:

Person.prototype.debug= function() {
console.debug(this);
}

person1.debug();
person2.debug();

let person3 = new Person("Nicolas", Date.now());

person3.debug();

Kotlin:擴充套件函式 / 屬性

幾年前,我開始嘗試著自學 Android。我發現這種體驗對開發人員來說不太友好:當然,我瞭解它其中一個目標是儘可能減少記憶體佔用,但這是以非常簡潔的 API 為代價的。

我記得當時我必須呼叫帶有很多引數的方法,其中大多數引數為null。在嘗試尋找到一種方法來解決這個問題時,找到了 Kotlin 的擴充套件屬性:帶有預設引數。我後來停止了 Android 的學習,但仍繼續使用 Kotlin。

我喜歡 Kotlin。很多人都稱讚 Kotlin 的 null 安全性(null-safety)實現。但對我來說,我喜歡它,並不是因為它是 null 安全的,而是因為別的。

假設我們經常需要將字串首字母改成大寫。在 Java 中實現這一目的的方法是使用靜態方法建立一個類:

publicclassStringUtils{

publicstatic String capitalize(Stringstring) {
var character =string.substring(0,1).toUpperCase();
var rest =string.substring(1,string.length() -1).toLowerCase();
returncharacter + rest;
}
}

在早期,每個專案幾乎都具有 StringUtils 和 DateUtils 類。幸運的是,現有的庫提供了最常用的功能,例如Apache Commons Lang和Guava。然而,它們仍遵循相同的設計原則,即遵循基於靜態方法的設計原則。這很糟糕,因為 Java 被認為是一種面嚮物件語言。不幸的是,靜態方法不是面向物件的。

擴充套件函式和屬性的幫助下,Kotlin 允許將行為、狀態分別新增到現有的類中。語法非常簡單,並且與面向物件的方法完全相容:

funString.capitalize(): String {
valcharacter = substring(0,1).toUpperCase()
valrest = substring(1, length -1).toLowerCase()
returncharacter + rest
}

在編寫 Kotlin程式碼時,我經常使用這個。

在底層,Kotlin 編譯器生成與 Java 程式碼類似的位元組碼。這僅僅是語法糖,但是從設計的角度來看,與 Java 程式碼相比,它是一個巨大的改進!

Go:隱式介面實現

在大多數面嚮物件語言(Java、Scala、Kotlin 等)中,類可以實現一個契約(也稱為介面)。這樣,客戶端程式碼可以引用該介面,而無需關心任何特定的實現。

publicinterface Shape {

floatarea();
floatperimeter();

defaultvoiddisplay(){
System.out.println(this);
System.out.println(perimeter());
System.out.println(area());
}
}
publicclassRectangleimplementsShape{

publicfinalfloatwidth;
publicfinalfloatheight;

publicRectangle(floatwidth,floatheight){
this.width=width;
this.height=height;
}

@Override
publicfloatarea(){
returnwidth*height;//(1)
}

@Override
publicfloatperimeter(){
return2*width+2*height;//(1)
}

publicstaticvoidmain(String... args){
varrect=newRectangle(2.0f,3.0f);
rect.display();
}
}

(1)處為了精確起見,應該使用 BigDecimal ,但這不是重點

重點是:由於 Rectangle 實現了 Shape,所以可以在 Rectangle 的任何例項上呼叫在 Shape 上定義的 display() 方法。

Go 不是一種面嚮物件語言:它沒有類的概念。它提供了結構體,並且函式可以與這種結構體相關聯。它還提供了介面,該介面可以使用結構體來實現。

然而,Java 實現介面的方式是顯式的:Rectangle 類宣告它實現了 Shape。相反,Go 的方式是隱式的。實現介面所有函式的結構體隱式地實現了該介面。

這可以轉換為如下程式碼:

packagemain

import(
"fmt"
)

typeshapeinterface{//(1)
area()float32
perimeter()float32
}

typerectanglestruct{//(2)
widthfloat32
heightfloat32
}

func(rect rectangle)area()float32{//(3)
returnrect.width * rect.height
}

func(rect rectangle)perimeter()float32{//(3)
return2* rect.width +2* rect.height
}
funcdisplay(shape shape){//(4)
fmt.Println(shape)
fmt.Println(shape.perimeter())
fmt.Println(shape.area())
}

funcmain(){
rect := rectangle{width:2, height:3}
display(rect)//(5)
}

(1)定義 shape 介面

(2)定義 rectangle 結構體

(3)將兩個 shape 函式新增到 rectangle 中

(4)display() 方法只接收一個 shape 引數

(5)因為 rectangle 實現了 shape 的所有函式,並且由於是隱式實現的,所以 rect 也是一個 shape。因此,呼叫 display() 方法並將 rect 作為引數進行傳遞是完全合法的

Clojure:“依賴型別”

我之前的公司對 Clojure 投入了大量的資金。正因為如此,我努力學習過這門語言,甚至還寫了幾篇文章來總結我對它的理解。

Clojure 深受LISP的啟發。因此,表示式用圓括號括起來,首先執行位於圓括號內部的方法。此外,Clojure 是一種動態型別語言:它們雖然有型別,但沒有宣告。

另一方面,該語言提供了基於契約的程式設計。可以指定前置條件和後置條件:它們在執行時計算。這些條件可以進行型別檢查, 例如,檢查引數是字串還是布林值等?甚至可以進行更進一步地檢查,類似於 _dependent 型別:

在電腦科學和邏輯學中,依賴型別是其定義依賴於某個值的型別。“整數對”是一種型別。由於對值的依賴,“第二個大於第一個的整數對”也是依賴型別。

— 維基百科:https://en.wikipedia.org/wiki/Dependent_type

它在執行時強制執行,因此它不能被真正稱為依賴型別。然而,這是我所接觸過的語言中最接近依賴型別的一種了。

之前,我曾詳細寫過一篇關於依賴型別和基於契約程式設計的文章。

Elixir :模式匹配

一些語言吹噓自己提供了模式匹配的特性。通常,模式匹配可用於計算變數,例如,在 Kotlin 中:

varstatusCode: Int
val errorMessage =when(statusCode) {
401->"Unauthorized"
403->"Forbidden"
500->"Internal Server Error"
else->"Unrecognized Status Code"
}

這個用法是類固醇上(steroids)的 switch 語句。然而,一般來說,模式匹配的應用要廣泛得多。在下面的程式碼片段中,首先檢查常規 HTTP 狀態錯誤碼,如果沒有找到,則預設設成更通用的錯誤資訊

val errorMessage =when{
statusCode== 401 ->"Unauthorized"
statusCode== 403 ->"Forbidden"
statusCode- 400 < 100 ->"Client Error"
statusCode== 500 ->"Internal Server Error"
statusCode- 500 < 100 ->"Server Error"
else->"Unrecognized Status Code"
}

不過,它是有限制的。

Elixir 是一種在 Erlang OTP 上執行的動態型別語言,它將模式匹配提升到了一個全新的水平。Elixir 的模式匹配可用於簡單的變數析構

{a,b, c} = {:hello,"world",42}

a 將被賦值成 :hello,b 被賦值成 “world”,c 被賦值成 42。

它還可以對集合進行更高階的析構:

[head | tail] = [1,2,3]

head 被賦值成 1,tail 被賦值成 [2, 3]。

然而,對於函式過載來說,它甚至更是如此。作為一種函式式語言,Elixir 沒有用於迴圈的關鍵字(for 或 while),迴圈需要使用遞迴來實現。

舉個例子,我們使用遞迴來計算 List 的大小。在 Java 中,這是很容易的,因為有一個 size() 方法,但是 Elixir API 沒有提供這樣的功能。讓我們用如下的虛擬碼來實現該功能,Elixir 也是採用這種遞迴的方法。

publicintlengthOf(List<?>item){
return lengthOf(0,items);
}

privateintlengthOf(intsize, List<?>items){
if(items.isEmpty()) {
return size;
}else{
return lengthOf(size+ 1,items.remove(0));
}
}

幾乎可以將它逐行的轉換成 Elixir:

def length_of(list),do: length_of(0,list)

defp length_of(size,list)do
if[]==listdo
size
else
[_|tail]=list//(1)
length_of(size+ 1,tail)
end
end

(1)變數析構的模式匹配。表頭的值被賦值給 _ 變數,這意味著以後就無法引用它了,因為它沒有用處了。

然而,如前所述,Elixir 模式匹配也適用於函式過載。因此,Elixir 的命名方式將是:

deflist_len(list),do:list_len(0,list)

defplist_len(size, []),do: size//(1)
defplist_len(size,list)do//(2)
[_|tail]=list
list_len(size+ 1,tail)
end

(1)如果列表為空,則呼叫此方法

(2)否則呼叫此函式

注意,模式是按照宣告的順序進行評估的:在上面的程式碼段中, Elixir 首先評估具有空列表的函式,如果不匹配,才評估第二個函式,即列表不為空。如果要以相反的順序宣告函式,則每次都會對非空列表進行匹配操作。

廣州vi設計http://www.maiqicn.com 我的007辦公資源網站https://www.wode007.com

Python:for 推導式

Python 是一種動態型別語言。與 Java 一樣,Python 通過 for 關鍵字提供迴圈功能。下面的程式碼片段迴圈遍歷集合中的所有項,並逐個列印它們。

fornin[1,2,3,4,5]:
print(n)

要在新集合中收集所有項,可以先建立一個空集合,然後在迴圈中新增每個項到空集合中:

numbers = []
fornin[1,2,3,4,5]:
numbers.append(n)
print(numbers)

然而,可以使用一個精美的 Python 特性:for 推導式(for comprehensions)。雖然它與標準迴圈使用相同的 for 關鍵字,但是 for 推導式是一個能獲得相同結果的函式式構造器。

numbers = [nfornin[1,2,3,4,5]]
print(numbers)

上面片段的輸出是 [1, 2, 3, 4, 5] 。

也可以轉換每個項。例如,下面的程式碼段將計算每個項的平方:

numbers = [n **2fornin[1,2,3,4,5]]
print(numbers)

輸出是 [1, 4, 9, 16, 25]。

for 推導式的一個好處是能夠使用條件語句。例如,下面的程式碼片段將只過濾偶數項,然後將其平方:

numbers = [n **2fornin[1,2,3,4,5]ifn %2==0]
print(numbers)

輸出是 [4, 16]。

最後,for 推導式允許使用笛卡爾積。

numbers = [a:nfornin[1,2,3]forain['a','b']]
print(numbers)

它將會輸出 [(‘a’, 1), (‘b’, 1), (‘a’, 2), (‘b’, 2), (‘a’, 3), (‘b’, 3)]。

以上的 for 推導式也被稱為列表推導式(list comprehensions),因為它們是為了建立新的列表而設計的。Map 推導式(Map comprehension)也是非常相似的,目的是為了創造 map。