好きなのどうぞ
C++ より速くて、Perl より簡潔で、Python よりきちんとしていて、Ruby より柔軟で、C# より型が充実していて、Java より頑強で、PHP とは何の共通点もないものって? に対する解答
宇宙語
非正格な評価を特徴とする純粋関数型プログラミング言語であり、高階関数や静的多相型付け、定義可能な演算子、例外処理といった多くの言語で採用されている現代的な機能に加え、パターンマッチングやカ
楽しい
型が全て
こんなん、一回でやり切れるはずなかったんや。。 主にスライドががが。。 ゆっくりしていきましょう。
Haskellの全部入り開発環境パッケージ
とりあえず、これ入れよう。
Ubuntu
sudo aptitude install haskell-platform
Fedora
sudo yum install haskell-platform
他のディストリビューションでも
Windows or Mac
http://hackage.haskell.org/platform/
OSXの場合、brewやportのhaskell-platformは古い可能性があるので上記推奨
Mac でのEditor選択
Eclipseにもpluginあるみたい
ブラウザ上から利用できるIDEとかもあります。FPComplete
遅延評価を特徴とする純粋関数型言語です。
ともかく使ってみよう
Glasgow Haskell Compiler
使い方としては3通り
シェルやコマンドプロンプトやPowerShellで
ghci
で起動する~はず~ ghciが起動すると最初にPrelude.hsという標準ライブラリが読み込まれる。
ghci で
> 1 + 2 * 3
と打ってみよう
7
が返ってくる 結合順も
1 + (2 * 3)
になってますね?
色々、試してみましょう
> True && True True > False || True True > not False True > 5 == 5 True > 5 /= 5 False > "hello" == "hello" True
他の言語を触ったことのある人には違和感のない結果だと思います。≠が/=が変わってるくらいかな。
> let x = 10
これで x という名前に 10 が束縛された
> x -- Enter 10
これで x 自体が評価される
Haskellでの変数は一回定義した後は不変。
だけど、ghciでもう一度定義し直すと
> let x = 20 x -- Enter 20
上書きされている?
これは、前に定義されたxが隠されて新しいxが見えている。
この状態で、
> x + 20 40
これは 20 + 20 と同等である
hello.hs を用意して
double x = x + x
とでも書いておく
数値xを引数に取って、x+xを結果を返すdoubleという関数です。
:l hello.hs
とすれば OK
> double 3 -- Enter 6
になったはず
-- から行末まではコメントですプログラムの実行には影響を与えません。
副作用を持たない関数のこと。 ある関数を同じ引数で呼び出したら、同じ値が返ってくることが保証されるのが純粋関数。
関数は引数を適用してなにか返り値を得ることが目的。
副作用は複雑さをもたらすので、それを回避することはバグの低減に繋がる。
先ほど定義した関数doubleは純粋な関数。
-- 再掲 double x = x + x
何回double 3しても同じ結果が返ってきますね?
文字列をコンソールに出力する関数putStrLnだと
putStrLn "文字列"
これは副作用が目的の関数。
コンソールに文字を表示するように命令するけど、それが成功するかどうかはHaskellプログラムの関知するところじゃない。
Haskellは、この関数について情報を得られないので返り値は意味を持たない。
じゃあ、副作用が存在するじゃん。何が純粋関数型だ!という事ですが、この副作用を分離することで、純粋関数を有効に用いたプログラムが書ける。だから、Haskellは純粋関数型言語。~ということだと思ってる。~
Haskellの仕様には副作用についての記述はなく,それを切り離してるからHaskellは紛れもなく純粋関数型言語だ!! というご指摘いただきました。ありがとうございました。
遅延評価を用いる関数型言語の標準となるべくHaskellは作られました。
関数を適用するときに引数をどのように評価するかのことを評価戦略といいます。
それの一種が遅延評価です。
hello.hsに
square x = x * x
という関数を追加で定義してみる。
ghciで
> :r
で前回読み込んだファイルを再読み込みできる。 もしくは、
> :l hello.hs
引数を先に評価する戦略。多くの言語で使われている。
square (3 + 5) square 8 8 * 8 64
引数に渡された式が先に評価される。 最内簡約とも呼ばれる。
引数の評価をできるだけ後伸ばしにする評価戦略。
square (3 + 5) (3 + 5) * (3 + 5) 8 * 8 64
最外簡約
どちらにしても結果は変わらない。関数が停止可能なとき、遅延評価(名前呼び出し)では必ず停止するという性質がある。
Haskellでは、計算できる要素のことを式(expression)と呼ぶ。 Haskellではもうすべてが式、文(statement)~のようにみえるもの~でさえ式。
((1 + 2) * 3) + 4)
そして全体 ((1 + 2) * 3) + 4)も式
さらに、+も、、式
その式の答え、計算の結果のことを値(value)と呼ぶ。 これ以上計算しようのない式のこと。
値であるなら式でもある
すべての式は何らかの型に結び付けられている。 だからもちろん全ての値はそれぞれの型をもつってわけ。
42 :: Int 'a' :: Char "string" :: String "a" :: String [1,2,3] :: [Int] (1,'a') :: (Int,Char)
この表記方法のことを型シグネチャとか型注釈とか言います。
全くの別世界。同じ記号とか使ってても別物。
ghciで:tに続けて式を書くと型を教えてくれる。
> :t 1 + 2 1+2 :: Num a => a
数値型であることが確認できる。この=>とかaはあとで説明します!!よろしくお願いします。
有効な値がないことを示す型。 ()という値だけを持つ
() :: ()
値の世界と型の世界は別。 適切に定義された関数では、この型は副作用を持つ関数にしかあらわれない。
すべての式が型を持つって言ったよね? じゃあ+の型は何なんだろう?
入力としてT1という型を取って出力としてT2という型を返す関数の型は、
T1 -> T2
と表現される。 だからIntを取ってIntを返す関数の型は
Int -> Int
+の型を確認してみてください。
> :t (+)
Haskellのリストは単一の型の要素だけを持てる。
など。決して 混ぜられない。
[1, 2, 3, 4 ] -- :: Num t => [t] ['a','b','c'] -- :: [Char] [] -- :: [a]
[1..4]
でも1から4までのリストを表現できる。
['a'..'z']
違う型でも一緒に括れるデータ構造、それがタプル。 括る数、括る型でそれぞれの型も異なったものとなる。
(1, "abc") -- :: Num t => (t, [Char]) ("abc", 1) -- :: Num t => ([Cahr], t) ("abc",'a') -- :: ([Char], Char) (1,2,3,4,5) -- :: (Num t, Num t1, Num t2, ..) => (t,t1,t2,t3,t4) [1,2,3,4,5] -- :: Num t => [t]
これが全部、違う型
(1, "abc") -- :: (数,文字列) ("abc", 1) -- :: (文字列,数) ("abc",'a') -- :: (文字列,文字) (1,2,3) -- :: (数,数,数) (1,2,3,4,5) -- :: (数,数,数,数,数) [1,2,3,4,5] -- :: [数]
全部、違う型
head [1,2,3,4] -- 1 head "abc" -- 'a'
これでリストの先頭の 1 が取れる
tail [1,2,3,4] -- [2,3,4] tail "abc" -- "bc"
これでリストの先頭以外の残り [2,3,4] が取れる 尻尾というか本体みたいな感じある
last [1..4] -- 4 init [1..4] -- [1,2,3]
1 : [2 3 4] -- [1,2,3,4] (:) 1 [2 3 4] -- [1,2,3,4]
1 つの要素とリストを連結して新しいリストを作る Haskellのcons
これは中置演算子として定義されている。 左辺が第1引数、右辺が第2引数となる2引数を取る関数。
+とか*とかと同じですね。
Haskellでは、 中置演算子として定義されている関数は、括弧()で囲むことで通常の関数として使える。
1 + 2 -- 3 (+) 1 2 -- 3 ((+) 1 2) -- 3 ((+) ((*) 3 5) 2) -- 17
逆に、2引数を取る関数があると、それをバッククォートで囲むことで中置演算子として使える。
f x y = x + y f 1 2 -- 3 1 `f` 2 -- 3
[1,2,3,4]
というリストは、実は
1 :(2 :(3 :(4 :[])))
という形になっている
[1,2,3] ++ [3,4,5] [1,2,3,3,4,5]
リストの連結は(++)で行える。 が、この操作は前方のリストを最初から最後まで走査するコストがかかるので 使えるかぎり(:)を使うといい。
f :: Int -> Int -- 型の世界(型注釈) f x = x + 1 -- 値の世界
これで引数 x に 1 を足す関数 f が定義された。Intを取ってIntを返す関数という型注釈をつけてみた。
f 2 -- > 3 f 2.0 -- > 型エラー
これはIntを取ってIntを返す関数として定義されたので、 浮動小数点数は受け付けない。 型で弾く(安心)
型注釈をつけなくてもコンパイラは賢いので推論してくれる。だけど、人間はコンパイラほど賢くないのでコンパイラに意図を伝えておくために 極力型注釈は付けるようにすべき。
Num a => a
とか
Fractional a => a
とかの表記についてです。
型クラスとは、振る舞いを定義するインタフェース。ある型クラスのインスタンスであるということは、要求される関数を定義している型であるということ。
ここでのaは実際の何らかの型を表す変数、型変数。 多相的に型を扱うために使われる。
リストは単一の型だけを保持できるデータ構造でした。リストは保持している型によってそれぞれの型が違う。
[1,2,3,4,5] :: Num t => [t] ['a','b','c'] :: [Char] [1.0,2.0,3.0] :: Fractional t => [t]
例えば、headはリスト先頭の要素を返す関数だったけど、関数は式で、式は型に結び付けられている。1つのheadという関数で全てのリストに対する定義するためには型変数がいい働きをしてくれる。
:t head head :: [a] -> a
これはどんな型でもいいからaという型を包んだリストを取ってその包まれたaという型を返す関数
型変数としてはaとかbとかcとかがよく使われる。
もうひとつリスト連結関数(++)を見ておきましょう。
[1..5] ++ ['a','b','c'] -- だめ。 > :t (++) (++) :: [a] -> [a] -> [a]
aは実際の何らかの型を表す。
になって、型があわない。
Num a => a
Fractional a => a
=>の左側を型制約と呼ぶ。
:t 30 :: Num a => a
型変数aはNum型クラスのインスタンスである、という制約を加えている。インスタンスになるためにはそのクラスが既定している関数を実装する必要がある。この制約によって、Num型クラスが定義を要求する関数をaが備えた型だということをコンパイラに伝えられる。
Numは数の型クラス
これのインスタンスになるためには以下を要求する
今まで見てきたように数字リテラル1とかは多相定数としてこのNumクラスの任意のインスタンスとして振る舞う
:t 20 20 :: Num t => t
Fractionalは分数の型クラス
を要求する Float,Doubleがこのインスタンス
等値比較のための型クラス Haskellの基本型は全てこの型クラスのインスタンス。
各型クラスの要件はghciで
:i Num
のようにして知ることができる。
λ式を使って無名関数を作れる。
\x -> x + 1
名前の無い関数。目を細めれば\はλに みえてきた…?
もしもその関数に f という名前が付いていれば
f 1
で良い
ならば
(\x -> x + 1) 1 -- > 2
で良い
if True then 1 else 2 -- > 1
elseは必須
if False then 3 else 4 -- > 4
if 1 == 2 then 10 else 20 -- > 20
if-then-elseは遅延評価のHaskellでは関数で問題なく定義できる。 だけどHaskellのifはcase式へのdesugarになってる。 理由は
関数の本体を引数の形で場合分けできる。 数値、文字、リスト、タプル、Boolなど多くの型で使える。
isThree :: Int -> Bool isThree 3 = True isThree _ = False
factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial (n - 1)
head' x:xs = x head' [] = []
どれにも合わないパターンがあると例外が飛ぶ 。 オプションつければghcが叱ってくれる。
引数の形が同一でもさらに条件を絞って場合分けできる。
max' :: (Ord a) => a -> a -> a max' a b | a <= b = b | otherwise = a
=の左側がBool型の値となる式です。 otherwiseってTrueです。 上側から順に合致するまで下っていきます。 合致したところのみ関数本体が評価されます。
head [] -- > Exception! tail [] -- > Exception! init [] -- > Exception! last [] -- > Exception!
全域関数にはなっていない。 では、これを全域関数で定義し直しましょう。
型のことがわかったので、同一の型となるようにそれぞれ定義してみましょう。
tail' x:xs = xs tail' [] = []
これはパターンマッチの練習に。
こういう関数が欲しい。ていうかあったけど忘れた。 …というときの探し方。 hoogleにアクセス。 欲しい関数の型は
[a] -> Int -> a
に類するような型
まさにこれな(!!) という関数がある これは中置演算子とし定義されているので
[10,20,30,40] !! 2 -- > 30
これでOK!
全要素に特定の関数を適用した結果のリストを得る
map (\x -> x * 2) [1,2,3] -- > [2,4,6]
全要素を特定の関数で右から畳み込む
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr (+) 0 [1,2,3] -- > 6
もともと渡したリストは
[1,2,3] (1:(2:(3:([]))))
この:を引数として渡した2引数を取る関数に[]を引数として渡した初期値に置き換える
(1+(2+(3+(0))))
これがfoldr
foldl (+) 0 [1,2,3] -- > 6 ((((0)+1)+2)+3)
これが左畳み込みfoldrを使うべきかfoldlを使うべきかは処理の効率を考えて
これは、また今度
foldr (\x y -> x * 10 + y) 0 [1,2,3]
これは
x # y = x * 10 + y
を
(1:(2:(3:([]))))
に対して適用させることなので、
(1 #(2 #(3 #(0))))
と等価なので 60 となる
foldl (#) 0 [1,2,3]
だと
(((0 # 1)# 2)# 3)
と等価なので 123になる。
遅延評価の仕組みのおかげで無限長のリストを扱える
repeat 1 -- > [1,1,1,1,1,1,1 ...)
このままでは ghciが永遠出力し続ける (Ctrl + C で中断可能)
与えられた引数を無限に要素に持つリストを返す
repeat 10 -- > [10,10,10, ...]
与えられた引数のリストを繰り返す無限長リストを返す
cycle [1,2,3] -- > [1,2,3,1,2,3,1,2,3 ...]
与えられた関数を繰り返し適用して得られる要素が並ぶリストを返す
iterate (/x -> x * 10) 1 -- > [1,10,100,1000,10000 ...] iterate (/x -> + x 1) 0 -- > [0,1,2,3,4 ...]
take 5 $ repeat 1 -- > [1,1,1,1,1] take 10 $ cycle [3 2 1] -- > [3,2,1,3,2,1,3,2,1,3]
もちろん take は有限長のリストにも使える $は開き括弧と無限遠方にある閉じ括弧のペアだと思うといい。
どのように定義されているかを見たければhoogleへ!
take 5 $ [1,2,3,4,5,6,7] -- > [1,2,3,4,5]
and' :: [Bool] -> Bool and' xs = foldr (&&) True xs
例えば
[True,True,False,True]
を与えてみると,このリストは表記を変えると
True :(True:(False:(True:([]))))
なので定石通り,foldrを適用して
True (&&)(True (&&)(False(&&)(True(&&)(True)))) True (&&)(True (&&)(False(&&)(True && True))) True (&&)(True (&&)(False && True)) True (&&)(True && False) True && False False
となる
これを無限リストに適応すると
and' $ repeat False -- and' $ [False, False, False ...] -- False (&&)(False (&&)(False ... False
&&は引数1つ目がFalseなら後ろを評価せず停止するように定義されているんです。
(&&) :: Bool -> Bool -> Bool True && True = True _ && _ = False
遅延評価だからそこで停止するんです。 正格評価だと引数の評価をすべて先にしにいくので止まりません。
関数を使って2つのリストを1つにする
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (+) [1..9] [9,8..1]
全要素を特定の関数でフィルタリングして残ったもののみで構成されたリストを得る
:t filter filter :: (a -> Bool) -> [a] -> [a]
filter (\x -> x >= 5) [1..9] -- > [5,6,7,8,9]
"abc" -- :: [Char] "" -- :: [Char] [] -- :: [a]
> :t "abc" "abc" :: [Char] > :t 'a' 'a' :: Char
"abc" ++ "def" -- > "abcdef"
Int からCharを作る関数が欲しいなら その型はInt -> Charなので hoogleで調べる。 Char -> Intもついでに。
intToDigit 1 -- > '1'
putStrLn "Hello World!"
println は文字列を行として標準出力に表示する関数
:t putStrLn putStrLn :: String -> IO ()
Unitの結果をもたらすAction型
モナドと呼ばれる型クラスのインスタンス。 モナド。恐ろしい名前
しかし、ただの型クラスだ。
Data.Mapに定義されている 一般的には連想リストと呼ばれてるものだけど、Haskellのリストは使われてないので、 Mapと呼ぶこと。 自前でリストで定義するより高速になるように作られている。 PreludeやData.Listと競合する名前が使われているので修飾インポートする
import qualified Data.Map as Map
number = Map.fromList [(1,"one"), (2,"two"), (3, "three")] Map.lookup number 1 -- "one"
上記の例は 1 というキーの値が "one",2 というキーの値が "two" のマップである.
ディレクトリを作って
mkdir appdir
cabalファイルを作って
vim app.cabal
Name: MyApp Version: 0.0 Description: Build-Type: Simple Cabal-Version: >=1.2 Executable MyAppExecutable Main-is: main.hs Build-Depends: base >= 2.0 , free-game >= 1.0
cabal setup cabal build
main という関数が main 関数 (Moduleを記述するとMainというモジュールのmain関数を探しに行く) ここから起動する
module Main where import System.Environment (getArgs) main :: IO () main = do args <- getArgs putStrLn ("Hello, " ++ args !! 0)
Haskellの全てのプログラムは Mainモジュールのmainアクションから実行開始する。
module名は一文字目大文字 定義名は一文字目小文字
ユニットの結果をもたらすAction型
モナドと呼ばれる型クラスのインスタンス。 モナド。恐ろしい名前 次のように改造
./MyAppExecutable
Hello, World!
ビルド
cabal build
./a.out World!
Hello, World!
Common Build And Tool
Haskell用のビルド、パッケージングシステム。 Haskellのpackage manager gemとかに類するもの 欲しいパッケージがあればこれを使って簡単に導入できる。