深入探討 F# 之 Function

栏目: ASP.NET · 发布时间: 6年前

内容简介:F# 身為 function first-first language,最迷人的當然就是 function 部分。.NET Core SDK 2.4.1F# 4.1

F# 身為 function first-first language,最迷人的當然就是 function 部分。

Version

.NET Core SDK 2.4.1

F# 4.1

Syntax

let f x = x + 1
  • 由於 function 也被視為 value,因此同樣使用 let 定義 function
  • f 為 function name, x 為 argument,之間以 space 隔開即可
  • = 右側為 function 定義
  • 由於 pure function 要求要有回傳值,所以 x + 1 將被回傳,不用加上 return
  • 不必使用 ; 結束

Scope

let list1 = [ 1; 2; 3]
let list1 = [] // module : error, function : []
let function1 =
   let list1 = [1; 2; 3]
   let list1 = []
   list1 // []
  • 當 value 名稱相同時,若在 module 會 compile error,若在 function 內則是 後蓋前 ,因此 list1 皆為 []
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
   let list1 = [1; 5; 10]
   x + List.sum list1
  • 若 function 內的 value 與 function 外的 value 名稱相同,則 function 內的 value 會蓋掉 (shadow) 掉 function 外的變數,因此 sumPluslist[1; 5; 10]

雖然 value 相同,F# 會自動啟動 shadow 機制,但實務上不建議重複使用 value 名稱,將造成閱讀上與維護上的困難

Parameter

let f (x : int) = x + 1
  • 亦可在 parameter 加上型別,必須使用 () ,在 parameter 名稱加上 :型別
let f x = x + 1
  • 儘管 paramter 不加上型別,因為 F# 的 Type Inference 機制,compiler 會自動由 function body 推導出 parameter 型別

深入探討 F# 之 Function

只要將滑鼠移動到 parameter,就可看到其型別為 int

F# 的 parameter 雖然不用寫型別,但不代表 F# 沒有型別,而是因為其強悍的 Type Inference 機制,讓我們可以少打點字,閱讀上真的想知道型別,就靠 IDE 工具

實務上建議 parameter 不用寫型別,使用 Type Inference 即可

let f x = (x, x)

若 Type Inference 無法推導出型別,就視為 泛型

深入探討 F# 之 Function

由於 Type Inference 無法由 function body 推導出型別,所以啟動 Automatic Generalization 機制,其中 'a 為自動推導出的 泛型

在 F# 要使用 泛型 ,只要不寫型別,且無法推導出具體型別,就被視為 泛型 ,syntax 比 C# 精簡很多

Function Body

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius
  • 若 function 內的程式碼不只一行時,則換行並加以縮排,不用使用 {}

  • Function 內的 value 的 scope 僅限於 function 內,因此 pi 只有 cylinderVolume 可讀取

C# 程式碼中, {} 佔了不少行數,F# 利用縮排取代 {} ,程式碼顯的更清爽,且輸入 tab 速度也比 {} 還快

Return Value

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius
  • Function 最後一行的 expression 或 value 都視為 return 值,因此回傳值為 length * pi * radius * radius expression
  • Function 最後一行的 expression 或 value 的型別會被推導為 return type,因為 pifloat ,所以 return type 被推導為 float

深入探討 F# 之 Function

只要將滑鼠移動到 function,就可看到其 return type 為 float

C# 程式碼中會到處充滿 return ,F# 很聰明的用最後一行的 value 或 expression 當回傳值,讓程式碼更精簡

let cylinderVolume radius length : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius
  • 亦可為 return type 加上型別,只要在最後加上 :型別
let cylinderVolume (radius : float) (length : float) : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius
  • 亦可為 parameter 與 return type 全部加上型別

實務上建議不用替 parameter 與 return type 加上型別,使用 Type Inference 即可

Calling a Function

let vol = cylinderVolume 2.0 3.0
  • Parameter 不需使用 () ,只要與 function name 用 space 隔開即可
  • Parameter 之間不需要 , ,只需用 space 隔開即可

傳入 parameter 不需 (), ,減少打字時間

Currying

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius
  • 若對 function 只傳入部分 parameter,將回傳一個新的 function,可將剩下的 parameter 繼續傳給新的 function,因此可先將 radius 傳入 cylinderVolume ,產生 smallPipeVolumebigPipeVolume 兩個新的 function
let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2
  • 再傳入 cylinerVolume 剩餘的參數 lengthsmallPipeVolumebigPipeVolume ,即可得到與 cylinderVolume 相同的結果

