iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0
Software Development

Haskell 從入門到放棄系列 第 3

[Haskell 從入門到放棄] Day 03 - 基礎語法

  • 分享至 

  • xImage
  •  

今天來介紹 Haskell 的基礎語法,我們可以先用 ghci 來試著運行 Haskell 語法看看。

只要在 terminal 輸入

ghci

然後我們就可以利用這個環境來讓我們簡單的使用 Haskell 。

https://ithelp.ithome.com.tw/upload/images/20230915/20159893rU8mo0Ngcm.png

如果要退出 gchi 只要輸入 :q 即可

https://ithelp.ithome.com.tw/upload/images/20230915/20159893CfCfEr2Rrk.png

基本語法

常見的運算子就跟我們平常使用的程式語言一樣:

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"

scope

let 在 Haskell 中是用來宣告區域變數所使用我們可以搭配 in 來限制這個區域變數所在的scope

這邊為了較好示範先寫一個 .hs

這個範例只是為了解釋 scope 的差異,所以對於::showdo 等等沒看過的東西各位可以先無視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))

從上面的範例我們在最上面宣告了兩個變數ab 然後在 main 裏面分別 let blet a 但直得注意的是一個後面有接 in 另一個則沒有。

我們先來看一下這個程式碼的輸出:

"b = 10"
"a = 2"
"a + b = 12"
"a = 1"
"b = 10"
"a + b = 11"

第一個 print 很好理解,在 main 裡面我就先 let b = 10 所以第一個 print10 而不是最外層的100

第二個及第三個 print 因為我們 let a = 2 所以第二個 print 就會是 2 ,然後因為還是在 main 裡面所以 a+b 就會是 12

剩下的 print 因為我們離開 let a = 2 in ... 的範圍,所以 a 就會是最外面的 1 但還是在 main 裡面所以 b 依然是 10a + b 就會是 11

簡單的 function

在 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 從入門到放棄] Day 02 - 開發環境
下一篇
[Haskell 從入門到放棄] Day04 - 簡簡單單的List
系列文
Haskell 從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
shootingstar
iT邦新手 4 級 ‧ 2023-09-16 14:23:22

補充

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關係,所以編譯器才會告訴這你奇怪的錯誤。

非常感謝大大的補充🙏🙏🙏

我要留言

立即登入留言