純粋関数型プログラミング言語Haskellを触ってみました。(『7つの言語 7つの世界』第7章)

7つの言語 7つの世界』(オーム社)の第7章で紹介されているHaskell(ハスケル)です。 

Haskell
http://www.haskell.org/haskellwiki/Haskell

1.Haskellについて

 Haskellは純粋関数型プログラミング言語です。「すごいH本」が出てWeb上で話題になったり、「関数型言語を学ぶならHaskellですよ!」と同僚に勧められたりと公私ともに非常に熱い言語のようです。

 他の関数型言語がオブジェクト指向言語の要素を取り入れたりするなど、関数言語としては非純粋ではないため、「純粋」なHaskellは関数型言語を学ぶのに向いているということなのでしょうか。
 

2.Haskellのインストール

 以下を参考にしました。Homebrewでもインストール可能なようですが、バージョンがやや古くなるそうなので、The Haskell Platformからインストーラー(.pkg)をダウンロードしてインストールしました。私はすでにインストールしていたので特に問題ありませんでしたが、事前にXcodeをインストールしておく必要があるそうです。
Haskell環境構築:Mac OS X – Google グループ
The Haskell Platform
 

3.Hello,World

ターミナルで「ghci」と打つとHaskellのシェルが起動します。

$ ghci
GHCi, version 7.4.1: http://www.haskell.org/ghc/  😕 for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude>

終了は「:quit」。

Prelude> :quit
Leaving GHCi.

 
 
 ではではということで、数値や文字列を入力してみます。

Prelude> 1+1
2
Prelude> "Hello, World"
"Hello, World"
Prelude> "1"+"1"

<interactive>:4:4:
    No instance for (Num [Char])
      arising from a use of `+'
    Possible fix: add an instance declaration for (Num [Char])
    In the expression: "1" + "1"
    In an equation for `it': it = "1" + "1"
Prelude> "1" ++ "1"
"11"

  
 可変長なデータセットであるリスト

Prelude> [1,2]
[1,2]
Prelude> ["a","b"]
["a","b"]
Prelude> ['H','e','l','l','o',',','W','o','r','l','d']
"Hello,World"
Prelude> "Hello,World" ==  ['H','e','l','l','o',',','W','o','r','l','d']
True

 リスト内でシングルクォーテーションで囲うと一つの文字列として返されます。つまり、文字列も単なる文字のリストということらしいです。

 固定長なデータセットであるタプル

Prelude> (1,2,3)
(1,2,3)
Prelude> ("a","b","c")
("a","b","c")

 

4. 変数

 
 変数の定義。変数は変更可能らしい。

Prelude> let x = 1
Prelude> x
1
Prelude> let x = 2
Prelude> x
2

 

5. 外部ファイルとしてのプログラム作成とコンパイル、ロード

 
 モジュールdouble.hsのコンパイルとロード。

Prelude> :load double.hs
[1 of 1] Compiling Main             ( double.hs, interpreted )
Ok, modules loaded: Main.

 上でコンパイルしたファイルの実行。

*Main> double 5
10

 

6. 関数

6.1. 関数の定義

 add という関数を定義してみる。

Prelude> let add n= 1 + n
Prelude> add 2
3
 

 外部ファイルとして作成し、プログラムをコンパイルして上のプログラムを実行してみます。

add n = n + 1

 上を”add.hs”というファイル名で保存し、コンパイルして読み込む。

Prelude>:load add.hs
[1 of 1] Compiling Main             ( add.hs, interpreted )
Ok, modules loaded: Main.
*Main> add 1
2

 

6.2. 再帰

 階乗を求める。

Prelude> let fac x = if x == 0 then 1 else fac (x -1)*x
Prelude> fac 1
1
Prelude> fac 5
120
Prelude> fac 100
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

 

6.3. カリー化

 Haskellでは、「全ての関数は常に1つの引数をとる」のだそうです。つまり、1つの関数で2つ以上の引数をとることができないということです。「2つ以上の引数を使う関数を使いたい場合はどうするの?」という疑問が当然生じてきます。2つの引数をとる関数は引数を1つずつ順番に受け取りながら処理をしていくのだそうです。

 例えば、こんな感じで引数を2つとる関数testを定義しています。

Prelude> let test  a b = a + b * 2
Prelude> test 2 4
10

 一見、上のtest関数は2つの引数をとるように見えますが、実はこの場合、

(test 2) 4

 と実行していることになるのだそうです。つまり、まず最初に引数2をとり、

test 2 

 を実行しますので、引数2が a に代入されて

2 + b*2

 となり、続けてbに引数4が代入されて

2 + 4*2

となり、10という結果が返ってきます。

 だらだらと書いてしまいたが、引数を一つずつ処理していくプロセスがカリー化と呼ばれるのだとか。
 

 

