12 6 實現選項的計算表示式

2021-06-28 12:55:34 字數 2643 閱讀 1666

12.6 實現選項的計算表示式

在 12.4 節,我們用選項值作為示例,介紹了用 linq 查詢和 f# 計算表示式建立非標準計算的概念,處理選項值的**,有自定義的值繫結讀取實際值,如同標準值。既然我們已經知道如何轉換計算表示式,也就知道我們的 bind 成員會接收值和 lambda 函式。因為我們處理的是選項型別計算表示式,只有當值是 some(x) 而不是 none 時,我們才打算執行 lambda 表示式;後一種情況,我們可以立即返回 none。

要執行前面的例子,我們需要在 c# 中實現 linq 查詢運算子,在 f# 中實現選項計算生成器。同樣,我們先看 f# 版本,清單 12.21 是有兩個成員的 f# 物件型別。我們已經在第六章實現過 option.bind 函式,在這裡要重新實現,提醒一下典型的 bind 操作做什麼。

清單12.21 選項型別的計算生成器 (f#)

type optionbuilder() =

member x.bind(opt, f) =

match opt with       [1]

| some(value) ->f(value)    [2]

| _ –> none    [3]

member x.return(v) = some(v)   <-- 包裝實際值

let option = new optionbuilder()

return 成員有乙個引數值,必須返回計算型別的值。在我們的示例中,計算型別是 option<'a>,所以,把實際值包裝到 some 識別器內。

為了在 c# 中使用查詢語法寫對應的**,就需要到在第五章為定義 option型別實現的 select 和 selectmany 方法。清單 12.22 實現了兩個額外的擴充套件方法,這樣,就可以在查詢表示式中使用選項了,這次,我們使用在第六章中寫的擴充套件方法,使**更加簡單。

清單12.22 選項型別的查詢操作 (c#)

static class optionextensions {

public static optionselect

(thisoptionsource, funcselector) {

returnsource.map(selector);    [1]

public static optionselectmany

(thisoptionsource,

func> valueselector,

funcresultselector) {

returnsource.bind(sourcevalue =>    [2]

valueselector(sourcevalue).map(resultvalue =>    [3]

resultselector(sourcevalue, resultvalue)));

如果給定選項值包含實際值,select 方法應該把給定的函式應用到所攜帶的值上,然後,再把結果打包到選項型別中。在 f# 中,函式稱為 option.map,c# 方法也使用類似的名字(map)。如果我們是先看到 linq 的話,那麼,首先可能會把方法稱為select,但是,最簡單的解決方案是新增新的稱為map 方法[1]。

selectmany 會更複雜,它類似於 bind 操作,但是,此外需要使用由第三個引數,指定額外的函式,格式化操作的結果。在第六章,我們用c# 寫過 bind 操作;在這裡,我們可以使用 bind 擴充套件方法[2]。要呼叫格式化函式 resultselector,需要兩個引數:乙個是由選項攜帶原始值,另乙個是由繫結函式(命名為 selector)產生的值。在處理的末尾,我們可以新增對 map 的呼叫執行此操作,但是,需要把這個呼叫放在 lambda 函式內部,給 bind 方法[3],這是因為我們還需要訪問來自源的原始值。在 lambda 函式內部,原始值是作用域之內(名為 sourcevalue 的變數),因此,我們可以把它和新值,即,分配給變數 resultvalue,放在一起使用。

這個實現是有點複雜,但它表明,使用函式式程式設計,可以把很多已有的功能組合起來。如果我們試圖自己實現的話,在這裡,可以看到型別是寶貴的助手。可能首先使用 bind 方法,但是,可能看到型別不匹配。會看到什麼型別不相容,如果看過哪些函式可用,你會發現,為了獲得正確的型別,需要新增什麼。我們自己重複的風險在於:函式式程式設計中的型別要重要得多,告訴你更多有關程式的正確性問題。

使用新的擴充套件方法,我們可以執行 12.3 節中的示例。在 f# 中,我們不需要實現 yield 和 for 基本操作,只使用 return 和 let! 就能執行。這是故意的,因為這些第一組基本操作更適合處理各種形式序列的計算。我們仍然需要實現 tryreadint 方法(類似於 f# 的函式),這些是簡單的,因為只要從控制台讀取一行,並嘗試解析,然後,當字串是數字時,返回 some,其它則返回 none。

肯定和可能單子

我們前面看到的兩個例子,在 haskell 中是眾所周知的。第乙個示例是肯定單子(identity monad),因為這個單子型別與值的實際型別相同,只打包在命名型別中。第二個示例是可能單(maybe monad),因為maybe 是 haskell 的型別名,對應於 f# 中的 option<'a> 型別。

第乙個示例沒有什麼實際意義,演示了在實現計算時需要做的;而第二個示例在寫組合大量操作的**時,其中每個可能失敗,是有用的。我們分析這兩個例子時,可以發現,單子型別非常重要;一旦我們理解了這種型別,就會知道,是什麼讓計算非標準。

到目前為止,例子已經有點抽象了;下一節,會有很多更具體的示例,在**中新增自動日誌。

12 6 實現選項的計算表示式

12.6 實現選項的計算表示式 在 12.4 節中,我們用選項值作為示例,介紹了用 linq 查詢和 f 的計算表示式建立非標準計算的概念。我們所寫的 處理選項值,有自定義的值繫結來讀取實際值,就好像是乙個標準值。我們已經看到如何轉換計算表示式,知道我們的 bind 成員會接收乙個值,和乙個 lam...

棧實現表示式計算

讓index 1,並盤算是否掃瞄到expression最後 index if index expression.length 掃瞄完畢,就順序從數棧和符號棧中pop出相應的數和符號,並執行 while true num1 numstack.pop num2 numstack.pop oper ope...

棧實現表示式計算

原理 數 入棧 入棧 運算子出棧,直到 和 匹配 運算子 當前符優先順序 棧頂符優先順序 入棧 當前符優先順序 棧頂符優先順序 棧內運算子出棧,運算後進棧,再比較 其中 優先順序大於 如下 先建compute.h標頭檔案 pragma once double compute char str 計算 ...