Skip to content

Instantly share code, notes, and snippets.

@j5ik2o
Last active October 28, 2022 00:08
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save j5ik2o/4334482 to your computer and use it in GitHub Desktop.
Save j5ik2o/4334482 to your computer and use it in GitHub Desktop.

Haskell チートシート

インストール

https://www.haskell.org/platform/mac.html

REPL起動

$ ghci
ghci> 

変数定義

変数とか代入とかないです…。 代わりに束縛があります。

NOTE 束縛=何かへの参照

ghci> let hoge = 1
ghci> hoge
1

型推論は指定された値から型を導く。

ghci> :t hoge
hoge :: Integer

in

letで束縛した値をin句で利用することができる

ghci> :{
ghci| let a = 1
ghci|     b = 2
ghci| in
ghci|     a + b
ghci| :}
3

関数定義

  • :: 以降は型を表すアノテーション
  • Int -> Intでは Int型引数を1個とり、Int型戻り値を返す型を表す。
    • つまり一番最後が戻り値の型で、それ以外は引数の型となる。
hoge :: Int
hoge = 1

hoge :: Int -> Int
hoge fuga = fuga

(Show a) =>は型制約。

この場合はaはShowという型である必要がある。Showにはshow関数があり文字列に変換することができる。

hoge :: (Show a) => a
hoge x = show x

NOTE letではなく関数に変数を束縛する方法もある

REPLでの宣言の仕方

NOTE REPLで関数定義する場合は以下のようにletを使い一行で定義してください

ghci> let hoge Int -> Int; hoge fuga = fuga
ghci> hoge 1
1

関数の呼び出し型

引数がない場合

hoge :: Int
hoge = 1

:t で型情報を調べることができる。

ghci> :t hoge
hoge :: Int

引数が1個の場合

hoge :: Int -> Int
hoge x = x
ghci> :t hoge
hoge :: Int -> Int
ghci> :t hoge 1
hoge 1 :: Int

引数が2つの場合

fuga :: Int -> Int -> Int
fuga x y = x + y
ghci> :t fuga
fuga :: Int -> Int -> Int
ghci> :t fuga 1
fuga 1 :: Int -> Int
ghci> :t fuga 1 2
fuga 1 2 :: Int
ghci> fuga 1 2
3
ghci> (fuga(1) 2)
3

引数は左側に結合します。(左結合)

カリー化と部分適用

ということでこんなことができます。

ghci> let fuga_1 = fuga 1
ghci> fuga_1 2
3

あれ?fuga(1)は関数? はい。そうです。

例えば、2つの引数を持つ関数は、1つの引数をとり、「一つの引数をとる関数」を返す関数と同義です。関数を返すことですべての関数を一つの引数の関数として表現することをカリー化という。

(fuga(1) 2)

本来より少ない引数で呼び出した時に部分適用された関数が得られます。その関数を使って残りの引数を渡せば関数の処理が可能です。

fuga(1)

括弧が外せる$関数

add :: Int -> Int -> Int
add x y = x + y

multi :: Int -> Int -> Int
multi x y = x * 2
ghci> multi 1 (add 2 1)

ghci> multi 1 $ add 2 1

と書くことができます。$は次のとおりになっています。

($) :: (a -> b) -> a -> b
f $ x = f x

データ型

Char 文字です。シングルクオートで囲みます。
Int 範囲が決まった整数です。少なくとも (- 229) --- (229 - 1) をカバーします。
Integer 任意精度の整数
Float IEEE 単精度
Double IEEE 倍精度の浮動小数点
Bool 真偽値です。True と False があります。

NOTE Stringは[Char]のエイリアス

コレクション

リスト

リストの生成

ghci> [1,2,3,4,5]
[1,2,3,4,5]

レンジ指定

ghci> [1..5]
[1,2,3,4,5]

ステップ指定

