Yet Another Haskell 4章 (2)
4.2 Polymorphic Types
多相型
関数tailはリストを引数にとるが、そのリストが内包する値の型に関わらず適用できる。
Prelude> tail [5,6,7,8,9] [6,7,8,9] Prelude> tail "hello" "ello" Prelude> tail ["the","man","is","happy"] ["man","is","happy"]
これはtailの型が多相型[a]->[a]だからだ。
Prelude> :t tail tail :: [a] -> [a]
つまり、tailは任意のリストを引数として受け取り、同じ型のリストを返す。
fstの型を調べてやると、
Prelude> :t fst fst :: (a, b) -> a
つまり、fstは任意のペアを引数として受け取り、ペアの最初の要素と同じ型の値を返す。
この多相型ってのは、C++のテンプレートや、JavaやC#のジェネリクスと似たようなものなのかな。
Exercise 4.2 省略
Yet Another Haskell 4章 (1)
4.1 Simple Types
Haskellは静的型付け言語であり、全ての式は型を持っている。
さらに、タイプインターフェイスというシステムがあり、式の型を指定する必要がない。型はコンテキストから推論される。
HugsやGHCでは、:tコマンドを使用することで式の型を調べることができる。
Prelude> :t 'c' 'c' :: Char
式'c'の型はCharだ。
Int(整数型), Double(浮動小数点型), Char(文字型), String(文字列型)等など、たくさんの組み込み型がある。
文字列の型は、
Prelude> :t "Hello" "Hello" :: [Char]
原文だと、Stringになっているけど、まあ同じものだし。
比較の結果はBool
Prelude> :t 'a' == 'b' 'a' == 'b' :: Bool
違う型同士を比較しようとすると、エラーになる。
Prelude> :t 'a' == "b" <interactive>:1:8: Couldn't match expected type `Char' with actual type `[Char]' In the second argument of `(==)', namely `"b"' In the expression: 'a' == "b"
::を使って明示的に型を指定することもできる。
Prelude> :t "b"::String "b"::String :: String
上の例では"a"の型はStringの可能性しかないため特に意味は無いが、数値の場合は少し状況が異なる。
Prelude> :t 5::Int 5::Int :: Int Prelude> :t 5::Double 5::Double :: Double
5はIntともDoubleとも解釈できる。
もし、型を指定しなければどうなるか?
Prelude> :t 5 5 :: Num a => a
変な結果になった。
5の型aはNumクラスのインスタンスという意味らしい。
詳しくは4.3で扱う。
Exercise 4.1
1. 'h':'e':'l':'l':'o':[] :: [Char] 2. [5, 'a'] -> error 3. (5,'a') :: Num t => (t, Char) 4. (5::nt) + 10 :: Int 5. (5::Int) + (10::Double) -> error
Yet Another Haskell 3章 (8)
3.8 Interactivity
ユーザーインタラクションに関して
キーボードからの入力を受け付ける関数(scanf, cin等)
は厳密な意味では関数ではない。
ユーザーの入力によって結果が変わってしまうからだ。
この問題は数学の圏論におけるモナドによって解決された。
モナドに関しては、5章や9章で詳しく扱う。
インタラクティブなプログラムを書くためには、doを使う。
これを使うことで、プログラムの実行順序を制御できる。
(Haskellはlazyな言語なので、通常、プログラムの実行順序は決まっていない)
ユーザーの名前を聞く簡単なプログラムが次だ。
module Main where import System.IO main = do hSetBuffering stdin LineBuffering putStrLn "Please enter your name: " name <- getLine putStrLn ("Hello, " ++ name ++ ", how are you?")
これを実行すると次のようになる。
*Main> main Please enter your name: quotientK Hello, quotientK, how are you?
確かに、インタラクションができている。
次にプログラムの解説。
import System.IOは入出力のための関数を定義しているIOというライブラリを取り込む。
doはコマンドを順番に実行するためのもの。
hSetBuffering stdin LineBufferingはGHCでの実行に必要なもので、特に重要ではないっぽい。(GHCでは通常ある一定の長さの入力を行うまで、処理が次に行かないらしい。このコマンドを実行すると処理が次に行くようになるらしいが、試しにこの行を削除して実行してみても特に問題無かった。環境がWindowsだからかな。)
putStrLnは文字列を画面に出力するコマンド。
name <- getLineは、他の言語では、name = getLine()のようなもの。
ただ、getLineは関数ではない(ユーザーの入力によって結果が代わる)ため、<-を使う。これは「getLineを実行し、その結果をnameに保存する」を意味する。
最後の行は、nameを加工して、画面に文字列を出力する。
こういうものだと思えば何のことは無いが、この後ろで働いてるモナドの理解が大変なんだろうね。まあとりあえず今は気にしないでおこう。
お次は、数字当てゲームの例。
1から100までの数字をランダムに決め、それを当てるプログラムだ。
乱数もまた純粋な関数ではないため、モナドを使う必要がある。
module Main where import System.IO import System.Random main = do hSetBuffering stdin LineBuffering num <- randomRIO (1::Int, 100) putStrLn "I’m thinking of a number between 1 and 100" doGuessing num doGuessing num = do putStrLn "Enter your guess:" guess <- getLine let guessNum = read guess if guessNum < num then do putStrLn "Too low!" doGuessing num else if guessNum > num then do putStrLn "Too high!" doGuessing num else do putStrLn "You Win!"
num <- randomRIO (1::Int, 100)は、1から100までの乱数を生成し、numに代入する。getLineと同じく<-を使用する。
- Intは生成する乱数が整数であること指定するためのもの。100の方にはいらないのは推論されるからだろう。
その次がdoGuessingを実行する。
doGuessing num = doでdoGuessingの定義が始まる。
guess <- getLineで、guessにユーザーからの入力を保存する。
let guessNum = read guessで、guessを数値に変換する。(getLineの結果はStringだから)
readは純粋な関数のため、read guessでは<-は使わない。
なお、doの中ではlet使用時にinは必要ないとのこと。
そして、guessとnumの値によって分岐し、次に続く処理を決める。
なるほど。
次は、再帰的にコマンドを実行する際の注意。
まず間違った例から。
askForWords = do putStrLn "Please enter a word:" word <- getLine if word == "" then return [] else return (word : askForWords)
間違っているのは最後の行。
askForWordsはリストではなく。アクション(action)だから、:で繋ぐことができない。
で、正しい例は、
askForWords = do putStrLn "Please enter a word:" word <- getLine if word == "" then return [] else do rest <- askForWords return (word : rest)
<-を使ってアクションの結果をrestに代入し、それを:で繋いで返すのが正しい。
returnがしれっと出てきているが、この<-やreturnがモナドの一部(?)というのは聞いたことがある。
基本的な部分は理解できた。と思う。
Exercise 3.10
module Main where import System.IO main = do hSetBuffering stdin LineBuffering nums <- scanNumbers putStrLn ("The sum is " ++ show (Main.sum nums)) putStrLn ("The product is " ++ show (Main.product nums)) printFactorialList nums scanNumbers = do putStrLn "Give me a number (or 0 to stop):" line <- getLine let n = read line if n == 0 then return [] else do rest <- scanNumbers return (n : rest) sum list = foldl (+) 0 list product list = foldl (*) 1 list factorial 1 = 1 factorial n = n * factorial(n - 1) printFactorialList [] = do return () printFactorialList (x:xs) = do putStrLn ((show x) ++ " factorial is " ++ (show (factorial x))) printFactorialList xs
実行結果
*Main> main Give me a number (or 0 to stop): 10 Give me a number (or 0 to stop): 2 Give me a number (or 0 to stop): 5 Give me a number (or 0 to stop): 1 Give me a number (or 0 to stop): 0 The sum is 18 The product is 100 10 factorial is 3628800 2 factorial is 2 5 factorial is 120
何もしないことの表し方でハマった…。return ()か。
Yet Another Haskell 3章 (7)
3.7 Recursion
再帰に関して
C言語などでは、for文を使ったループをよく使う。
しかし、for文では破壊的変更を伴うため、Haskellでは使われない。
代わりに再帰を使う。
例えば、C言語で階乗を計算する関数は次のように書ける。
int factorial(int n) { int fact = 1; for (int i = 2; i <= n; ++i) fact = fact * i; return fact; }
Haskellでは次のように書く。
factorial 1 = 1 factorial n = n * factorial(n - 1)
シンプルですね。
再帰をマスターするのは難しいけど、最初の場合と再帰の場合を分けて考えるのが大事。
例えば、aのb乗を計算する関数は、b=1とそれ以外で分けるといい。
exponent a 1 = a exponent a b = a * exponent a (b-1)
リストの長さを計算する関数(lengthと同じ関数)は、リストが空の場合と、それ以外で分けるといい。
my_length [] = 0 my_length (x:xs) = 1 + my_length xs
この(x:xs)という記法は特徴的ですね。
リストが空でない場合は、最低1つの要素があるため、先頭の要素をxで操作でき、残りのリストをxsで表し、それを自分自身に渡して再帰すればいい。
filterもリストが空の場合と、それ以外で分けるといい。
リストが空の場合は、空のリストを返し、
リストが空でない場合は、先頭の要素が条件を満たすかしらべ、残りのリストを再帰で処理する。
my_filter p [] = [] my_filter p (x:xs) = if p x then x : my_filter p xs else my_filter p xs
このパターンは頻出しそう。
Exercise 3.7
fib 1 = 1 fib 2 = 1 fib n = fib (n-1) + fib (n-2)
Exercise 3.8
mult a 1 = a mult a b = a + mult a (b-1)
Exercise 3.9
my_map p [] = [] my_map p (x:xs) = p x : my_map p xs
Yet Another Haskell 3章 (5.2)
3.5.2 Infix
インフィックスに関して
(+), (*), (++)等の記号の関数は、インフィックス関数と呼ばれる。
これらは()で包むことで、通常の関数と同じように使える。
Prelude> 5 + 10 15 Prelude> (+) 5 10 15
foldlの例で(+)と書いていたのはこれが理由ですね。
通常の関数を``(バッククォート)で包むことで、インフィックス形式で使用できる。
Prelude> map Data.Char.toUpper "Hello World" "HELLO WORLD" Prelude> Data.Char.toUpper `map` "Hello World" "HELLO WORLD"
(何に使うんだろう。)
Yet Another Haskell 3章 (5.1)
3.5.1 Let Bindings
let bindingに関して
今までの知識を使うと、2次方程式の根を求める関数rootsは次のように書ける。
roots a b c = ((-b + sqrt(b*b - 4*a*c)) / (2*a), (-b - sqrt(b*b - 4*a*c)) / (2*a))
let inを使うことで、関数内でのみ参照できるローカル変数を定義できる。
roots a b c = let det = sqrt(b*b - 4*a*c) in ((-b + det) / (2*a), (-b - det) / (2*a))
ローカル変数は複数定義できる。
roots a b c = let det = sqrt(b*b - 4*a*c) twice_a = 2*a in ((-b + det) / twice_a, (-b - det) / twice_a)
letの後は、インデントに注意。
roots a b c = let det = sqrt(b*b - 4*a*c) twice_a = 2*a in ((-b + det) / twice_a, (-b - det) / twice_a)
とするとエラーになる。