7.型クラス(type class)

 Haskellは型推論を行ってくれるので、特に型を定義をする必要はありません。しかし、プログラマがきちんと型付けしたい場合もあります。そんなときは

Prelude> 1 :: Int
1

“:t”で上で定義した「型」を確認することもでき・・・・・

Prelude> :t 1
1 :: Num a => a

 Intと型を定義したのにNumが返ってきます。以下によるとNumは型クラス(type class)で、Intは(インスタンス)なのだそうです。“:t”では上で「型」と書いてしまいましたが、型クラスを返すみたいですね。

10分で学ぶHaskell – HaskellWiki
オブジェクト指向から理解する型クラス – think and error
 
 次は文字列です。
 
 文字列の場合は、StringというCharというがあるようです。ちなみに以下によるとChar型とString型はPreludeモジュールで定義されたRead、Show、Eq、 Ord、Enum および Boundedという型クラスの型(インスタンス)のようです。
The Haskell 98 Report: 定義ずみの型とクラス
Prelude Standard types, classes and related functions

  Charは文字、Stringは文字列を示す型なのだそうで、ダブルクォーテーションで囲ってしまうと文字列になってしまうらしく、Charという型では定義できません。シングルクォーテーションで囲った1文字の文字であればCharで定義できます。

Prelude> "a" :: String
"a"
Prelude> :t "a"
"a" :: [Char]
Prelude> 'b'::Char
'b'
Prelude> :t 'b'
'b' :: Char
Prelude> "b"::Char

<interactive>:21:1:
    Couldn't match expected type `Char' with actual type `[Char]'
    In the expression: "b" :: Char
    In an equation for `it': it = "b" :: Char
Prelude> 'abc'::Char

<interactive>:20:1:
    Syntax error on 'abc'
    Perhaps you intended to use -XTemplateHaskell

 しかし、文字列”a”は「3.Hello,World」で少し触れたように[‘a’]というリストでもあるわけですので、リストの要素として扱えばCharになるんでしょうか、たぶん。

  Haskellが組み込みの型クラスと型はPreludeモジュールで定義されているようです。その型クラスと型は上で一度紹介しましたが、以下で一覧されています。
Prelude Standard types, classes and related functions

 Slideshareでは型クラスと型についてわかりやすく紹介している発表資料がいくつもありましたので、そのうち2つを掲載します(一つは上で紹介したものですが)。

関数型プログラミング言語でLisp方言の1つのClojureを触ってみました。(『7つの言語 7つの世界』第7章)

7つの言語 7つの世界』(オーム社)の第7章で紹介されているClojureです。

Clojure
http://clojure.org/

1. Clojureについて

  Clojureは関数型言語Lisp方言の1つです。Lipsは非常に歴史が古い言語であるため、ClojureのほかにEmacs Lisp、Common Lisp、Schemeなど多くの方言をもっています。Lispがもつ高い柔軟性とJavaとの統合性の高さから(あと括弧が他の方言と比べると少ないらしい)、多々あるLisp方言のなかでもっとも成功しているとされるClojureを『7つの言語 7つの世界』は選択しています。

 ここではClojureの紹介をしていくつもりですが、私にとって初めてさわる「Lisp」であり、Lispとしての特徴とClojureとしての特徴の区別がほとんどつかないため、Lispの特徴とClojureの特徴が混在した紹介にならざるを得ませんでした。ご了承ください。

 なお、他の言語もそうですが、Clojureでは 『7つの言語 7つの世界』だけではなく、Web上の掲載されている様々な情報を参考にさせていただきました。

2. Clojureのインストール

 Web上にはいろいろなインストール方法が紹介されていましたが、MacOSX Lionへのインストールの場合、HomeBrewでLeiningenをインストールすれば必要なものがもろもろインストールされて一発でした。

$ brew install leiningen

3. Hello,World

 起動と終了は以下の通り。終了方法は最初にそれを知った時に「なるほど、そうか!」という気分になりますね。

・起動

$ lein repl
REPL started; server listening on localhost port ○○○○○
user=>

 

・終了

user=> (exit)
$

 

文字列はダブルクォートで囲います。

user=> (println "Hello,World")
Hello,World
nil

 

 制御構文はこんな感じ。

user=> (if (> 2 1) (println "2"))
2
nil

 

演算

user=> (+ 1 1)
2
user=> (- 2 1)
1
user=> (+ 1 2 3)
6
user=> (* (+ 1 1)(- 5 1))
8

上はそれぞれ

  • (+ 1 1) → 1+1
  • (- 2 1) → 2-1
  • (+ 1 2 3) → 1+2+3
  • (* (+ 1 1)(- 5 1)) → (1+1)*(5-1)
  • をあらわしています。

4. リスト

Lispではリストは以下のように丸括弧( )で括弧って要素を空白で区切って記述します。

(要素 要素 要素 ・・・)

 つまり・・

 (1 2 3)