ghci> [1,3..10]
[1,3,5,7,9]
リストの連結
ghci> [1, 2, 3, 4] ++ [9, 10, 11, 12]
[1,2,3,4,5,9,10,11,12]
ghci> "hello" ++ " " ++ "world"
"hello world!"
ghci> ['w','o'] ++ ['o','t']
"woot"
リストの要素の取得
ghci> [1, 2, 3] !! 2
3
リストの先頭要素の取得
ghci> head [5, 4, 3, 2, 1]
5
リストの先頭要素以外の取得
ghci> tail [5, 4, 3, 2, 1]
[4,3,2,1]
リストの最終要素の取得
ghci> last [5, 4, 3, 2, 1]
1
リストの最終要素以外の取得
ghci> init [5, 4, 3, 2, 1]
[5,4,3,2]
リストのcons演算子

headとtailを結合できる

ghci> 'A' : " B C"
"A B C"
ghci> 1 : [2,3,4,5]
[1,2,3,4,5]
リストの要素数の取得
ghci> length [5, 4, 3, 2, 1]
5
リストの要素が空か判定
ghci> null []
True
ghci> null [1, 2, 3]
False
順序の入替え
ghci> reverse [5, 4, 3, 2, 1]
[1,2,3,4,5]
指定の値を持つ要素が含まれるか判定
ghci> elem 4 [5,  4,  3,  2,  1]
True

中置記法での表現

ghci> 4 `elem` [5,  4,  3,  2,  1]
True
ghci> 4 `elem` [3,  2,  1]
False

記号は``で囲まなくても中置記法できます。

