10分で学ぶHaskell

From HaskellWiki
Revision as of 01:59, 5 December 2009 by Ymotongpoo (talk | contribs)
Jump to: navigation, search

概要

Haskellは関数型で(つまりすべてが関数呼びだしで処理される)、静的な暗黙的型付けで(typeはコンパイラによって確認され、明示的に宣言する必要はない)、遅延評価(必要となるまで処理されない)の言語です。系統が近い言語として最も人気のあるのはおそらくML系の言語でしょう。(MLは遅延評価ではないですが)

最も普及しているHaskellのコンパイラは GHC です。GHCは http://www.haskell.org/ghc/download.html からダウンロードできます。GHCのバイナリは GNU/Linux FreeBSD MacOSWindowsSolarisで動作します。GHCをインストールすると、ghc ghciという2つのプログラムが入っているのが確認できます。最初のghcの方はHaskellのライブラリやアプリケーションをバイナリコードにコンパイルします。 ghciはインタプリタで、Haskellコードを書いてすぐに結果を得ることができる環境です。

簡潔な表現

たいていの数学的表現は直接ghciに入力して結果を得ることができます。Prelude>はGHCiのデフォルトのプロンプトです。

 Prelude> 3 * 5
 15
 Prelude> 4 ^ 2 - 1
 15
 Prelude> (1 - 5)^(3 * 2 - 4)
 16

文字列は"ダブルクォート(二重引用符)"で囲みます。文字列の結合をするときは++を使います。

 Prelude> "Hello"
 "Hello"
 Prelude> "Hello" ++ ", Haskell"
 "Hello, Haskell"

functionを呼び出すときは関数の後に直接引数を並べて行います。関数呼び出しに括弧は必要ありません。こんな感じです:

 Prelude> succ 5
 6
 Prelude> truncate 6.59
 6
 Prelude> round 6.59
 7
 Prelude> sqrt 2
 1.4142135623730951
 Prelude> not (5 < 3)
 True
 Prelude> gcd 21 14
 7

コンソール

I/O actionsはコンソールからの入出力を行うのに使います。有名なものの例は:

 Prelude> putStrLn "Hello, Haskell"
 Hello, Haskell
 Prelude> putStr "No newline"
 No newlinePrelude> print (5 + 4)
 9
 Prelude> print (1 < 2)
 True

putStr関数 と putStrLn関数 は文字列をターミナルに出力します。print関数はどんな型の値でも出力します。(文字列をprintするときは、引用符が前後につきます)

ひとつの表現の中で複数の I/O アクションを使いたい場合は、doブロックを使います。アクションはセミコロンで区切られます。

 Prelude> do { putStr "2 + 2 = " ; print (2 + 2) }
 2 + 2 = 4
 Prelude> do { putStrLn "ABCDE" ; putStrLn "12345" }
 ABCDE
 12345

読み込みは getLineStringを返します)かreadLn(どんな型の値でも返します)でできます。<-シンボルはI/Oアクションの結果に名前を束縛するために使います。

 Prelude> do { n <- readLn ; print (n^2) }
 4
 16

(4が入力で16が結果です。)

実際にはdoブロックを書くには別の方法があります。Haskellでは中括弧やセミコロンをあまり使わないので、インデントが重要になります。ghciでは書きにくいかもしれないので、ソースファイル(例えばTest.hs)に書いてビルドしてみましょう。

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

ビルドは ghc --make Test.hs でできます。実行ファイル Test ができるはずです。(WindowsではTest.exeとなります)ここでif式も一緒に習得できてしまいましたね。

doの直後の最初の空白でない文字は特別な文字です。上の例の場合ではputStrLnの先頭のpがそれです。doブロック内のすべての行はそのpと同じ列から開始します。もしより深くインデントすれば、それ以前の行の一部となります。逆に浅くインデントすれば、doブロックはそこで終わります。これは"レイアウト"と呼ばれ、Haskellでは、いつも行の終了文字や中括弧を使うのは面倒なので、代わりにレイアウトを使います。(then句とelse句はこれが理由でインデントされなければならないのです。もしifと同じ列から開始した場合、別の行と解釈されてしまって、エラーとなります。)

