Pemrograman · Bahasa pemrograman
Tutorial Haskell untuk pemula
Jawaban singkat
Haskell adalah bahasa fungsional murni bertipe statis dengan inferensi tipe dan lazy evaluation. Program disusun dari fungsi murni, algebraic data type, dan pattern matching, sedangkan efek samping dilacak lewat tipe seperti IO. Halaman ini membahas cara memasang GHC, tipe, fungsi dan guard, list dan fungsi tingkat tinggi, tipe data, type class, Maybe dan Either, serta satu contoh lengkap.
Apa itu Haskell dan untuk apa dipakai
Haskell adalah bahasa pemrograman fungsional murni dengan sistem tipe statis yang kuat, inferensi tipe, dan lazy evaluation. Haskell dirancang oleh sebuah komite peneliti dan pertama kali didefinisikan pada 1990; standar saat ini adalah Haskell 2010, dan GHC (Glasgow Haskell Compiler) adalah implementasi utamanya. Di Haskell, fungsi tidak memiliki efek samping kecuali tipenya menyatakan demikian, sehingga program lebih mudah dipahami dan diuji.
Haskell dipakai di bidang keuangan dan fintech, compiler dan tool bahasa pemrograman (Pandoc dan linter ShellCheck ditulis dalam Haskell), proyek blockchain, backend web, dan riset. Banyak orang juga mempelajarinya karena gagasannya, seperti algebraic data type, type class, dan fungsi murni, telah memengaruhi Rust, Swift, Scala, Kotlin, dan TypeScript.
Memasang GHC dan menjalankan Hello, world
Installer yang direkomendasikan adalah GHCup, yang memasang GHC, build tool Cabal dan Stack, serta Haskell Language Server untuk editor. Setiap program memiliki action main bertipe IO ().
-- hello.hs
main :: IO ()
main = putStrLn "Hello, world!"runghc hello.hs # run without compiling
# Hello, world!
ghc hello.hs && ./hello # compile to a native executable
ghci # interactive REPL; load a file with :load hello.hsKomentar diawali dengan --. Di GHCi, :type expression (atau :t) menampilkan tipe dari ekspresi apa pun, yang merupakan salah satu cara terbaik untuk belajar.
Nilai, tipe, dan inferensi tipe
Nama diikat sekali dan tidak pernah berubah: tidak ada variabel yang dapat diubah (mutable). Type signature ditulis dengan :: dan bersifat opsional karena compiler dapat menyimpulkannya, tetapi menuliskannya untuk definisi tingkat atas adalah praktik yang baik.
name :: String
name = "Haskell"
year :: Int
year = 1990
ratio :: Double
ratio = fromIntegral year / 1000 -- convert Int to Double explicitly
main :: IO ()
main = do
putStrLn (name ++ " " ++ show year) -- Haskell 1990
print ratio -- 1.99
print (7 `div` 2, 7 `mod` 2) -- (3,1)
print (2 ^ 64 :: Integer) -- 18446744073709551616| Tipe | Contoh | Catatan |
|---|---|---|
Int | 42 | integer berukuran tetap (64-bit dengan GHC di platform umum) |
Integer | 2 ^ 100 | integer dengan presisi arbitrer |
Double | 3.14 | floating point 64-bit |
Bool | True, False | constructor diawali huruf kapital |
Char | 'h' | karakter Unicode |
String | "text" | list dari Char, yaitu [Char]; Data.Text lebih cepat |
show mengubah sebuah nilai menjadi String, dan print adalah putStrLn . show. Angka tidak pernah dikonversi secara implisit; gunakan fromIntegral, round, atau truncate.
Fungsi, guard, dan pattern matching
Pemanggilan fungsi ditulis dengan spasi, bukan tanda kurung: add 2 3. Setiap fungsi menerima satu argumen dan mengembalikan fungsi lain untuk sisanya, fitur yang disebut currying, sehingga add 2 sendiri adalah sebuah fungsi. Pattern matching dan guard menggantikan sebagian besar rangkaian if.
add :: Int -> Int -> Int
add x y = x + y
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n - 1)
grade :: Int -> Char
grade score
| score >= 90 = 'A'
| score >= 70 = 'B'
| otherwise = 'C'
describe :: [a] -> String
describe [] = "empty"
describe [_] = "one item"
describe _ = "many items"
main :: IO ()
main = do
print (add 2 3) -- 5
print (map (add 10) [1, 2, 3]) -- [11,12,13] (partial application)
print (factorial 20) -- 2432902008176640000
print (grade 75) -- 'B'
putStrLn (describe "hi") -- many items
print (if even 4 then "even" else "odd") -- "even"if adalah ekspresi dan selalu memerlukan else. Tidak ada perulangan: pengulangan dilakukan dengan rekursi atau fungsi tingkat tinggi seperti map, filter, dan foldr.
List, tuple, dan fungsi tingkat tinggi
List menyimpan nilai dengan satu tipe dan merupakan koleksi yang paling umum. Tuple mengelompokkan sejumlah nilai tetap yang tipenya boleh berbeda. List comprehension, range, dan operator komposisi fungsi . membuat pengolahan data menjadi ringkas.
import Data.List (sortOn)
main :: IO ()
main = do
let nums = [5, 3, 8, 1] :: [Int]
print (head nums, length nums, sum nums) -- (5,4,17)
print (0 : nums) -- [0,5,3,8,1] (prepend)
print (filter even nums) -- [8]
print (foldr (+) 0 [1 .. 10]) -- 55
print [x * x | x <- [1 .. 5], odd x] -- [1,9,25]
print (take 5 [1, 3 ..]) -- [1,3,5,7,9] (lazy infinite list)
let people = [("Ann", 31), ("Bo", 25)] :: [(String, Int)]
print (map fst (sortOn snd people)) -- ["Bo","Ann"]
print ((length . filter (> 2)) nums) -- 3
print (zip [1 :: Int ..] "abc") -- [(1,'a'),(2,'b'),(3,'c')]Karena Haskell bersifat lazy, nilai dihitung hanya saat diperlukan. Itulah sebabnya take 5 [1, 3 ..] bekerja pada list tak hingga. Kelambanan ini juga bisa menumpuk ekspresi besar yang belum dievaluasi; pilih foldl' dari Data.List daripada foldl untuk menjumlahkan list yang panjang.
Algebraic data type dan type class
Anda mendefinisikan tipe sendiri dengan data. Sebuah tipe dapat punya beberapa constructor (sum type), masing-masing dengan field (product type). Type class seperti Show, Eq, Ord, dan Functor menggambarkan perilaku bersama, mirip interface; deriving membuat instance yang umum secara otomatis.
data Shape = Circle Double | Rect Double Double
deriving (Show, Eq)
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rect w h) = w * h
data User = User { userName :: String, userAge :: Int }
deriving Show
class Describable a where
describe :: a -> String
instance Describable Shape where
describe (Circle _) = "a circle"
describe (Rect _ _) = "a rectangle"
main :: IO ()
main = do
print (map area [Circle 1, Rect 2 3]) -- [3.141592653589793,6.0]
putStrLn (describe (Rect 1 1)) -- a rectangle
let u = User { userName = "Ann", userAge = 31 }
print (u { userAge = 32 }) -- User {userName = "Ann", userAge = 32}Maybe, Either, dan IO
Haskell tidak punya null dan tidak punya exception tanpa pemeriksaan di kode murni. Nilai yang mungkin tidak ada bertipe Maybe a (Just x atau Nothing), dan komputasi yang bisa gagal beserta alasannya bertipe Either e a (Left err atau Right x). Efek samping seperti membaca input berada dalam tipe IO, dan notasi do mengurutkannya.
import Text.Read (readMaybe)
safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "division by zero"
safeDiv x y = Right (x `div` y)
parseAge :: String -> Maybe Int
parseAge s = case readMaybe s of
Just n | n >= 0 -> Just n
_ -> Nothing
main :: IO ()
main = do
print (safeDiv 10 2) -- Right 5
print (safeDiv 1 0) -- Left "division by zero"
print (parseAge "42") -- Just 42
print (parseAge "abc") -- Nothing
print (fmap (+ 1) (Just 41)) -- Just 42
putStrLn "Your name?"
n <- getLine -- an IO action that returns a String
putStrLn ("Hi, " ++ n)Program lengkap kecil: frekuensi kata
Program ini membaca teks dari standard input, menghitung frekuensi kata dengan Map, dan mencetak tiga kata terbanyak. Program ini memakai komposisi fungsi, Data.Map dari paket containers yang disertakan bersama GHC, dan pengurutan.
import Data.Char (isAlpha, toLower)
import Data.List (sortBy)
import Data.Ord (comparing, Down (..))
import qualified Data.Map.Strict as Map
wordFreq :: String -> [(String, Int)]
wordFreq =
sortBy (comparing (Down . snd) <> comparing fst)
. Map.toList
. Map.fromListWith (+)
. map (\w -> (w, 1))
. words
. map (\c -> if isAlpha c then toLower c else ' ')
main :: IO ()
main = do
text <- getContents
mapM_ (\(w, n) -> putStrLn (w ++ ": " ++ show n)) (take 3 (wordFreq text))echo "The cat and the dog. The dog sat, and the cat ran." | runghc wordfreq.hs
# the: 4
# and: 2
# cat: 2Langkah selanjutnya
- Halaman dokumentasi di haskell.org, yang menaut ke tutorial, buku, dan GHC User's Guide.
- Hackage, repositori paket, dan Hoogle, mesin pencari yang menemukan fungsi berdasarkan nama atau tipe.
- Learn You a Haskell for Great Good! dan Real World Haskell, dua buku yang bisa dibaca gratis.
- Haskell 2010 Language Report untuk definisi bahasa yang presisi.
FAQ
Apakah Haskell sulit dipelajari?
Terasa berbeda karena tidak ada variabel mutable maupun perulangan, dan memakai sistem tipe yang ekspresif. Pemula yang mempelajari rekursi, pattern matching, dan tipe selangkah demi selangkah biasanya menemukan bahwa bahasa intinya kecil dan konsisten.
Apa arti fungsional murni?
Fungsi selalu mengembalikan hasil yang sama untuk input yang sama dan tidak mengubah state atau melakukan input dan output. Efek tetap dimungkinkan, tetapi dinyatakan dalam tipe seperti IO.
Apa itu monad di Haskell?
Monad adalah type class untuk tipe seperti Maybe, Either, dan IO yang mendukung perangkaian komputasi dengan >>=. Notasi do adalah sintaks untuk perangkaian itu.
Apa itu lazy evaluation?
Ekspresi dievaluasi hanya ketika nilainya diperlukan. Ini memungkinkan list tak hingga dan menghindari pekerjaan yang tidak perlu, tetapi dapat membuat penggunaan memori lebih sulit diprediksi.
Sebaiknya pakai Stack atau Cabal?
Keduanya membangun proyek Haskell dan keduanya dipasang oleh GHCup. Cabal adalah build tool standar; Stack menambahkan snapshot paket yang dikurasi. Keduanya cocok untuk pemula.