Q : 為什麼要使用 Currying ?

傳統 function 必須在所有 argument 都準備好後,才可以呼叫 function,且 function 是立即執行。

若使用 currying,可分階段將 argument 傳入 function,並回傳新的 function,直到所有 argument 都具備後,function 才會真正執行。

Q : 實務上何時會使用 Currying ?

  1. Argument 無法一次提供,需要逐次提供時
  2. Function 的某些重要 argument 先由底層 library 提供,並傳回新的 function 給 client,client 只要提供剩下的參數即可

Recursive Function

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
  • 若在 function body 需要呼叫 function 本身,在 function name 前面加上 rec ,表示此為 recursive function

有些演算法的數學,就是使用 recursive 表示,若要改用 loop 改寫反而有難度,若直接用 recursive 表示,不僅能忠實呈現演算法,也比較容易 implement

Function Value

let apply1 (transform : int -> int ) y = transform y
  • FP 的核心概念就是將 function 當成 value 看,稱為 Function Value。
  • 除了與 value 一樣使用 let 定義 function 外,也跟 value 一樣,可以將 function 當成 function 的 parameter,因此 apply 為 function, transformapply1 的 parameter,但 transform 為 function,其 type 為 int -> int ,此為 transfom 的 input 為 int ,output 為 int
  • F# 以 -> 定義 function 的 signature type
let increment x = x + 1
let result1 = apply1 increment 100
  • 因此可定義 increment function,再將 increment 傳入 apply
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 10 20
  • 當 function 有多個 parameter 時,其型別表示為多個 -> 串起來,如 let mul x y = x * y ,則 mul 的型別為 int -> int -> int

Q : 為什麼多 paramter 要以 -> … -> 表示

別忘了 F# 的 Currying,如 mul 相當於以下寫法

let mul2 = mul 2
let mul2x3 = mul2 3

所以多個 parameter 就相當於 1 個 parameter 的 function 連續呼叫多次,因此相當於 -> 串起來多次。

Lambda Expression

let result3 = apply1 (fun x -> x + 1) 100
let result4 = apply2 (fun x y -> x * y ) 10 20
  • Function 的 parameter 可以傳入 function,除了使用 let 先定義好 function 外,也可以直接在 arguemtn 以 unnamed function 或 anonymous function 表示,這就是 Lambda Expression
  • Lambda Expression 以 fun 開頭,使用 -> 取代 =
let increment = fun x -> x + 1 // let increment x = x + 1 is better
let result1 = apply1 increment 100
  • 就語法而言,的確可以 let 配合 fun ,但實務不建議這種寫法,因為 increment 的 parameter 必須由 fun 才能看出,較不直覺
  • 建議 letfun 不要混用,將 fun 用在直接傳入 function 的 argument 即可

Function Composition

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
  • 若有兩個 function,需求是先執行 function1 ,並將 function1 的結果傳入 function2 ,可使用 >> 將兩個 function 組合成新的 function

在數學,我們常常有 fog(x) = f(g(x)),若以 F#,可使用 let fog = g >> f 表示,重點還是 從左到右 ,可讀性更數學更高

Pipelining

let result = 100 |> function1 |> function2
  • 100 傳入 function1 ,並將結果傳入 function2

在 imperative language,我們會寫成 function2(function1(100)) ,只要層數夠多,程式碼可讀性就不高,而且還必須 從右到左 ,但使用 pipelining 之後,無論幾層都很容易閱讀,並且還是 從左到右

Conclusion

  • 本文介紹了 F# 所有的 function 功能,一些看似直覺的如 Currying、Function Composition 與 Pipelineing ….,在 F# 寫法都很直覺,但在大部分非 FP 語言實現都有難度,這就是 F# 可愛的地方

Reference

F# , Functions


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python语言程序设计

Python语言程序设计

(美)Y. Daniel Liang / 机械工业出版社 / 2013-3 / 79.00元

本书保持了Liang博士系列丛书中一贯的、标志性的教与学的哲学:以实例教,由实践学。书中采用了他所提出的已经经过实践检验的“基础先行”的方法,即在定义类之前,首先使用清晰简明的语言介绍基本程序设计概念,如选择语句、循环和函数;在介绍面向对象程序设计和GUI编程之前,首先介绍基本逻辑和程序设计概念。书中除了给出一些以游戏和数学为主的典型实例外,还在每章的开始使用简单的图形给出一两个例子,以激发学生的......一起来看看 《Python语言程序设计》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具