(Note: レイアウトを使う場合はインデントにタブを用いないでください。技術的にはタブが8文字であれば動作しますが、おすすめできません。また中にはそういう人もいるかもしれませんが、プロポーショナルフォントを使うこともおすすめしません。

簡潔な型

これまで、type宣言に関して全く触れてきませんでした。その必要がなかったのですが、それはHaskellが型推論を行うからです。一般的に型宣言はしたい場合以外はする必要がありません。型宣言をする場合は::を使って行います。

 Prelude> 5 :: Int
 5
 Prelude> 5 :: Double
 5.0

HaskellではType (そして後で触れる型class)は常に大文字から開始します。変数は常に小文字から始まります。これは言語仕様であり、命名規則(naming convention)ではありません。

ghciを使って、ある値がどんな型かを確認することもできます。普段、自分では型宣言をしないので、この機能は役に立ちます。

 Prelude> :t True
 True :: Bool
 Prelude> :t 'X'
 'X' :: Char
 Prelude> :t "Hello, Haskell"
 "Hello, Haskell" :: [Char]

(上の例を見て気づいたように、[Char]Stringの別名です。あとでをsection on listsを見てみましょう)

数字に関してはもっと面白いものが見られます。

 Prelude> :t 42
 42 :: (Num t) => t
 Prelude> :t 42.0
 42.0 :: (Fractional t) => t
 Prelude> :t gcd 15 20
 gcd 15 20 :: (Integral t) => t

これらの型は"型クラス"を用いています。つまり:

  • 42 はあらゆる数字型として使うことができます。(このおかげで5IntとしてもDoubleとしても宣言できたわけです)
  • 42.0 はどのような分数型にもなれますが、整数型にはなれません。
  • gcd 15 20 (ちなみにこれは関数呼び出しです)はどのような整数型にもなれますが、分数型にはなれません。

Haskellの"prelude"(初期状態でimportされるライブラリの一部)内には5種類の数値型があります。

  • Int は少なくとも30ビットの精度の整数です
  • Integer は精度無制限の整数型です
  • Float は単精度浮動小数点数です
  • Double は倍精度の浮動小数点数です
  • Rational は分数型で丸め誤差がありません

これら5つはすべて Num型クラスの インスタンス です。最初の2つはIntegralインスタンス で、残り3つはFractionalインスタンス です。

ごちゃまぜにしてみましょう。

 Prelude> gcd 42 35 :: Int
 7
 Prelude> gcd 42 35 :: Double
 
 <interactive>:1:0:
     No instance for (Integral Double)

最後に触れておいたほうがよい型は ()で表されるもので "Unit" と呼ばれます。この型はたった一つの値をとり、やはり()と書かれ "Unit" と呼ばれます。

 Prelude> ()
 ()
 Prelude> :t ()
 () :: ()

これはC言語系の言語でのvoidキーワードに似ています。もしなにも返り値を期待しない場合、I/Oアクションから()を返すことができます。

構造化されたデータ

基本データ型は2つの方法で容易にまとめることができます。一つはリストで [ かぎ括弧 ] で囲います。もう一つはタプルで ( 括弧 ) で囲みます。

リストは同じ型の複数の値を保持する場合に使われます。

 Prelude> [1, 2, 3]
 [1,2,3]
 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> [1, 3 .. 10]
 [1,3,5,7,9]
 Prelude> [True, False, True]
 [True,False,True]

文字列は単に文字のリストにすぎません。

 Prelude> ['H', 'e', 'l', 'l', 'o']
 "Hello"

:演算子は要素をリストの先頭に追加します。(Lisp系言語でいうところのcons関数です

 Prelude> 'C' : ['H', 'e', 'l', 'l', 'o']
 "CHello"

タプルは決まった数の値を保持できます。ただしそれぞれの型は異なっていても構いません。

 Prelude> (1, True)
 (1,True)
 Prelude> zip [1 .. 5] ['a' .. 'e']
 [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]

この例の最後でzipが使われています。これは2つのリストを1つのタプルのリストにするライブラリ関数です。

型は予想どおり以下のようになります。

 Prelude> :t ['a' .. 'c']
 ['a' .. 'c'] :: [Char]
 Prelude> :t [('x', True), ('y', False)]
 [('x', True), ('y', False)] :: [(Char, Bool)]

リストはHaskellではとてもよく使われます。リストを操作する便利な関数がいくつもあります。

 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> map (+ 2) [1 .. 5]
 [3,4,5,6,7]
 Prelude> filter (> 2) [1 .. 5]
 [3,4,5]

また整列されたペア(2つの要素を持つタプル)に対する便利な関数が2つあります。

 Prelude> fst (1, 2)
 1
 Prelude> snd (1, 2)
 2
 Prelude> map fst [(1, 2), (3, 4), (5, 6)]
 [1,3,5]

how to work on listsも参照してください。

Function 定義

mainと呼ばれるIO actionの定義について先に触れました。

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

では次に、実際にfactorialという関数のfunction"定義を書いてみることで、上の例を補ってみましょう。丁寧に書こうと思うので、モジュールヘッダも追加します。

module Main where

factorial n = if n == 0 then 1 else n * factorial (n - 1)

main = do putStrLn "What is 5! ?"
          x <- readLn
          if x == factorial 5
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

ghc --make Test.hsと入力し再ビルドします。そして実行します。

 $ ./Test
 What is 5! ?
 120
 You're right!

階数が得られました。ビルトイン関数のように、factorial 5と括弧なしで関数が呼び出せました。

今度はghciを使ってtypeを調べてみましょう。

 $ ghci Test.hs
 << GHCi banner >>
 Ok, modules loaded: Main.
 Prelude Main> :t factorial
 factorial :: (Num a) => a -> a

関数の型は引数の型とともに表され、その後に->が来て結果の型が書かれます。(結果も型クラスNumとなります)

階数は場合分けを使うことで簡単にできます。

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

便利な構文

さらにいくつかのsyntaxが便利です。

secsToWeeks secs = let perMinute = 60
                       perHour   = 60 * perMinute
                       perDay    = 24 * perHour
                       perWeek   =  7 * perDay
                   in  secs / perWeek

let式は一時的な名前を定義します。(ここでもレイアウトが使われています。好みによりますが、{ 中括弧 }とセミコロンによる区切りを使うこともできます)

classify age = case age of 0 -> "newborn"
                           1 -> "infant"
                           2 -> "toddler"
                           _ -> "senior citizen"

case式は複数の場合分けを行います。特別なラベル _ は"その他すべて"を表します。

ライブラリを使う

Everything used so far in this tutorial is part of the Prelude, which is the set of Haskell functions that are always there in any program.

The best road from here to becoming a very productive Haskell programmer (aside from practice!) is becoming familiar with other libraries that do the things you need. Documentation on the standard libraries is at http://haskell.org/ghc/docs/latest/html/libraries/. There are modules there with:

module Main where

import qualified Data.Map as M

errorsPerLine = M.fromList
    [ ("Chris", 472), ("Don", 100), ("Simon", -5) ]

main = do putStrLn "Who are you?"
          name <- getLine
          case M.lookup name errorsPerLine of
              Nothing -> putStrLn "I don't know you"
              Just n  -> do putStr "Errors per line: "
                            print n

The import says to use code from Data.Map and that it will be prefixed by M. (That's necessary because some of the functions have the same names as functions from the prelude. Most libraries don't need the as part.)

If you want something that's not in the standard library, try looking at http://hackage.haskell.org/packages/hackage.html or this wiki's applications and libraries page. This is a collection of many different libraries written by a lot of people for Haskell. Once you've got a library, extract it and switch into that directory and do this:

 runhaskell Setup configure
 runhaskell Setup build
 runhaskell Setup install

On a UNIX system, you may need to be root for that last part.

10分以上かかるので省いたトピック

Languages: en zh/cn ja