というデータセットも

 (println “2”)

というコードも同じ記述方法で記述します。Lispはあらゆるものがリストだと言われ、データ以外のコードも必要に応じてリストとして処理することができます。これがLispが強力なメタプログラミング機能を持つ理由の1つであるようです。

 コードの記述はリストの最初の要素として関数や演算子などを記述する前置記法と呼ばれる記述方法で記述します。残りの要素は引数です。

 (関数 引数 引数・・) 
   ex. (println “2”)

 (演算子 引数 引数・・・)
   ex. (+ 1 1)

 データのセットとしてリストを記述したい場合に

 (1 2 3)

とそのまま記述してしまうと最初の要素が関数と評価されてエラーになってしまいます。
 
 list関数を使用するか、括弧の前にアポストロフィ(‘)を置きます。

user=> (list 1 2 3)
(1 2 3)
user=> '(1 2 3)
(1 2 3)

 ちなみに式とコードと混同しやすいため、Clojureでは慣習的に式はリストで、データのセットは後述するペクタを使用するそうです。
 

5. ベクタ・セット・マップ

 
 ペクタ(vector。「ベクトル」と呼ぶ人も)という角括弧[ ]で記述するデータセットです。また、角括弧を使用せずとも、リストでvector関数を使用してもベクタを生成できます。

user=> [1,2,3]
[1 2 3]
user=> (vector 1 2 3)
[1 2 3]

 慣習的な使い分けはともかく、その機能を見る限り、データセットとして使用する場合のリストとベクタの違いがよくわからなかったのですが、内部構造でのデータの持ち方が異なるようです。

 リストとベクタは要素の順序が重要な意味を持ちますが、意味を持たないセットというデータセットもあります。

user=> #{1 2 3}
#{1 2 3}

 
  キーと値を対応づけるRubyでいうハッシュに相当するデータセットがマップです。上のセットというデータセットと同じ{ }で囲いますが、{キー 値 キー 値}という順序で並んでいます。キーはコロンをつけたシンボルが使用されます。

user=> {:a "a" :b "b"}
{:a "a", :b "b"}
user=> {:a 1 :b 1}
{:a 1, :b 1}

  読みづらいということで、Clojureではキーと値ごとに区切り記号としてカンマをいれることも可能です。

user=> {:a "a", :b "b"}
{:a "a", :b "b"}
user=> {:a 1, :b 1}
{:a 1, :b 1}

 

6.シーケンス

シーケンスとは・・・

 とかけるほどシーケンスのことをよく理解していませんが、リスト、ベクタ、セット、マップ、文字列、ファイルシステム構造をラップして共通のインターフェイスを提供する抽象化されたなにか(型?)のようです。共通の関数が使える、ということですかね。 

user=> (count (list 1 2 3))
3
user=> (count [1 2 3])
3
user=> (count #{1 2 3})
3
user=> (count "abc")
3

 

7.変数

 Clojureでの変数の定義以下を読むとdef関数を使用するようです。

Clojure – vars

user=> (def a 1)
#'user/a
user=> a
1

一度変数を定義しても変更可能な(mutable)ようです。

user=> (def a 2)
#'user/a
user=> a
2

  ソフトウェアトランザクショナルメモリを使用して同じ変更可能な場所をより安全に参照するためにデータをラップするref関数というものもあるようです。

user=>  (ref "Totoro")
#<ref@1726c5a5: "Totoro">

  
ref関数でラップした値をdef関数で変数とひもづけることで参照することができるようになります。

user=>  (def movie (ref "Totoro"))
#'user/movie
user=> @movie
"Totoro"
user=> (deref movie)
"Totoro"

 参照するときは変数の頭に@をつけるか、deref関数を使用します。
参考: Clojure – refs

 ref関数でラップしたデータは同じトランザクションのスコープ内でしか変更することができませんが、そのスコープ外でデータをラップするためにアトムといものがClojureにはあります。

user=>  (atom "Totoro")
#<atom@762d80ae: "Totoro">
user=>  (def movie (atom "Totoro"))
#'user/movie
user=> movie
#<atom@54b4b0a4: "Totoro">
user=> @movie
"Totoro"

8.関数

 defn関数で関数を定義することができます

user=> (defn hello [x] (str "Hello," x))
#'user/hello
user=> (hello "world")
"Hello,world"

 

 defn関数は引数をうけないとエラーを検出していまうようですが、引数を受け取らない関数を定義する場合は、fn関数では無名関数をつくり、それをdef関数で変数に代入?する方法があるようです。

user=> (def hello_world (fn [] (println "Hello,World")))
#'user/hello_world
user=> (hello_world)
Hello,World
nil

参考
プログラミングClojure 勉強メモ 第1章 Getting Started – Life is a Role Playing Game
関数 – Closer to Clojure
untitled: 『プログラミング Clojure』 で勉強 第2章 – 2