F#在2005年就已經發布第一版,2007年時從研究室移出變成產品部
是Dotnet平台上唯一一款函數導向(Functional Programming)的語言
什麼是函數導向?
跟物件導向就像是物件導向的另一面(不是對面的那種面,比較像是魔術方塊的另外一面)
物件導向的語言強調程式是由一堆物件構築,而函式導向則認為程式由一堆函式所構築,並且強調變數的不可變性
另外一項特點則是函式可以當作一個另一個函式的輸入值或輸出值
那這麼寫有什麼好處嗎?
這麼做的好處是用於數學計算,由於數學面的計算大多都是由各種計算組成
因此相較於將他們轉成物件導向的程式,轉成函數導向的程式會相對輕鬆
所以F#的主要用途會在數學及科學領域
但是F#也不是完全的函數導向語言,你也可以在F#裡面使用物件導向的寫法
讓程式變得更具有彈性,也比其他純函數語言更為易學
同時由於F#生長於Dotnet平台,因此你也可以將需要使用函數語言的方法寫在F#,而其餘的部份寫在C#
這樣也能看到微軟發展Dotnet平台的好處,不必要求一個語言面面俱到,而是將不同的需求使用不同的語言寫成
現在,讓我們開始吧(你可以使用docker或是之前玩C#時安裝的dotnet core)
F#跟C#一樣可以使用dotnet的指令建立專案,只是dotnet預設的語言是C#
因此如果要使用F#來開發需要做特別的指令
dotnet new console -lang "F#" -n convert
建立專案
-lang可以指定語言
然後在你的Program.fs貼上以下的程式碼吧
open System
let returnAE input:string =
if input<10 then (string input)
else
match input with
| 10 -> "A"
| 11 -> "B"
| 12 -> "C"
| 13 -> "D"
| 14 -> "E"
| 15 -> "F"
|_ -> ""
let t2h input:string =
let mutable output = ""
let num = (float input)
for i = 0 to 15 do
for j = 0 to 15 do
for k = 0 to 15 do
if Math.Pow(16.0,2.0)*(float i)+Math.Pow(16.0,1.0)*(float j)+Math.Pow(16.0,0.0)*(float k) = num then output <- (returnAE i)+(returnAE j)+(returnAE k)
output
let AEreturn input =
match input with
| 'A' -> 10
| 'B' -> 11
| 'C' -> 12
| 'D' -> 13
| 'E' -> 14
| 'F' -> 15
| _ -> (int input - int '0') //注意char轉型int
let h2t (input:string):int =
let mutable output = 0
let mutable i = 0
while i < input.Length do
output <- output + (AEreturn input.[i])*int(Math.Pow(16.0,(float (input.Length-i-1))))
i <- i+1
output
[<EntryPoint>]
let main argv =
printfn "Tell me what you want to do:"
printfn "(1)T to H (2)H to T"
let choose = Console.ReadLine();
match choose with
| "1" ->
printfn "Please enter the number:"
let input = Console.ReadLine();
printfn "%s" (t2h input)
| "2" ->
printfn "Please enter the number:"
let input = Console.ReadLine();
printfn "%A" (h2t input)
| _ ->
printfn "Wrong selection"
0
執行方式跟C#一樣使用
dotnet run
F#行尾不需要;
方法之類的也不需要{}
雖然F#接近直譯式語言,不管是寫法上還是結構上
但還是有需要註明程式進入點的地方
這個註明方式就是
[<EntryPoint>]
這個註明方式有別種特殊用法,詳細可以看這裡
往下看看主程式在做什麼吧
let main argv =
printfn "Tell me what you want to do:"
printfn "(1)T to H (2)H to T"
let choose = Console.ReadLine();
match choose with
| "1" ->
printfn "Please enter the number:"
let input = Console.ReadLine();
printfn "%s" (t2h input)
| "2" ->
printfn "Please enter the number:"
let input = Console.ReadLine();
printfn "%A" (h2t input)
| _ ->
printfn "Wrong selection"
0
跟C++一樣,主程式具有回傳值
在F#裡面回傳值不需要return,只需要放在最後一行,他自己就會回傳了
這裡如果程式沒出錯的話我們就回傳0
開頭的argv則是程式執行時帶入的輸入值,我們這邊不會用到,但是可以去查一下怎麼用
printfn "Tell me what you want to do:"
printfn "(1)T to H (2)H to T"
let choose = Console.ReadLine();
輸出採用printfn,不用(),但還是需要用""讓F#知道裡面是字串而不是變數
輸入則採用dotnet平台提供的Console.ReadLine(),所以跟C#一樣
在F#中沒有傳統的switch,而是使用模式比對match做代替
語法長這樣
match 輸入的參數 with
| 模式1 ->
符合模式1則執行
| 模式2 ->
符合模式2則執行
| _ ->
若都沒有符合則執行
看起來跟傳統的switch沒什麼兩樣嘛
理由在於我們只需要進行值的比對,模式比對讓你可以使用下面的方式來寫
let foundX input =
match input with
| ("X", "X") -> printfn "Found two X"
| (var1, var2) & ("X", _) -> printfn "First value is X"
| (var1, var2) & (_, "X") -> printfn "Second value is X"
| _ -> printfn "None X here"
相對於傳統switch這種寫法更具有比對的特性
詳細的用法可以看這邊
比對完成後將執行後續的程式
printfn "Please enter the number:"
let input = Console.ReadLine();
printfn "%s" (t2h input)
前面兩行同樣再問一次問題,並且取得要轉換的值
最後一行的printfn則是將後面的值格式化後輸出
結構長這樣
printfn "結構語句" (要填入填充字元的變數)
使用的填充字元為%s而不是C#的{0}
注意後面的方法使用方式
t2h input
使用方法時填入的參數不需要使用(),而是直接放在後面
我們往上一點點看到h2t這個方法吧
let h2t (input:string):int =
let mutable output = 0
let mutable i = 0
while i < input.Length do
//printfn "%A" (AEreturn input.[i])
output <- output + (AEreturn input.[i])*int(Math.Pow(16.0,(float (input.Length-i-1))))
i <- i+1
output
這種寫法比較接近於傳統意義上的方法寫法,先來看看結構吧
let 方法名稱 (輸入值:輸入值型別):回傳值型別 =
方法本體
回傳值 //最後一行
F#的方法本體並不需要使用{}將他們包起來,而是使用tab確認方法的範圍
這裡的方法宣告跟變數宣告都是使用let這個關鍵字
方法名稱為h2t,輸入值的變數名稱為input,型別為string
而回傳值型別為int,回傳可以不用return這個關鍵字,直接把要回傳的值放在最後一行即可
F#是一個具有自動推斷型別能力的強型別語言
因此你可以使用let讓F#自動推斷變數的型別
要注意的是F#的變數預設是沒辦法改變值的,因此如果你需要累加就要使用mutable這個關鍵字
F#的while內的條件可以不使用()包起來(如果你需要當然也是可以)
結構為
while 條件 do
同樣不需要使用{}
內部的程式碼
output <- output + (AEreturn input.[i])*int(Math.Pow(16.0,(float (input.Length-i-1))))
並沒有如往常使用
output = output + ~~~
的原因為=在非宣告時其實視為一般常見的比較運算子==
理由是因為F#的變數預設為不可變動,因此也沒有必要留下 = 讓你做變數值變更
雖然編譯時還是會通過,但是意思已經跟原本要表達的意思完全不同了
如果我們需要對可變動的值做變更,就需要<-
F#的陣列使用
input.[i]
作為取值的方式
而不是如同往常使用
input[i]
我們往上看到用來將十六進位轉成十進位的AEreturn吧
let AEreturn input =
match input with
| 'A' -> 10
| 'B' -> 11
| 'C' -> 12
| 'D' -> 13
| 'E' -> 14
| 'F' -> 15
| _ -> (int input - int '0') //注意char轉型int
這裡為什麼沒有return?
這就是函數語言跟物件語言最不同的地方了
函數語言專注於計算的過程跟結果,而不是回傳了什麼
就像是數學函式
f(x) = x + 1
你會關心f(x)會回傳了什麼給你嗎?
不會,因為你只關心你自己因為你只需要專注於算了什麼跟算出的結果
因此在F#會這樣寫
let f x = x + 1
這樣是不是跟傳統的函式寫法很像?
f是方法名稱,x 則是輸入值,x+1則是方法的函式
(所以不是看成 x = x +1 喔, 應該是 f = x +1, x為輸入值)
重點在於x+1這件事情上面
也因此F#不限訂你一定要為輸出值加上型別(注意x後面並沒有:int)
好,說了這麼多我們回來看這個方法的方法本體
match input with
| 'A' -> 10
| 'B' -> 11
| 'C' -> 12
| 'D' -> 13
| 'E' -> 14
| 'F' -> 15
| _ -> (int input - int '0') //注意char轉型int
這裡雖然我們把input拿去比對,之後進行後續的動作
但是我們換個角度想,如果輸入值為'A'這個方法會如何執行?
let AEreturn input =
// match input with
| 'A' -> 10 //直接進到這裡
// | 'B' -> 11
// | 'C' -> 12
// | 'D' -> 13
// | 'E' -> 14
// | 'F' -> 15
// | _ -> (int input - int '0') //注意char轉型int
也就是
let AEreturn input = 10 //不是input=10, 而是AEreturn=10 輸入值為input
看懂了嗎?
沒看懂也沒關係,畢竟我們以前都寫物件導向現在寫函式導向會比較難懂
所以
別試著理解他,感受他
因此相對應的returnAE也是類似的寫法
let returnAE input:string =
if input<10 then (string input)
else
match input with
| 10 -> "A"
| 11 -> "B"
| 12 -> "C"
| 13 -> "D"
| 14 -> "E"
| 15 -> "F"
|_ -> ""
最後我們看回AEreturn的這段
| _ -> (int input - int '0') //注意char轉型int
比對沒有結果的值將會使用我們之前在C++做過的將字元內的byte相減的作法,
最終得到轉型的結果(注意不是轉型,只是從字元算出相對應的數字)
裡面的//則是單行註解,跟C#相同
多行註解則是
(*
註解
註解
註解
*)
跟C#稍微有點不同
我們看到最後一個沒講過的方法這吧
let t2h input:string =
let mutable output = ""
let num = (float input)
for i = 0 to 15 do
for j = 0 to 15 do
for k = 0 to 15 do
if Math.Pow(16.0,2.0)*(float i)+Math.Pow(16.0,1.0)*(float j)+Math.Pow(16.0,0.0)*(float k) = num then output <- (returnAE i)+(returnAE j)+(returnAE k)
output
for的寫法相當簡單易懂
for 變數=起始值to結束值 do
不需要使用()將迴圈值包住,也不用{}將執行內容給包住
我們來看看超級長的這段程式碼吧
if Math.Pow(16.0,2.0)*(float i)+Math.Pow(16.0,1.0)*(float j)+Math.Pow(16.0,0.0)*(float k) = num then output <- (returnAE i)+(returnAE j)+(returnAE k)
我們將他拆解成兩部份,條件句及執行句
條件句長這樣
Math.Pow(16.0,2.0)*(float i)+Math.Pow(16.0,1.0)*(float j)+Math.Pow(16.0,0.0)*(float k) = num
注意到了嗎?
如同我們剛才說的由於變數的不可變動性,我們不需要保留=給變數的指定值,因此這邊的=反而是我們往常見到的==
裡面的Math.Pow是dotnet平台上的寫法,F#你其實可以這樣寫
16.0**2.0*(float i)
計算的數值需要加上.0是因為要讓F#自動判定成float型別,才能進行冪次的計算
執行句的
then output <- (returnAE i)+(returnAE j)+(returnAE k)
則表示將後面算出的三個字串相加後丟入output這個變數裡頭
還記得方法開頭時宣告output為mutable吧
最終,我們將output放在方法的最後一行當作回傳值,完成這個方法
我們來複習一下吧
F#在台灣使用的人真的相當少,情況大概只比Raku好一點
排名甚至在TOBIE上排不進前50(2020.08.30)
而且又是FP(Functional Programming)的概念,因此觀念的部份可能會有寫錯的地方,請不吝糾正
不過寫起來相當舒適,如果用數學函式的方式來看這門語言會覺得相當易寫
或許對於非學界來說用途並不在於寫程式,而是用來當作C#的函式庫來使用
算是MicroSoft正在下的一盤大棋
最後兩門語言我們來介紹google也在下的棋吧
先介紹排名跟F#一樣比較後面的kotlin
如果有任何寫不清楚或是觀念沒有很明白的話請留言告知我
會盡快補上
如果有任何寫錯的地方也麻煩留言告知我
會盡快修正
感謝各位