はすけるで遊ぶ

(2005.7.18 新規作成, 2009.1 更新。)

関数型プログラミングのための言語 Haskell について。

メモ

らむだ ruby -> haskell
(2008.7.19) Rubyist九州ミーティングで、Rubyプログラマ向けにHaskellを紹介しました。
Haskell vs Ruby
HaskellとRubyとで、プログラムの書き方がどのように変わるのか。
Haskell環境の用意
GHCなどのインストール
Haskell の基本型
組み込み型についてのメモ。
リストの操作
組み込みのリスト操作関数。
薔薇木
木に対する操作。
HaskellでFizzBuzz問題
小さな問題をHaskellで。
HaskellのファイルIO
ファイルから読み込んだりエラー処理について。
Haskellでデータベース操作
RDBMSにSQLを投げる。Haskell cabalパッケージの扱い。
Haskell での多態 (多相)
C++のテンプレートに近いHaskellの多態。Java/C++の抽象クラスのようなアドホック多相。

次のメモはC++メインですが、前半でHaskellのMaybeモナドを解説しています。

関数型プログラミング

(2008.11.7 この節を追加。)

例えばC言語でもオブジェクト指向プログラミングができます(gtk+など)が、プログラミング言語の支援があったほうが自然にプログラミングできます。

Haskell, あるいはほかの関数型プログラミング言語 (O'Caml, Concurrent Clean, Erlang など)は、関数型プログラミングを支援します。

関数型言語は、おおむね、

  • 関数が第一級のオブジェクト
  • 高階関数 (higher-order function) を多用
  • 変数の再代入を行わない
  • オブジェクトの書き換えを行わない
  • ループ構文がない
というような特徴があります。

変数とオブジェクトの書き換えをおこなわないことで、平行処理が容易にできる可能性があります (concurrent computing)。

Haskellは、上の特徴に加えて、式が遅延評価(lazy evaluation)されます。例えば、リストで最初の5要素しか必要でなければ、6番目以降の要素はそもそも計算されません。

リンク

はろーわーるど・コンパイル

(2008.11.9 この節更新。)

まずはお約束のはろーわーるどから。

Haskellソースはファイル名の拡張子を .hs にします。GHCでは、文字コードはUTF-8 限定です。UTF-8以外の場合、コメント以外の場所に書けるのはASCIIだけです。

文字列リテラルは内部ではUnicodeで保持されますが、入出力の文字コードを指定する方法は、組み込みでは用意されていないようです。GHC 6.8.2 だと、UTF-8でも出力できなくて、ちょっと泣けます。

追加ライブラリについては、このあたりを参照; blog.kfish.org: Survey: Haskell Unicode support

さて、ハローワールドですが、こんな感じです;

  1. {- Haskell "Hello World" -}
  2. main = putStrLn "Hello World!" -- putStrLn は文字列を出力する
  • コメントは「--」から行末までか「{-」から「-}」までの間です。
  • 「=」は関数の定義です。再定義(再代入)はできません。
  • mainが主関数です。main関数から実行が開始されます。
  • putStrLn は文字列を出力する関数です。関数呼び出しでは引数に括弧は付けません(付けてはいけません)。

コンパイルはghcコマンドです。オプションを何も付けないとリンクまでおこないます。コンパイルに成功すると a.out が出力されます。

$ ghc first.hs

インタプリタ

GHCには、インタプリタGHCiが付属している。ghciコマンドで起動できる。抜けるのは:quit。

$ ghci
   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 6.4, for Haskell 98.
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base-1.0 ... linking ... done.
Prelude> 1 + 2 * 3
7
Prelude> "abc" ++ "def"
"abcdef"
Prelude> [1] ++ [4]
[1,4]
Prelude> [1] ++ [2,3]
[1,2,3]
Prelude> :quit
Leaving GHCi.

予約語

Haskellの予約語は、

case | class | data | default | deriving | do | else | if | import | in | infix | infixl | infixr | instance | let | module | newtype | of | then | type | where | _

これだけ。大文字・小文字を区別する。

引数のパターンマッチ

Haskell では、関数の名前が同じで引数が違うものを複数定義できる。

次のスクリプトは、リストの値を順番に表示する。

[]は空リスト、[x..y]は、xからyまでの値を含むリスト。[1..]と書くと、1以上無限の値を含むリストになる。

関数fooをオーバーロードし、空リストのときは上の関数を実行し、そうでないときは下の関数を実行する。

x:xsの「:」は演算子で、最初の引数を二つ目の引数であるリストの前に加える。関数の引数としては、リストを先頭の値xと残りのリストxsに分ける。

showは値を文字列に変換する(表示用)。

main = putStrLn (foo [1..])

foo []     = ""
foo (x:xs) = (show x) ++ "\n" ++ foo xs

何気なく無限リストを作ったが、エラーにならずに表示できる。

ガード (guard)

(2005.9.3)

関数を定義するとき、引数のパターンマッチで書く以外に、条件式でどの定義を使うかを選択するようにできる。

次のサンプルは階乗を計算する。

factorial :: Integer -> Integer     -- 関数宣言

factorial 0 = 1
factorial n = n * factorial (n - 1)

main = putStr (show (factorial 10))

これは、次のようにも書ける。その他の場合の定義は、| otherwise = ... と書く。

factorial n 
  | n == 0 = 1
  | n > 0  = n * factorial (n - 1)

関数をつなげる

関数をつなげる (.) や ($) も関数(演算子)です。

(.) :: (b -> c) -> (a -> b) -> a -> c
g . f x は、x に対して、f して g する。
($) :: (a -> b) -> a -> b
f $ g $ h x = f (g (h x))

コマンドラインオプション

Haskellスクリプトをコンパイルして実行したときに、コマンドラインオプションを取れるようにしたい。

do ... は、手続き型言語のように上から順に実行する。<- は変数に代入する。

import System          -- getArgs

main = do
  args <- getArgs      -- argsは[String]型になる
                       --     x <- getLine と書くと、コンソールから読み込む
  putStrLn (show args)

実行結果はこう。

$ ./main.exe foo bar --list=hoge
["foo","bar","--list=hoge"]

推薦図書

『ふつうのHaskellプログラミング -- ふつうのプログラマのための関数型言語入門』

(2007.1.19)

Haskellの初歩から文法、勘所まで着実、丁寧に解説した、非常に優れた本。これを読めばHaskellを読み書きできるようになること間違いなし。

Haskellの考え方に触れることで、Java / C++ / Ruby / Pythonなどほかのプログラミング言語でプログラミングするときでも再利用しやすく、保守しやすいコードを書く参考になる。

お薦め。

外部リンク

Learn You a Haskell for Great Good!
新しいチュートリアル。素敵。
Real World Haskell
書籍のテキストを一般公開。実用的な例が豊富。