6 1 2 自定義運算子

2021-05-25 06:45:54 字數 2801 閱讀 1493

6.1.2 自定義運算子

定義自定義的運算子的方式類似於函式,使用 let 繫結。它們可以使用任何字元,可以是通常的 f# 數**算符(+/-*<>),或者是邏輯運算子(& | =),還可以是其他字元 ($%.?@^~!)。宣告乙個運算子,要把它的名字括在括號中,這是與通常的 let 繫結的唯一區別。使用星號時要小心,因為,(* 用於 f# 多行注釋的開始。在這種情況下,解決方案是在星號與括號之間加上空格。清單 6.2 顯示了如何宣告和使用乙個簡單的運算子來處理字串。

listing 6.2 working with strings using a custom operator (f# interactive)

> let (+>) a b = a + "/n>> " + b;;

val ( +> ) : string -> string –> string

> printfn ">> %s" ("hello world!" +>

"how are you today?" +>

"i'm fine!");;

>> hello world!

>> how are you today?

>> i'm fine!

使用自定義的運算子而不用函式的好處,是可以使用中綴表示法(infix notation)。這意味著,對於 concat "a" (concat "b" "c"),可以寫成 "a"+>"b"+>"c"。這在多次應用運算子時是特別有用的,就像我們前面的示例,因為,不必把每個呼叫括在括號中。

在清單 6.2 中,我們宣告一胩中綴運算子,它取兩個引數。f# 也允許定義一元運算子,只取乙個引數,用於字首表示法(prefix notation)。內建字首運算子的乙個示例是一元負,寫成為 -1。這種運算子不基於宣告中引數的數目,因為,它可能是不明確的。這有點棘手,但是,可以寫一元運算子,返回乙個函式,那麼純粹基於這個型別簽名,它看起來像二元運算子(由於科里化,我們在第 5 章討論的)。字首和綴運算子之間的這種區別是基於第乙個符號。當定義字首運算子,必須以 ~ 或 !符號開始。

在 c# 中模擬自定義運算子

在 c# 中,不能宣告新的運算子,雖然可以過載現有的運算子。然而,在某種程度上,使用擴充套件方法,可以實現相同的模式。這是 c# 3.0 中的乙個新功能,下面,我們簡要介紹一下。

擴充套件方法

在 c# 中,每個方法必須被包裝在乙個類中,處理物件的操作是類宣告的一部分,可以使用點表示法來呼叫方法。擴充套件方法給我們一種新增新方法的方式,來處理物件,而無需修改原始的類宣告。以前,這可能要通過寫靜態方法才行,就像這樣:

stringutils.reverse(str);

這是很不切實際的,因為,在某個 「utils」 類中找到乙個靜態方法是相當困難的。c# 3.0 中,我們可以實現 reverse,作為擴充套件方法,以這種方式呼叫:

str.reverse();

實現擴充套件方法是很容易的,因為,它是普通的靜態方法,有乙個特別的修飾符。唯一的區別,是它可以作為例項方法來呼叫,使用點表示法。它仍然是乙個靜態方法,所以,它既不能新增新字段,也不能訪問物件的私有狀態:

static class stringutils

}所有擴充套件方法都必須括在非巢狀的靜態類中,它們必須是靜態方法。修飾符 this 放在第乙個引數之前,告訴編譯器為它是擴充套件方法。

如果我們把前面示例中字串連線實現為擴充套件方法,得到的語法會非常類似於原始的 f# 版本。清單 6.3 顯示了使用標準的靜態方法呼叫和擴充套件方法,寫相同的**。

listing 6.3 working with strings using extension methods (c#)

public static string addline(this string str, string next)

console.writeline(

stringutils.addline(

stringutils.addline("hello world!", "how are you today"),

"i'm fine!"));

console.writeline("hello world!"

.addline("how are you today")

.addline("i'm fine!"));

好處是純粹的在可讀性方面:我們能夠 寫該方法呼叫,與我們希望其發生的順序相同,而不需要指定實現這個方法的類,也不需要額外的大括號。就像這樣的情況 ,語法產生乙個非常重要的差異。

f# 的流運算子

流水線的運算子(|>) 允許我們在函式的左側寫出第乙個引數——即,在該函式名字前。這是非常有用的,如果我們想要呼叫幾個處理函式,用序列中的某個值,我們想先寫出要處理的值。下面的示例演示如何在 f# 中反轉列表,然後取第乙個元素:

list.hd(list.rev [1 .. 5])

這並不是非常優雅的,因為,寫的操作順序與執行順序相反,將要處理的值在右側,括在括號中。在 c# 中,使用擴充套件方法,我們會寫成:

list.reverse().head();

在 f# 中,可以通過使用流運算子,來得到相同的結果:

[1...5] |> list.rev |> list.hd

儘管這看起來可能非常棘手,但是,運算子是非常簡單的。它具有兩個引數值:第二個(右側)是乙個函式,第乙個(左側)是乙個值。運算子把值作為這個函式的引數值,並返回結果。

在某種意義上,流是類似於呼叫方法,在物件是使用點表示法,但是,它並不侷限於內在的物件的方法。這是類似於擴充套件方法,所以,當我們寫了乙個通常用的流運算子的 f# 函式的 c# 替代方案時,我們將它作為擴充套件方法實現。

現在,我們已經完成了對泛型高階函式和運算子的簡短說明,終於可以看看如何使用它們解決日常函式程式設計問題。我們將討論的第乙個主題是使用高階函式元組。

6 1 2 自定義運算子

6.1.2 自定義運算子 定義自定義的運算子的方式類似於函式,使用 let 繫結。它們可以使用任何字元,可以是通常的 f 數 算符 或者是邏輯運算子 還可以是其他字元 宣告乙個運算子,要把它的名字括在括號中,這是與通常的 let 繫結的唯一區別。使用星號時要小心,因為,用於 f 多行注釋的開始。在這...

scala自定義運算子

通過隱式轉換來實現自定義運算子 案例 定義運算子 使得num1 num2可以獲取到對偶,兩個元素分別為 的結果和 的結果 當然,這個 運算子在bigint中已經實現了,這裡在int中將其實現一次 當使用int呼叫乙個int中不存在的方法的時候,就會來這個類中尋找該方法 implicit class ...

swift 自定義運算子

除了實現標準運算子,在swift當中還可以宣告和實現自定義運算子 custom operators 新的運算子要在全域性作用域內,使用operator 關鍵字進行宣告,同時還要指定prefix infix或者 postfix限定符 code a uikit based playground for ...