Haskellの基本を抑える。どんなものかイメージがつかめるといいな。エッセンスだけ駆け足で。
1.基本
基本的にコードの相関はインデントで表しているため、
勝手なインデントは許されない。
(1)エントリーポイント
main = do
print "Hello, World!"
「関数名 = 処理内容」
※1行処理の場合、doは省略可能
main =
print "Hello, World!"
※複数行処理の場合、doを省略するとエラーになる
main =
print "Hello, World!"
print "multi"
(2)コメント
1行コメント「–」、複数行コメント「{- -}」
main = do
print "Hello, World!" -- 1行コメント(--後、記号いれると落ちるので、スペース入れるのが作法っぽい)
{-
複数行コメント
{- ネストしても問題ないのだそう
-}
-}
(3)変数
トップレベル変数、ローカル変数、「let 変数名 = 値」、「where 変数名 = 値」
tvar = 123 -- トップレベル変数(グローバル変数)
main = do
let cvar11 = 4 -- ローカル変数
cvar12 = 5
cvar1 = cvar11 + cvar12
print (tvar + cvar1)
print (tvar + cvar2)
where
-- 使用する箇所の下でwhereを使って定義することもできるそう
cvar21 = 6 -- ローカル変数
cvar22 = 7
cvar2 = cvar21 + cvar22
代入ではなく、束縛というのだそう。再代入はできない。
ローカル変数は「let」か「where」が必要。
※do省略時はinキーワードが必要なのだそう
tvar = 123
main =
let cvar11 = 4
cvar12 = 5
cvar1 = cvar11 + cvar12 in
print (tvar + cvar1)
理屈はよくわからない。
※doはIO()が戻り値型。mainは戻り値にIO()が必要。
※letの基本構文は「let 変数 in (変数を使用して何か値を返す)」で、letの戻り値はin()内の戻り値。
※なので、以下がいえる気がする。
「main = do { let 何か (IO()を返さなくてもいい処理) }」
「main = let 何か in (IO()を返さなければいけない処理)」
※結局よくわからないが、「複数行処理の場合、doを省略するとエラーになる」も関係してそう
(4)関数
「関数名 引数 = 処理内容」
{-
f(x) = x + 1
関数名「f」、引数名「x」、処理内容「引数+1」
-}
f x = x + 1
main = do
print (f 1) -- 関数の引数で関数を使用する場合は括弧で括る(print関数の引数に関数fの結果を渡している)
print $ f 1 -- $で括弧の代用も可能
※mainって引数なし関数なのか?
(5)関数の演算子化、演算子の関数化
「add 引数1 引数2」=「引数1 `add` 引数2」
「引数1 + 引数2」=「(+) 引数1 引数2」
add x y = x + y
main = do
print $ 1 `add` 2
print $ (+) 1 2
関数のように「関数 引数1 引数2」の書き方を前置記法。
演算子のように「引数1 演算子 引数2」の書き方を中置記法。
(6)比較(if、case of も)
一致「==」、不一致「/=」
main = do
let a = 1
-- 1行記述
if a == 1 then print "1" else print "unknown"
-- 複数行記述(インデント注意)
if a /= 1
then print "unknown"
else print "1"
-- case of
let res = case a of
1 -> "1"
_ -> "unknown" -- default
print res
-- case of では後述するガードも使用できる
let res = case a of
1 -> "1"
_
| a /= 1 -> "unknown"
print res
if はelseの省略不可。
(7)論理演算
AND「&&」、OR「||」、NOT「not」
main = do
print $ True && True
print $ True || False
print $ not True
(8)型指定(型注釈)
「::」
-- 関数型指定・・・引数「Int」、戻り値「String」
f :: Int -> String
f x = show x
-- 関数型指定・・・引数「Int, Int」、戻り値「String」
m :: Int -> Int -> String
m x y = show x ++ show y
main = do
let var :: Int
var = 12
print $ var - (5 :: Int)
(9)関数の再帰呼び出し
fact 0 = 1 -- ①引数が0の関数
fact n = n * fact (n - 1) -- ②引数がnの関数(再帰呼び出ししている)
main = do
print $ fact 5
①の方が先に記述されているので
引数が0以外は②を使用して、0になると①を使用している。
この書き方を”パターンマッチ”というらしい。
以下と同じ動作となる。
fact n = if n == 0 then 1 else n * fact (n - 1)
また、以下のようにも書ける。この書き方を”ガード”という。
fact n
| n == 0 = 1
| otherwise = n * fact (n - 1)
{-
~~~~~~~~~~~~
この部分が条件
~~~~~~~~~~~~~~~~~~~
こっちは処理内容
処理内容のインデント注意(=の書き出しを合わせる)
-}
※パターンマッチで書けない場合、ガードを検討というのが一般的らしい。
※Haskellにループ等の繰り返し関数は存在しない。再帰で実現する。
(10)関数引数省略
処理内容が引数によらない場合、「_」で省略可能
f1 x = "1"
f2 _ = "1"
mf 0 _ = 0
mf x y = x + y
-- 通常「x+y」だがxが0だと無条件に0になる...みたいな
(11)リスト
「[1, 2, 3, 4, 5]」
main = do
print [1, 2, 3, 4, 5]
print [1..5] -- 上記と同じ
print [1, 4..5] -- [1, 4, 5]こんなのも可能
let mugen = [1..] -- 無限リスト(遅延評価のなせるわざ)
print $ [1, 2] ++ [3, 4, 5] -- リストの結合
print $ 1 : [2..5] -- 要素追加(先頭に追加)
print $ 1 : 2 : 3 : 4 : 5 : [] -- 同じ結果
print $ [1..4] ++ [5] -- 最後に要素追加はリストの結合で
print ([] :: [Int]) -- 要素0のリスト(エラー回避のため型注釈)
print $ [1..5] !! 3 -- 3番目の要素取り出し(最初の要素の番号は0。0オリジン)
print $ length [1..5] -- 要素数
print $ take 2 [1..5] -- 先頭から2個の要素を取り出し([1,2])
print $ map (+3) [1..5] -- 各要素を(+3)する([4,5,6,7,8])
print $ zip [1,2,3] [4,5,6,7] -- 2つのリストからペアのリストを作成する(タプルになる)([(1,4),(2,5),(3,6)])
-- リスト操作関数は他にも色々
print $ "ABCDE"
print $ ['A', 'B', 'C', 'D', 'E'] -- 文字列はcharのリスト
print $ 'A' : ['B'..'E']
print $ "ABCD" ++ "E"
print $ "ABCDE" !! 3
(12)リストを関数の引数で使用する
get1 (x:xs) = x
main = do
print $ get1 [1..5] -- 出力値「1」
print $ get1 "ABCDE" -- 出力値「'A'」
{-
get1の引数の形が「x:xs」つまり「最初の1要素+2要素以降」の形で指定されているので、
引数が[1..5]の場合、xは「1」、xsは「[2..5]」となる。
なので、引数の最初の要素を返却している。
xsを使用しないなら「x:_」でよい。
また「x:y:xs」も可能。
べたに「get1 xs = xs !! 1」でもいいんだよね?おすすめはしないのかな?
-}
(13)リストの内包表記
どんなリストにするか指定する
書式『[要素 | 条件式]』
main = do
print [1..100]
print [x | x <- [1..100]] -- 上と同じ結果。要素xが1~100を取るリスト
print [x | x <- [1..100], x `mod` 2 == 0] -- 要素xが1~100の偶数を取るリスト
print [x^2 | x <- [1..100], x `mod` 2 == 0] -- 要素xが1~100の偶数を取り2乗したリスト「4,16,36,...,10000」
print $ kisuRemban 100
print [pow x 3 | x <- [1..100]] -- 要素にも関数の使用が可能
print [(x, y) | x <- [1..4], y <- "ABC"] -- 組み合わせのリストを作成する([(1,'A'),(1,'B'),(1,'C'),(2,'A'),(2,'B'),(2,'C'),(3,'A'),(3,'B'),(3,'C'),(4,'A'),(4,'B'),(4,'C')]) kisuRemban :: Int -> [Int]
kisuRemban n = [x | x <- [1..n], x `mod` 2 == 1] -- 引数nまでの奇数リストを返却
pow x n = x^n
(14)タプル
calcsam x y = (x + y, x - y, x * y)
main = do
let tpl = calcsam 1 2
(arg1, arg2, arg3) = calcsam 1 2
print tpl -- 出力値「(3,-1,2)」
print arg1 -- 出力値「3」
print arg2 -- 出力値「-1」
print arg3 -- 出力値「2」
print $ fst ("iti", "ni") -- 出力値「"iti"」
print $ snd ("iti", "ni") -- 出力値「"ni"」
let (_, _, trd) = tpl
print trd -- 出力値「2」
(15)import
ライブラリの読み込み
import Data.Char
-- ord 文字から文字コードを取得
-- chr 文字コードから文字に変換
-- 以下サンプルとしてROT13
rot13 :: Char -> Char
rot13 a
| (ord a < ord 'A') || (ord a > ord 'Z') = a
| cnum > ord 'Z' = chr (cnum - ord 'Z')
| otherwise = chr cnum
where
cnum = ord a + 13
-- ROT13を文字列適用
rot13s :: [Char] -> [Char]
rot13s [] = []
rot13s (x:xs) = rot13 x : rot13s xs
main = do
print $ rot13s "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234abc$%&A"
(16) デバッグ
「Debug.Trace.trace 表示する文字列 戻り値」
import Debug.Trace
f x y = trace ("x=" ++ show x) x + trace ("y=" ++ show y) y
main = do
print $ "result=" ++ show (f 1 2)
{-
出力結果
--------
y=2
x=1
result=3
-}
(17) 列挙型
他言語でいうところのenum。
「data 型構築子 = データ構築子 | データ構築子 deriving (型クラス, 型クラス)」
型構築子とデータ構築子の先頭文字は大文字のこと。
型クラスは対応する機能を付与する。
data Opt1 = ReadOnly1 | Edit1 | Update1
data Opt2 = ReadOnly2 | Edit2 | Update2 deriving Show
main = print ReadOnly1
型クラスは標準で6つ存在する。
Show | データ構築子をprintで表示できるようにする |
---|---|
Enum | 数値互換になる。”fromEnum データ構築子”で数値に変換(0から)。”toEnum 数値 :: 型構築子”でデータ構築子に変換 ※デフォルトだと何の数値が割り当てられるのだろう?ビット演算が可能らしいが、ということは乗数値? |
Eq | “==”や”/=”で比較できるようにする |
Ord | 順番を持つようにし、”<“や”>”で比較ができる |
Read | 文字列から変換できるようにする |
Bounded | 最小値と最大値を持つようにする |
(18) 直積型
他言語でいうところのstruct(構造体)。
「data 型構築子 = データ構築子 フィールドの型 フィールドの型 deriving (型クラス, 型クラス)」
型構築子とデータ構築子は同名でも別名でも可能。
data Item = Item [Char] Double deriving Show
warimashi (Item s t) p = Item s (t + t * p /100)
main = do
print $ warimashi (Item "desk" 100) 10
フィールドが1つだけのときは”newtype”でも可能。
“data”よりも高速に動作するらしい。
newtype Item = Item [Char] deriving Show
(19) 直和型
列挙型と直積型をあわせたもの。
「data 型構築子 = データ構築子 フィールドの型 フィールドの型 | データ構築子 フィールドの型 フィールドの型 deriving (型クラス, 型クラス)」
data Elem = Elem1 Int Int | Elem2 Int Int String
-- 直和型を引数にすることによって、関数fは数値も文字列も受け取れるようになっている
data Test = TestInt Int | TestStr String deriving Show
f (TestInt 1 ) = "Str"
f (TestStr "1") = "Int"
f _ = "?"
-- 型注釈は「f :: Test -> Str」か?
main = do
print $ f $ TestInt 0 --「"?"」
print $ f $ TestInt 1 --「"Int"」
print $ f $ TestStr "0" --「"?"」
print $ f $ TestStr "1" --「"Str"」
(20) レコード構文
直積型や直和型のフィールドに名前をつけること。
「data 型構築子 = データ構築子 { 名前 :: フィールドの型, 名前 :: フィールドの型 } … 」
data Item = Item { name :: [Char], price :: Double } deriving Show
main = do
print $ Item { name = "desk", price = 100 } -- 名前を指定
print $ Item "desk" 100 -- 名前を指定しない(名前を付けていない場合と同じ)
let ib = Item "desk-B" 200
print $ name ib -- フィールド名を関数として使用し、フィールド値を取得
let (Item tmpNm tmpPc) = ib -- パターンマッチで取り出し
print (tmpNm, tmpPc) -- 出力結果「("desk-B",200)」
let (Item { price = tmpPc2 }) = ib -- レコード構文でパターンマッチ
print tmpPc2 -- 出力結果「200」
let ib2 = ib -- コピー
ic = ib { name = "desk-C" } -- nameを変更したコピー
print ib2 -- 出力結果「Item { name = "desk-B", price = 200 }」
print ic -- 出力結果「Item { name = "desk-C", price = 200 }」
関数の引数にパターンマッチが不要になる
data Item = Item { name :: [Char], price :: Double } deriving Show
warimashi im p = im { price = (t + t * p /100) }
where
t = price im
main = do
print $ warimashi (Item "desk" 100) 10
(21) 参照透過性と副作用(アクション)
●参照透過性・・・同じ引数の場合、関数の結果は同じという性質
●副作用・・・同じ引数でも結果が異なる場合、何かしらの状態の影響を受けている。この状態の変化を副作用という
動的関数、静的関数というとこかね。
Haskellは参照透過性のない関数は定義できないそうだ。(関数定義は定義・宣言であり、実体・領域は持たないとか?)
動的関数を扱いたい場合は”アクション”をつかう。
import System.Random
rand = getStdRandom $ randomR ('A', 'Z') -- アクション"rand"
main = do
r <- rand -- アクション"rand"から値を取り出し、変数rに束縛 print r rand >>= print -- アクション"rand"から値を取り出して、関数に渡す
print =<< rand -- アクション"rand"から値を取り出して、関数に渡す(逆向き) let r' = rand -- こうすると、変数r'の中身は値ではなくアクション"rand"になる r' >>= print
取り出し”<-“はdoブロックの中でのみ有効。
ただし、doブロックはmain以外でも使用できる。
import System.Random
rand = getStdRandom $ randomR ('A', 'Z')
sample = do -- 引数が無いので、関数ではなく変数
r <- rand
print r
main = do
sample -- 乱数値が出力される
sample -- 乱数値が出力される
doブロック全体で1つのアクションとなる。
sampleはアクションが束縛された変数。
つまり、mainもアクションが束縛された変数といえる。
return を使うとアクションが作成できる。
main = do
a <- return 1 -- アクション「return 1」の値「1」を変数「a」に束縛 print a -- 出力結果「1」 return 9 >>= print -- 出力結果「9」
-- returnで処理がとまることはない
return 8 >>= print -- 出力結果「8」
do と return でアクションを戻す関数が作れる
conb s t = do
print s
print t
return $ s ++ t
main = do
conb "abc" "def" >>= print
(22) アクションの型注釈
とりあえず”IO”という型を知れ。型名は「IO 型」。
型推論があるため必須ではないが、型推論できない(可能性が複数個存在する)場合は記載しないとエラーになる。
import System.Random
-- Charを返すアクションの"rand"という意味
rand :: IO Char
rand = getStdRandom $ randomR ('A', 'Z')
-- 1行表記
rand2 = getStdRandom $ randomR ('A', 'Z') :: IO Char
-- 引数付き
rand3 :: Int -> IO Int
rand3 n = getStdRandom $ randomR (0, n - 1)
main = do
print =<< rand
print =<< rand2
print =<< rand3 10
(23) unit(値なし)
空のタプル”()”をunitと呼び、値がないことを表している。
v = () -- 値なしを束縛している変数
f _ = () -- 値なしを戻す関数
a = return () :: IO () -- 値を戻さないアクション(voidみたいなもの)
main = return () -- 値を戻さないmain
(24) printという関数
“print”はアクションを返す関数。
アクションから値を取り出すタイミングでの副作用により、文字が表示されている。
main = do
a <- print "hello" -- ここで"hello"が出力され
print a -- ここでは"()"(空)が出力される
(25) doブロック
doブロックの末尾ではアクションを戻す必要がある。
f = do
return "a"
return "b" -- アクション「return "b"」がdoブロック最後のアクション
変数main はアクションを束縛する必要がある。
main = return "a"
main = do
return "a"
-- 1行のprintがOKだったのはprintがアクションを戻し、mainがそのアクションを受け取っていたから
main =
print "Hello, World!"
main = do
print "Hello"
print ", World!"
{-
letを使うとdoいらないと最初の方で書いたが、
letの構文は「let 変数定義 in 式」であり、式の戻り値がlet全体の戻り値になる。
なので、式でアクションを戻していれば、以下が成立する。(以下の場合はprintでアクションを戻している)
-}
main =
let cvar11 = 4
cvar12 = 5
cvar1 = cvar11 + cvar12 in
print cvar1
(26) bind
“>>=” とか “=<<“とかのことをbindと呼ぶ。アクションとアクションを結びつけ(bind)ている。
「アクション >>= アクションを返す関数」
e.g. 「return 2 >>= print」
incrementAct x = return $ x + 1
increment x = x + 1
main = do
return 2 >>= incrementAct >>= print
{-
1.アクション「return 2」の値「2」をアクションを返す関数「incrementAct」に渡す。
2.アクションを返す関数「incrementAct」の結果「3」をアクションを返す関数「print」に渡す。
3.アクションを返す関数「print」の副次効果で値「3」が表示される。
-}
print $ increment =<< return 2
{-
こっちは「increment」がアクションを返す関数でないため、エラーになる。
-}
アクションを返す関数にのみbindできる仕様は
一度、アクションに値を入れたら、アクションの外にその値を取り出せなくなることを意味している。
この仕様により参照透過性を保っている。
“<-“はdoブロック内でしか使用できないため、doアクション内に閉じ込められていると考えられる。
(27) Applicativeスタイル
アクションの値を関数に渡す方法としてbindの他に、Applicativeスタイルもある。
import Control.Applicative
inc x = x + 1
add x y = x + y
main = do
print =<< inc <$> return 1 -- "=<<"の代わりに、"<$>"
print =<< add <$> return 1 <*> return 2 -- 続く場合は2つ目以降が"<*>"になる。xが「return 1」の値、yが「return 2」の値。
(28) アクションのデバッグ
値を返すデバッグに「trace」があった。「Debug.Trace.trace 表示する文字列 戻り値」
アクションを返すデバッグに「putStrLn」がある。「Debug.Trace.putStrLn 表示する文字列」
返すアクションは”print”と同じ”()”。
アクションを使うと関数の戻り値もアクションにする必要がある、そんな時に使う?
import Debug.Trace
f x y = trace ("x=" ++ show x) x + trace ("y=" ++ show y) y
fa x y = do
putStrLn ("xa=" ++ show x)
putStrLn ("ya=" ++ show y)
return $ "resulta=" ++ show (x + y)
main = do
print $ "result=" ++ show (f 1 2)
print =<< fa 1 2
{-
出力結果
--------
y=2
x=1
result=3
xa=1
ya=2
resulta=3
-}
(29) IORef
値が変更できる変数のようなもの。
import Data.IORef
main = do
a <- newIORef 1 -- 1をaに束縛。"newIORef"は初期化。aの型は「IORef Int」。
b <- readIORef a -- 変数aの値を変数bに束縛。"readIORef"は読み取り。bの型はaと同じ。
writeIORef a 2 -- 変数aに2を書き込み。"writeIORef"は書き込み。
print =<< readIORef a -- 出力結果「2」
print b -- 出力結果「1」
サンプル「カウンター」
import Data.IORef
counter = do
c <- newIORef 0 -- 「c (型 IORef Int)」を0で初期化。cが保持される対象。
return $ do
tmp <- readIORef c
writeIORef c $ tmp + 1
readIORef c -- 「readIORef c (型 IO (IORef Int))」はアクション。
main = do
f <- counter -- 「return $ do~~」というアクションが変数fに束縛される。cの初期化は実行される。
print =<< f -- 出力結果「1」。アクション「return $ do~~」を処理し、値を取り出す。
print =<< f -- 出力結果「2」。〃。
print =<< f -- 出力結果「3」。〃。
サンプル「ループ」
import Data.IORef
main = do
i <- newIORef 0 -- (IORef)iの初期化
let loop = do -- アクション"loop"定義
i' <- readIORef i
if i' < 5 -- 5未満なら、
then do
print i' -- 値をprintして、
writeIORef i $ i' + 1 -- 値に+1して、
loop -- 再帰呼び出し。
else return () -- 何も返さずアクション終了。
loop -- アクション"loop"実行
(30) IOUArray
IORefの配列版。
import Data.Array.IO
main = do
-- ① 作成「newArray 範囲 初期値 :: IO(IOUArray インデックス型 値型」※型注釈必須
a <- newArray (0, 2) 0 :: IO (IOUArray Int Int)
-- ② 全ての値をリストで取得。出力結果「[0,0,0]」
print =<< getElems a
-- ③ 書き込み「writeArray 配列 インデックス番号 値」
writeArray a 0 3
-- ④ 読み取り「readArray 配列 インデックス番号」。出力結果「3」
print =<< readArray a 0
-- ⑤
writeArray a 1 6
-- ⑥ 出力結果「[3,6,0]」
print =<< getElems a
-- ⑦
writeArray a 2 7
-- ⑧ 出力結果「[3,6,7]」
print =<< getElems a
{-
① 要素0番~2番、初期値0で配列作成 → [0,0,0]
③ 要素0番に「3」書き込み → [3,0,0]
⑤ 要素1番に「6」書き込み → [3,6,0]
⑦ 要素2番に「7」書き込み → [3,6,7]
-}
2.関数あれこれ
(31)ラムダ式
関数の引数を右辺で定義する文法における右辺。
f x = x + 1 -- 通常
f = \x -> x + 1 -- ラムダ式(このメモの表示上はバックスラッシュを全角"\"で代用)
-- ラムダ式は型指定と同じ形をしている
f :: Int -> Int
f = \x -> x + 1
-- 引数を2つ以上使用する場合も型指定と同じ形
f :: Int -> Int -> Int
f = \x -> \y -> x + y
f x y = x + y -- ラムダ式使わない形
f = \x y -> x + y -- こんな書き方もある
(32)無名関数
ラムダ式は名前のない関数(無名関数)で、それを変数に束縛していると捉えることができる。
f = \x -> x + 1
{-
これは
f = 『\x -> x + 1』
※変数 f に 『\x -> x + 1』を代入しているといえる
-}
main = do
print $ f 1
-- ↓変数 f を介さずに直接記述すると、無名関数
print $ (\x -> x + 1) 1
(33)関数の引数や戻り値を関数でやり取りする(高階関数)
{- 引数が関数 -}
f23 g = g 2 3 -- 引数g が関数
add = \x y -> x + y
main = do
print $ f23 add -- f23で「add 2 3」となる
print $ f23 (\x y -> x + y) -- 無名関数でも
print $ f23 $ \x y -> x + y -- 括弧を$で省略
print $ f23 (+) -- 演算子も前置記法にすることで高階関数に渡すことが可能
{- 戻り値が関数 -}
add x = \y -> x + y -- 引数x を受け取り、引数y の関数『\y -> x + y』を返却する。返却時にxは決定される。
main = do
let g2 = add 2 -- g2には『\y -> 2 + y』という関数が入る
print $ g2 3
print $ add 2 3 -- 「add 2」してyに3を渡す
print $ (\g -> g 1 2) $ \x y -> x + y -- 『\g -> g 1 2』の引数gに『\x y -> x + y』
print $ (\g -> g 1 2) (\x y -> x + y) -- これでもいける気がするんだけど、どうだろう?
(34)カリー化
(Wikipediaより)
カリー化 (currying, カリー化された=curried) とは、
複数の引数をとる関数を、
引数が「もとの関数の最初の引数」で
戻り値が「もとの関数の残りの引数を取り、結果を返す関数」であるような関数にすること(あるいはその関数のこと)
汎用的に書くと『f(x,y,z) = g(y,z) = h(z)』を満たす関数をカリー化された関数ということみたい。
f = \x y z -> x + y + z
f' = \x y -> \z -> x + y + z
f'' = \x -> \y -> \z -> x + y + z
main = do
print $ f 1 2 3
print $ (f' 1 2) 3 -- (f' 1 2)は関数「\z -> 1 + 2 + z」が返却される
print $ (f'' 1) 2 3 -- (f' 1)は 関数「\y -> \z -> 1 + y + z」が返却される
面白いもので
「f” = \x -> \y -> \z -> x + y + z」って
引数3つでInt返却の
「f” x y z = x + y + z」とも取れるし
引数2つで関数返却(引数1つ)の
「f” x y = (\z -> x + y + z)」とも取れるし
引数1つで関数返却(引数2つ)の
「f” x = (\y z -> x + y + z)」とも取れる。
型注釈はいずれも
「f” :: Int -> Int -> Int -> Int」となる。
とすれば、
「f” x y z」を呼び出してInt値を取得する処理はもちろん
「f” x y」を呼び出してzを引数に持つ関数「関数(z)」を取得することもできる。
f'' x y z = x + y + z
main = do
print $ f'' 1 2 3 -- 結果「6」
let ff = f'' 1 2
let z = 3
print $ ff z -- 結果「6」
-- 1と2は前提値、zは入力値とかで、zのみ可変で複数回処理されるとかの場合に有用な気がする
上記の「ff」みたいな一部の引数を固定化した関数を作ることを”部分適用”というらしい。
※Haskellは普通に関数を記述すれば、カリー化された関数になるが、他言語で記述すると以下のようにめんどくさいことになる。
// C#の場合
Func<int, Func<int, Func<int, int>>> f
= new Func<int, Func<int, Func<int, int>>>
((a_x)=>{
return new Func<int, Func<int, int>>
((a_y)=>{
return new Func<int, int>
((a_z)=>{
return a_x + a_y + a_z;
});
});
});
int x = 1;
int y = 2;
int z = 3;
Func<int, Func<int, int>> f_yz = f(x);
Func<int, int> f_z1 = f_yz(y);
Func<int, int> f_z2 = f(x)(y);
int result = f_z1(z);
int result2 = f(x)(y)(z);
(35)セクション
中値演算子のままオペランドを省略した不完全な式は部分適用になる。
これを”セクション”というらしい。セクションは括弧必須。
f g = g 5
main = do
-- 要素0はラムダ式の記述、要素1はセクションでの記述
print [f (\x -> 2 + x), f (2 +)]
print [f (\x -> x + 2), f (+ 2)]
print [f (\x -> 2 - x), f (2 -)]
print [f (\x -> x - 2), f (+(-2))] -- "(- 2)"では"マイナス2"として扱われるため、このような記述で回避する
print [f (+(-2)), f subtract 2, f (`subtract` 2)] -- 要素1は減算関数で部分適用、要素2は関数を演算子化してセクション
部分適用と考えるよりも、ラムダ式でない関数の記述方法と捉えたほうがわかりやすいかも。
manuplate m x y = m x y
manuplate' m y = m y
main = do
-- 4つとも引数mを固定化して取得した関数について引数を渡しているので、部分適用
print $ manuplate (+) 2 3
print $ manuplate' (2 +) 3
print $ manuplate (\x y -> x + y) 2 3
print $ manuplate' (\y -> 2 + y) 3
(36)map, filter, foldl, foldr, flip
main = do
-- 1行目は関数使用。2行目はリスト内包表記。
-- map 「リスト要素に対しての処理」
print $ map (* 2) [1..5]
print [x * 2 | x <- [1..5]]
-- filter 「リスト要素取り出し条件」
print $ filter (< 5) [1..9]
print [ x | x <- [1..9], x < 5] -- foldl 「リスト要素を左から1つずつ処理しながら集計する」 -- (関数定義)「fold1 :: 集計関数 -> 初期値 -> リスト -> 集計結果」
print $ sum [1..100]
print $ foldl (+) 0 [1..100]
-- foldr 「リスト要素を右から1つずつ処理しながら集計する」
print $ foldr (-) 0 [1..5] -- 結果「3」※「=(1-(2-(3-(4-(5-0)))))」
print $ foldl (-) 0 [1..5] -- 結果「-15」※「=(((((0-1)-2)-3)-4)-5)」
-- flip 「2引数関数で引数順序を逆にする」
test1 f = map f [1..5] -- 通常の記述
test2 = (`map` [1..5]) -- 中値記法(セクション)
test3 = flip map [1..5] -- 引数順序逆転(セクション)
{-
「map f xs」なので
test1・・・「map f [1..5]」 登場人物は揃っている
test2・・・「f `map` [1..5]」 処理関数fが省略されている
test3・・・「flip map [1..5] f」処理関数fが省略されている
-}
main = do
print $ test1 (* 2)
print $ test2 (* 2)
{-
→ (`map` [1..5]) (* 2)
→ (\f -> f `map` [1..5]) (* 2)
-}
print $ test3 (* 2)
{-
→ (flip map [1..5]) (* 2)
→ (\f -> flip map [1..5] f) (* 2)
-}
(37)関数合成
“.”で関数を合成する
add x = x + 1
mul x = x * 2
manuplate m x = m x
mul' x y = x * y
main = do
print $ add (mul 1) -- 「add (1 * 2)」 → 「(1 * 2) + 1」
print $ (add . mul) 1 -- 評価順序は同じ
print $ manuplate (add . mul) 1
print $ manuplate add (mul 1)
let manuplate' = flip manuplate 1
print $ manuplate' $ add . mul
-- 2引数関数の合成
print $ add $ mul' 1 2
print $ ((add .) . mul') 1 2 -- 関数合成
(38)ポイントフリースタイル
別の関数に渡すだけの引数を省略できる。これを”ポイントフリースタイル”と呼ぶらしい。
別の関数に渡すだけの引数のことを”ポイント”という?
-- f1~f4は同じ意味
f1 x y = x - y -- 標準
f2 x y = (-) x y -- 前置記法
f3 x = (-) x -- ポイントyを省略
f4 = (-) -- ポイントxも省略
g1 x = f4 2 x -- 「(-) 2 x」
g2 = f4 2 -- ポイントxを省略
g3 = flip f4 5 -- ポイントyを省略
main = do
print $ g2 5
print $ g3 2
単純な引数「arg0, arg1」とかになりがちなとこで使用すればいいのだろうか?
(39) doブロック内のbind
incrementAct x = return $ x + 1
main = do
-- ①「(26) bind より」
return 2 >>= incrementAct >>= print -- 出力結果「2\n3」
-- ②ラムダ式で書くと
return 2 >>= \x -> incrementAct x >>= \x -> print x
-- ①は引数と実行するアクションへの引数が省略されていたことがわかる(ポイントフリースタイル)
-- 上記は結果1つのアクションのため、doの省略が可能
main =
return 2 >>= \x -> incrementAct x >>= \x -> print x
-- 引数が不要な場合は"\_"で記述
main =
print 2 >>= \_ -> print 3 >>= \_ -> print 4
-- インデントは以下が通常
main =
print 2 >>= \_ ->
print 3 >>= \_ ->
print 4
(40) replicateM
指定された条件を繰り返してリストを作成する。Control.Monadモジュール。
replicate・・・「replicate 要素数 値 -> リスト」
replicateM・・・「replicateM 要素数 アクション -> リスト」
replicateM_・・・「replicate 要素数 アクション -> IO()」※戻り値はリストではなく空アクション
import Control.Monad
import System.Random
dice :: IO Int
dice = getStdRandom $ randomR (1, 6)
main = do
print $ replicate 5 1 -- 出力結果「[1,1,1,1,1]」
print =<< replicateM 5 (return 1) -- 出力結果「[1,1,1,1,1]」
print =<< replicateM 5 dice -- 出力結果「[5,3,6,4,1]」※値はランダム replicateM_ 5 $ do dice >>= print -- 出力結果「5\n3\n6\n4\n1」※値はランダム
(41) forM
foreach。Control.Monadモジュール。
forM・・・「forM リスト アクション -> リスト」
forM_・・・「forM_ リスト アクション -> IO()」
import Control.Monad
act i = print i
main = do
forM [1..3] act -- 出力結果「1\n2\n3」
forM_ [1..3] act -- 出力結果「1\n2\n3」
a <- forM [4..6] $ \i -> return i
print a -- 出力結果「[4,5,6]」
(42) whenとunless
when・・・if のthenに相当
unless・・・if のelseに相当
import Control.Monad
import System.Random
dice :: IO Int
dice = getStdRandom $ randomR (1, 6)
-- どちらも3が出るまで繰り返す
main = do
r <- dice
print r
when (r /= 3) main
main = do
r <- dice
print r
unless (r == 3) main
(43) 「>>」
次のアクションに移る。
以下の2つは同じ動作
-- 値を取り出すが、次のアクションに渡さない
main =
print 2 >>= \_ ->
print 3 >>= \_ ->
print 4
-- 値を取り出し、捨てる
main =
print 2 >>
print 3 >>
print 4
(44) アクション合成
“>=>”または”<=<“でアクションを合成する。
import Control.Monad
-- f1~f5はすべて同じ処理
f1 x = putStr x >>= print
f2 = \x -> putStr x >>= print
f3 = putStr >=> print
f4 = print <=< putStr f5 = (>>= print) . putStr
main = f1 "1" >> f2 "2" >> f3 "3" >> f4 "4" >> f5 "5"
3.モナド
(45) IOモナド
上述のアクションはすべてIOモナド。
IOモナドは隠蔽された関数を持ち、その関数内に値を格納している。
「IOモナド( 関数 ( 値 ) )」
import GHC.Base -- 処理系に依存しているので注意
main = IO (\world -> unIO (print "hello") world)
{-# LANGUAGE UnboxedTuples #-}
import GHC.Base
main = do
let m = return 1
print =<< m -- IOモナドの値をprintに渡す
let f = unIO m -- IOモナド「m」から"unIO"で関数を取り出し変数「f」に束縛
let m1 = IO f -- 「f」の関数を持つIOもなどを"IO"で作成
print =<< m1
v <- m -- IOモナドから値を取り出し
let m2 = return v -- vを値とするIOモナドを作成
print =<< m2
(46) アンボックス化タプル
通常のタプル・・・式を保持
アンボックス化タプル・・計算結果を保持
-- アンボックス化タプルを使用するために言語拡張宣言
{-# LANGUAGE UnboxedTuples #-}
import GHC.Base
addsub x y = (# x + y, x - y #) -- アンボックス化タプルは「(#」「#)」で囲む
main = do
let (# a, b #) = addsub 1 2 -- 戻り値のアンボックス化タプルをパターンマッチで「a」「b」に束縛
print (a, b) -- アンボックス化タプルのままでは渡せないためそれぞれの値を直接渡している
returnを自作
{-# LANGUAGE UnboxedTuples #-}
import GHC.Base
main = do
let a = IO $ \s -> (# s, 1 #) -- 引数「s」を受け取り、値「1」を保持するIOモナド
print =<< a -- 出力結果「1」
{-
上記は「let a = return 1」相当
つまり、returnは
IOモナド(
関数(
1 ※保持する値
) 引数「s」
)
であり、引数「s」をどこかから受け取り、関数の戻り値はアンボックス化タプルということ。
-}
mainを自作
{-# LANGUAGE UnboxedTuples #-}
import GHC.Base
main = IO $ \s -> (# s, () #) -- 引数「s」はHaskellの処理系によって渡される
引数「s」を具現化
{-# LANGUAGE UnboxedTuples #-}
import GHC.Base
main = IO $ \s ->
let (# s1, r #) = unIO (print "hello") s
in (# s1, r #)
{-
「unIO (print "hello")」で関数「\s -> ~なんやかんや~ (# s, "hello" #)」を取り出し
引数「s」を渡した結果「(# s, "hello" #)」を「(# s1, r #)」に束縛(s1にsが、rに"hello"が入る)
その結果の「(# s1, r #)」を戻すIOモナドをmainに渡している
-}
(47) リストモナド
リストもモナドの一種。
main = do
print [1]
print (return 1 :: [Int]) -- 型注釈必須。書き方1。
print (return 1 :: [] Int) -- 書き方2。
print =<< return 1 -- 型注釈を書かないと、型推論によりリストモナドではなくIOモナドになる。
「副作用を持つ」=「一度、アクションに値を入れたら、アクションの外にその値を取り出せなくなる」
はIOモナドのみに言えることであり、モナドの制限ではない。
main = do
let a = return 1 :: IO Int
b = return 1 :: [] Int
print =<< a
print $ b !! 0
print b
(48) 異種間bind
bindでモナドを結合するとき、結合先の関数は同種のモナドを返す必要がある。
値を受け取ってリストを返す関数であれば、リストとbind可能。
main = do
print $ [7] >>= replicate 3 -- 出力結果「[7,7,7]」
print $ "7" >>= replicate 3 -- 出力結果「"777"」
print =<< [1] -- printはIOモナドを返すためエラーになる
(49) 多相
bind先の関数に型注釈を書かないでreturnを使えば、異なる種類のモナドを受け付ける関数ができる。
このように異なる型をまとめて扱うことを多相という。
inc x = return $ x + 1
main = do
print $ inc =<< [1] -- 出力結果「[2]」
print =<< inc =<< return 1 -- 出力結果「"2"」
多相で型注釈を書くときは、小文字で始まる適当な名前を付ける。これを型変数と呼ぶ。
型名は大文字で始まると決まっているため、小文字で始まれば自動的に型変数となる。
rep :: Int -> a -> [a]
rep 0 _ = []
rep n x = x : rep (n - 1) x
main = do
print $ rep 3 7 -- 出力結果「[7,7,7]」
print $ rep 5 'a' -- 出力結果「"aaaaa"」
(50) 型クラス制約
“IO Int”の”IO”の部分を型変数化する場合、型変数に対してそれがモナドであることを指定する必要がある。
これを型クラス制約と呼ぶ。
inc :: Monad m => Int -> m Int -- "m"がモナド
inc x = return $ x + 1
main = do
print $ inc =<< [1]
print =<< inc =<< return 1
1つのdoの中では同じ種類のモナドしか扱えない。
-- 以下はエラーになる
main = do
a <- print "a" -- IOモナド
b <- [1] -- リストモナド
return ()
-- doをネストすればエラーにならない
main = do
print $ do -- IOモナド
a <- [1] return $ a + 1 print $ do -- リストモナド? [1] >>= \a ->
return $ a + 1
(51) リストのループ
-- 出力結果「[2,4,6]」
main = do
print $ do
a <- [1, 2, 3]
return $ a * 2
-- 出力結果「[4,5,6,8,10,12,12,15,18]」
main = do
print $ do
a <- [1, 2, 3]
b <- [4, 5, 6]
return $ a * b
-- 空のリストを与えると、処理が終了する。
-- 出力結果「[]」
main = do
print $ do
a <- [1, 2, 3]
b <- [] -- 空のリスト
return a -- 処理されない
**********
一旦ここまでにしよう。これ以上は実際にいくつか作りながらでないと理解が追いつかないな。