今天來介紹 Haskell 的基礎語法,我們可以先用 ghci
來試著運行 Haskell 語法看看。
只要在 terminal 輸入
ghci
然後我們就可以利用這個環境來讓我們簡單的使用 Haskell 。
如果要退出 gchi 只要輸入 :q
即可
常見的運算子就跟我們平常使用的程式語言一樣:
2 + 2 -- 4
2 - 2 -- 0
3 * 2 -- 6
3 / 2 -- 1.5
True || False -- True
True && False -- False
5 == 5 -- True
5 /= 2 -- False
5 >= 2 -- True
5 <= 2 -- False
"hello" == "hello" -- True
"he" ++ "llo" -- hello
如果是寫習慣js的讀者可能會想試試看一下我們平常的語法在 Haskell 運行起來會是怎樣,我們把數字跟布林值一起做邏輯運算的話會發生以下問題
0 || True
error:
• No instance for (Num Bool) arising from the literal ‘0’
• In the second argument of ‘(||)’, namely ‘0’
In the expression: True || 0
In an equation for ‘it’: it = True || 0
這裡會告訴我們 ||
運算子並不能傳入 0
,因為 Haskell 是一個強型別語言所以並不會自動地幫我轉換數值的型別。
在 Haskell 宣告變數我們只要 name = expression
就可以了
x = 5
x -- 5
x = 2 + 2
x -- 4
x = "1" ++ "23"
x -- "123"
let
在 Haskell 中是用來宣告區域變數所使用我們可以搭配 in
來限制這個區域變數所在的scope
這邊為了較好示範先寫一個 .hs
檔
這個範例只是為了解釋 scope 的差異,所以對於
::
、show
、do
等等沒看過的東西各位可以先無視xD
a :: Integer
a = 1
b :: Integer
b = 100
main :: IO ()
main = do
let b = 10
do
print ("b = " ++ show b)
let a = 2
in do
print ("a = " ++ show a)
print ("a + b = " ++ show (a + b))
print ("a = " ++ show a)
print ("b = " ++ show b)
print ("a + b = " ++ show (a + b))
從上面的範例我們在最上面宣告了兩個變數a
、b
然後在 main
裏面分別 let b
及 let a
但直得注意的是一個後面有接 in
另一個則沒有。
我們先來看一下這個程式碼的輸出:
"b = 10"
"a = 2"
"a + b = 12"
"a = 1"
"b = 10"
"a + b = 11"
第一個 print
很好理解,在 main
裡面我就先 let b = 10
所以第一個 print
是 10
而不是最外層的100
第二個及第三個 print
因為我們 let a = 2
所以第二個 print
就會是 2
,然後因為還是在 main
裡面所以 a+b
就會是 12
剩下的 print
因為我們離開 let a = 2 in ...
的範圍,所以 a
就會是最外面的 1
但還是在 main
裡面所以 b
依然是 10
那 a + b
就會是 11
在 Haskell 中要宣告一個function 也很簡單只要 name args(看有幾個) = expression
add x y = x + y
add5 x = x + 5
使用上也很簡單只要在 function name 後面加上參數就可以呼叫了
add5 2 -- 7
add 5 2 -- 7
x = 10
add5 x -- 15
在 Haskell 中 function 還有很多很多很多操作,像是 infix
、$
、Currying、Higher-Order Functions ,就留到之後的文章再來好好說明了。
今天的程式碼依然會放到 github
補充
Haskell的type system非常嚴格,不同的type就是不同的type,某個function如果需要Foo type的參數,你就絕對不可能傳一個Bar type的參數進去,別的OO語言或許可以向上轉型再向下轉型來唬弄編譯器(e.g. Java可以寫成這樣(Bar) (Object) foo
),但很可惜的是Haskell是純FP語言,不存在任何OO的特性。
Haskell也不存在自動轉型這種功能,別的語言有int自動轉型成long或是float轉型成double的情況,Haskell可就沒如此親切,Float就是Float、Double就是Double,不同的type湊在一起只會編譯錯誤。
上述例子的0 || True
編譯錯誤訊息寫No instance for (Num Bool) arising from the literal ‘0’
,這該如何解釋?
Haskell的type system非常強大但也因此讓它的錯誤訊息非常難以理解。
首先我們需要知道||
的type為Bool -> Bool -> Bool
,它是一個吃兩個Bool回傳一個Bool的function,再來True
的type是Bool
應該不難理解,那0
的type是什麼?
在GHCi裡可以輸入:type 0
來觀察,它應該會告訴你是Num p => p
,這可以讀作「0的type是p,這個p是Num的instance」,p是type variable,可以想成是Java Generics常見的T、U、V名稱;Num是type class,可以類比OO的interface;instance可以類比OO裡實作某個interface的概念。其實這個0的type還沒確定下來,這個p可能是Int、Double等,實際的type編譯器會從上下文來推敲。
就是這個編譯器推敲type的結論造就上述的錯誤訊息,因為它把p看成是Bool,但Bool並不存在Num Bool的instance關係,所以編譯器才會告訴這你奇怪的錯誤。
非常感謝大大的補充🙏🙏🙏