リスト内包表記
ghci> [x * 2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

フィルター条件

ghci> [x * 2 | x <- [1..10], x * 2 >= 12]
[12,14,16,18,20]
ghci> [x | x <- [50..100], x `mod` 7 == 3]
[52,59,66,73,80,87,94]
ghci> boomBangs xs = [if x < 10 then "BOOM" else "BANG!" | x <- xs, odd x]
ghci> boomBangs [7..13]
["BOOM!", "BOOM!", "BANG!", "BANG!"]

複数のリストを利用する

ghci> [ x*y | x <- [2,5,10],  y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]
ghci> [ x*y | x <- [2,5,10],  y <- [8,10,11], x*y > 50]
[55,80,100,110]

タプル

タプルの生成
ghci> (1,2)
(1,2)
ghci> (1,2,3)
(1,2,3)
値の取得
ghci> fst (1, 2)
1
ghci> snd (2, 3)
2

マップ

マップはData.Mapを使う。

import Data.Map
let map = fromList [("Taro", 20), ("Jiro", 17), ("Saburo", 15)]
mapM_ putStrLn [key ++ " => " ++ (show value) | (key, value) <- toList map]

lookupを使えばキーから値を取得できる。値はMaybeでラップされている。

import Prelude hiding (lookup)
import Data.Map
let map = fromList [("Taro", 20), ("Jiro", 17), ("Saburo", 15)]
let value = lookup "Jiro" map -- Just 17

分数

ghci> import Data.Ratio
ghci> 1 % 4 + 2 % 3
11 % 12

複素数

ghci> import Data.Complex
ghci> (3 :+ 1) + (1 :+ 2)
4.0 :+ 3.0

制御構文

if式

  • if式は値を返します
  • elseが必須です( ー`дー´)キリッ
doubleSmallNumber x = if x > 100
                      then x
                      else x * 2

パターンマッチ

引数のパターンにマッチさせて戻り値を返す。

getPosition :: Int -> Maybe String
getPosition 0 = Nothing
getPosition 1 = Just "平社員"
getPosition 2 = Just "係長"
getPosition 3 = Just "課長"
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
asパターン

@を用いて、パターンに名前をつけることができる

firstChar :: String -> String
firstChar "" = ""
firstChar all@(x:xs) = all ++ "'s first char is " ++ [x]
ワイルドカード

_がワイルドカード。

head :: [a] -> a
head (x:_)             = x

tail :: [a] -> [a]
tail (_:xs)            = xs

場合分け

evalBmi :: Double -> String
evalBmi bmi
    | bmi <= 18.5 = "低体重(痩せ型)"
    | bmi <= 25.0 = "普通"
    | bmi <= 30.0 = "肥満(1度)"
    | bmi <= 35.0 = "肥満(2度)"
    | bmi <= 40.0 = "肥満(3度)"    
    | otherwise   = "肥満(4度)"
whereに計算式を束縛できる
evalBmi :: Double -> Double -> String
evalBmi weight height
    | bmi <= 18.5 = "低体重(痩せ型)"
    | bmi <= 25.0 = "普通"
    | bmi <= 30.0 = "肥満(1度)"
    | bmi <= 35.0 = "肥満(2度)"
    | bmi <= 40.0 = "肥満(3度)"    
    | otherwise   = "肥満(4度)"
    where
        bmi = weight / height ^ 2

case式

take m ys = case (m,ys) of
            (0,_)       ->  []
            (_,[])      ->  []
            (n,x:xs)    ->  x : take (n-1) xs

繰り返し処理

再帰で行います。

数値を持つリストを単一の文字列に結合する場合

ghci> listToString [1, 2, 3, 4, 5]
"12345"

これでもよいが、スタックオーバーフローする場合がある…。

listToString :: (Show a) => [a] -> String
listToString [] = ""
listToString (x:xs) = show x ++ listToString xs

末尾再帰版その1

listToString :: (Show a) => [a] -> String
listToString xs = listToString0 xs ""

listToString0 :: (Show a) => [a] -> String -> String
listToString0 [] r = r
listToString0 (x:xs) r = listToString0 xs $ r ++ show x

末尾再帰版その2

関数定義がまとまっていて見やすい。

listToString :: (Show a) => [a] -> String
listToString xs = f xs ""
  where
    f [] r = r
    f (y:ys) r = f ys $ r ++ show y

foldr(畳込み関数)を使う方法

ghci> foldr (\a b -> a ++ b) "" $ map show [1, 2, 3]
"123"
ghci> foldr (++) "" $ map show [1, 2, 3]
"123"

関数定義その2

無名関数の定義方法

通常の関数定義は次のとおり。

add :: Int -> Int
add x = x + 1

これをラムダ式を使って、無名関数として定義する。そして引数を適用する。

ghci> (\x -> x + 1) 1
2

省略形で記述。

ghci> (+1) 1
2

無名関数を束縛する

関数で束縛する。
add :: Int -> Int
add = (\x -> x + 1)
add :: Int -> Int
add = (+1)
ghci> add 1
2
let式で束縛する
hoge = let add = \x -> x + 1
       in add 1

REPLの場合

let add :: Int -> Int ; add = (\x -> x + 1)

関数合成

数学における関数合成がHaskellにも定義されています。

ghci> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

f (g (x))(f . g)(x)で書くことができます。

利用例は次のとおり。

(putStrLn . fizzbuzz) は 関数合成。(\n -> putStrLn $ fizzbuzz n) と等価です。

fizzbuzz :: Int -> String
fizzbuzz n
  | n `mod` 15 == 0 = "fizzbuzz"
  | n `mod` 3 == 0  = "fizz"
  | n `mod` 5 == 0  = "buzz"
  | otherwise       = show n -- showはStringに変換する関数
 
main = mapM (putStrLn . fizzbuzz) [1..100]

遅延評価

Haskellはデフォルトで遅延評価を採用している。他の命令型言語は先行評価だが、Haskellの場合は式が必要になるまで評価されない。Haskellの関数は数学的な関数であり、このような関数はいつ呼び出しても引数に対して戻り値の結果が変わらない特性を持っている。なので必要になるまで評価しないのは、実行時の性能を最適化するのに良い方法といえる。

answer = const 42 (1 `div` 0)

constは常に第一引数を返すので、(1 `div` 0)は評価されないのでエラーにならない。

代数的データ構造

Haskellには構造体や列挙型の性質を兼ね備えたデータ構造を簡単に定義することができる。

構造体のような宣言

data PersonのPersonは型コンストラクタという。= Person ...のPersonは値コンストラクタという。

ghci> data Person = Person String String deriving (Show)
ghci> Person "Junichi" "Kato"
Person "Junichi" "Kato"

列挙型のような宣言

ghci> data Color = Red | Blue | Yellow deriving (Show)
ghci> :t Red
Red :: Color

組み合わせた宣言

ghci> data Shape = Circle Int | Rectangle Int Int Int Int

型変数を利用する

ghci> data Maybe a = Nothing | Just a
ghci> :t Just "abc"
Just "abc" :: Maybe [Char]

レコード構文

data Person = Person String String

firstName :: Person -> String
firstName (Person f _) = f

lastName :: Person -> String
lastName (Person _ l) = l
ghci> firstName $ Person "Junichi" "Kato"
"Junichi"

これは正直うざいので、こういうときはレコード構文を使う。

data Person = Person { firstName :: String, lastName :: String } deriving (Show)
ghci> firstName Person { firstName = "Junichi", lastName = "Kato" }
"Junichi"
ghci> firstName $ Person "Junichi" "Kato"
"Junichi"

型変数

head関数はaのリストを引数にとり、a型の戻り値を返す。aのことを型変数と呼ぶ。

ghci> :t head
head :: [a] -> a

型クラス

型に対して、何らかの振る舞いを定義するインターフェイスです。ある型に対して適用する関数の集合を定義したもの。

既存の型クラス

Eq型クラス

Eq型クラスは次のように定義されています。classと定義されているけど、Javaのinterface宣言ように読むと良い。実装も書くことができます。Scalaのtraitのようだがmixinができるわけではない。

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

==演算子は、任意の型を2つとり、等値性を評価し結果をBoolで返します。ただし、aはEq型クラスのインスタンスでなければなりません。(Eq a) =>は型クラス制約という。

ghci> :t (==)
(==) :: (Eq a) => a -> a -> Bool

Int型に対応した型クラスが定義されているので次の操作は可能。

ghci> 1 == 1
True
ghci> 1 /= 1
False

Int型に対応するEq型クラスの実装は次のとおり。 Eq aのaの部分がIntなのでxとyはInt型ということになる。

instance Eq Int where
    x (==) y = x == y
    x (/=) y = x /= y

Eq型クラスはいろいろな型に対応している。便利!

ghci> 'a' == 'a'
True
ghci> 3.14 == 3.14
True

Ord型クラス

値を比較するための型クラス。

ghci> :t (>)
(>) :: (Ord a) => a -> a -> Bool

Eq型クラスと同様にいろいろな型にて適用できる。

ghci> 2 > 1
True
ghci> 1 > 2
False
ghci> "Abrakadabra" < "Zebra"
True

Show型クラス

Showは文字列に変換するための型クラス。

ghci> :t show
show :: Show a => a -> String

いろいろな型に対応している。

ghci> show 1
"1"
ghci> show 3.14
"3.14"
ghci> show "a"
"\"a"\"

Read型クラス

文字列を読み込んである型に変換するための型クラス。

ghci> :t read
read :: Read a => String -> a

型推論ができない場合は型アノテーションを付与する。

ghci> read "1" + 1
2
ghci> read "1" :: Int
1
ghci> read "8.2" + 3.8
12.0
ghci> read "5" - 2
3

型クラスの自動導出

derivingは、derive = 自動導出の動名詞。キーワードの後ろで指定した型クラスを自動的に作ります。

data Person = Person { firstName :: String, lastName :: String } deriving (Show,Eq,Ord)

ShowはREPLでインスタンスが表示される時にも利用される。

ghci> Person "Junichi" "Kato"
Person "Junichi" "Kato"

Showが指定されていないとREPLでインスタンスの内容を表示する際にエラーになる。

ghci> data Person = Person String String
ghci> Person "Junichi" "Kato"

<interactive>:14:1:
    No instance for (Show Person)
      arising from a use of `print'
    Possible fix: add an instance declaration for (Show Person)
    In a stmt of an interactive GHCi command: print it

Eq,Ordの例は次のとおり。

ghci> Person "Junichi" "Kato" == Person "Junichi" "Kato"
True
ghci> Person "Junichi" "Kato" == Person "junichi" "kato"
False
ghci> Person "Junichi" "Kato" > Person "" ""
True
ghci> Person "Junichi" "Kato" > Person "Junichi" "Kato"
False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment