単語記事: Haskell

編集  

Haskellとは、純関数型プログラミングの一種である。

概要

Haskellという言名は論理学Haskell B. Curryの名前が由来。純関数型言語の標準化を的として制定されたといわれている。

静的型付けコンパイラで、型推論が利用できるため関数変数宣言を省略することがある程度は可である。プログラムの記述は、「等式の左辺は右辺の式へと変換できる」という関係性の定義を列挙していく形になる。

型付け以上に特筆すべきなのは関数型言語ということである。Haskellにおける「関数」とは、数学における「関数」と同じく、引数の値が関数の返り値と一意に対応し不変であるものに限定される。関数が内部状態を持つことは許可されない。変数の初期値は再代入によって変更することはできない。このような制約を持つため、プログラムの各部分の実行順序を任意に選ぶことができ、今後並列演算への応用も期待できる。

Haskellにおいて、標準入出を用いた処理など純な(数学における)関数表現では実現できない処理は「アクション」と呼ばれ「関数」とは区別しているが、両者を同一プログラム内で矛盾を起こさないように共存させる仕組みが提供されている。

変数関数のスコープは、中括弧などコードブロックを囲むような記号は使わず、Pythonなどのようにソースコードインデントの深さで表現される。

遅延評価を採用しており、リストを容易に扱うことができる。

実装と開発環境

HaskellのためのフリーコンパイラとしてはGHC(Glasgow Haskell Compiler)がデファクトスタンダード。他にはHugsなどがある。これらはEclipseからも利用することもできたが、現在プラグインEclipse FPと連携するパッケージ依存関係の変化に対応できず開発中止になっている。他の統合開発環境パッケージ依存関係を解決できなくてインストールもままならないことが多く、フリーの開発環境はもっぱらテキストエディタが中心になる。

マイナー実装だが、Java仮想マシン向けにFregeという実装が存在する。Haskellからは最低限のコード修正で移植とされている。

用途

間違いが発生しにくいとか、数学と相性が良いなどの理由からか、数字の計算に終始する融投資関係での利用が多いといわれている。

参照透過性とGUIとは相性が悪いのでゲーム開発には適さないと考えられているが、Haskellで書かれた日本で有名なゲームMonadiusというのがあり、開発が不可能ということはない。(GUIの処理ははOpenGLを利用したライブラリGLUTによるもの)

純粋関数型言語

Haskell関数型言語の中でも、関数や再代入による状態変更を副作用として排除するを選んだ関数型言語である。

プログラミングスタイルとしては、その特性を活かして実行順序に関係なく、関係性の定義を宣言的に記述していくことで自動的に結果が導き出される宣言プログラミングが推奨されている。従来のプログラミング言語とは全く違うその発想に、いわゆる命令プログラミングに親しんできた人が接すると、拒絶反応を起こすか熱心な信奉者になるかに二極化するらしい。

プログラミング言語が車だったら」のHaskellの項より選択抜

モナド

先述のようにHaskellが属する純関数型言語というのは副作用を排除するを選んだ言である。副作用とは再代入のように状態を変更することである。それくらいならたいした問題ではないのだが、たとえば「画面に文字を表示する」という通常のプログラミング言語ではごく基本的なことに分類されることでも、Haskellでは画面の「状態の変更」であるとみなされる。

従って、純関数型言語であるためにはひたすら計算のみを行い結果の画面表示すら行わないという滑稽な状態にならざるをえない。もちろん、こんなことでは使い物にならないのでHaskellモナドという仕組みを利用している。IOモナドというものがあり、副作用をIOモナドの処理に分離することにより、IOモナドの内部で純関数型言語であり続けるのである。

モナドHaskellにとって副作用を起こすために必要なものではあったが、副作用を起こす以外にも様々な用途のモナドがあり、いわゆるぬるぽの対処をするMaybeモナドが代表的。さらにはListまでもがモナドとして提供されている。

擬人化するなら

Haskell擬人化するなら、以下のような感じだろうか。

すべての具が造り付けで固定された部屋の中に引きこもる潔癖症の数学少女。部屋から出ないので近眼のメガネ属性。外部とのやりとりは、にあいたIOモナドという小さなから行う。

期限が来てからでないと仕事を始めない彼女のことを怠け者と評する人もいるが、その評価は正格ではない。すべてのものが固定された彼女の部屋には予めすべてのものがあるべき場所に「ある」のだから、必要になったときに手を伸ばせば事足りるのだ。これを可にするために常日頃から部屋を璧に整理整頓している彼女は、怠け者ではなく地な努なのである。だが引きこもりだ。

コード表記の例など

Hello worldの例

    main = putStrLn "Hello world!"

Haskellにおいて、文字列(String)は文字Char)のリストとして扱う。リストは列挙した要素群を'['と']'で囲み、各要素の間は','で区切る。
文字列"Hello world!" は文字リスト ['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] と同じもの。

左辺の「とする結果」に代入するために右辺の「"Hello world!"と画面に表示された状態」を計算して作り出すと考えるなら、先述の「的地として「信号のときのみ進行し、飛び出す子供に注意しながら、ブロック塀にかすることなく入った」が定できる」ということの意味も見えてくるかもしれない。

Schemeにおけるリスト表現で、

(1 . (2 . (3 .(4 . ()))))

と 

(1  2  3  4)

が同一のリストのを表わすように、

Haskellでは、

1 : (2 : (3 : (4 : [])))    ※丸括弧省略して、 1 : 2 : 3 : 4 : [] と書いてもよい

と 

[1, 2, 3, 4]

は同一のリストを表わす。 

Schemeのコード表記との比較例

 (記号「→」以降に式の評価結果を記す)

コードの説明 Haskell Scheme
リストの先頭要素取り出し

head  [1, 2, 3]
→ 1
(car  '(1 2 3))
→ 1
リストの先頭要素を除いた残り部分の取り出し

tail  [1, 2, 3]
→ [2, 3]
(cdr  '(1 2 3))
→ '(2 3)
リストに先頭要素を追加
1 : [2, 3]
→ [1,2,3]
(cons 1 '(2 3))
→ '(1 2 3)
リスト判定

null  []
True
(null?  '())
#t 
関数を使った演算
(\x  y  ->  x + y)  1  2
→ 3
((lambda  (x  y) (+ x  y)) 1  2)
→ 3

自然数nの階乗 n ! を求めるコードの例

繰り返し処理は、他言によくあるforループのようなものではなく再帰呼び出しや高階関数を使った手法で書くのがHaskellなど関数型言語の基本的な流儀。

-- 同名の関数定義を複数用意して、引数によるパターンマッチングを利用する例

factorial :: Int -> Int   -- 関数名はfactorialとし、引数整数の値を一つ取る、返り値も整数の値
factorial 0 = 1                       -- 引数が0の場合、返り値は1とする
factorial n = n * factorial (n - 1)   -- 引数が0以外の場合は再帰呼び出しを使って返り値をめる

-- 条件式if使った例

factorial :: Int -> Int   
factorial n =
        if n == 0              -- Haskell2010の規定により then/else節のインデントえなくてもよい
        then 1    -- 引数nが0の場合、返り値は1とする     else n * factorial (n - 1) -- それ以外の場合は再帰呼び出しを使って返り値をめる
-- ガード節を使った例

factorial :: Int -> Int
factorial n
        | n == 0     = 1                          -- 引数nが0の場合、返り値は1とする
        | otherwise = n * factorial (n - 1)     -- それ以外の場合は再帰呼び出しを使って返り値をめる

-- case構文のパターンマッチングを使った例

factorial :: Int -> Int
factorial n = case n of 
0 -> 1 -- 引数nが0の場合、返り値は1とする _ -> n * factorial (n - 1) -- それ以外の場合は再帰呼び出しを使って返り値をめる

同様の計算を再帰呼び出しを使わないで行うコードの例

-- 1からnまでの整数リストを生成してその要素をすべてかけ算する例
-- 高階関数 foldl は、第2引数「1」と第3引数整数リストの全要素を使って第1引数の「*」関数を適用 factorial :: Int -> Int factorial n = foldl (*) 1 [1..n] -- 具体的には、1 * (1 * 2 * 3 ....* n) の演算が行われる

関連動画

関連商品

下記の2冊は原書(英語)がwebで読めるらしい。

下記左上は上記左の翻訳書。ちなみに「すごいH本」といういかがわしい通り名がついているが、原題を見ればわかるように普通の本である。

関連コミュニティ

関連項目

参考外部リンク

コラム: Hakellの闇に迫る

使わなかったものは理解不と罵り、使用したものはすばらしいと絶賛する純関数型言語Haskell。Hakellユーザーからの肯定的な報告が相次ぐ中、あえてHaskellの闇に迫ってみようと思う。(全6回連載終了)

1. 名前空間は犠牲になったのだ…

Haskellでは状態変更を禁止することにより、他のモジュールからの変更を考慮しなくていいように設計されている。逆に言うとオブジェクト指向などで強調されるカプセル化をしなくても、外部からの変更による問題は発生しにくいようにできているといえる。

この結果、Haskellでは内部構造を隠蔽しようという思想よりも、どこからでもアクセスできたほうが便利という思想が優先されている。代数データレコード(他の言では要素とかフィールドとかメンバーとかに相当)は、定義すれば条件で外部から参照可になる。変更はできないのでアクセスできても問題ないという点では正しいのだが、レコード実装方法が変わった時は、内部構造が隠蔽されていないので外部に変更が及んでしまう。

実装変更の影を受ける以上に外部から参照可なことによる弊があるのが、名前空間での衝突である。代数データレコードへは、レコード名と同名の関数によってアクセスすることができる。従って、一つのファイルに同じ名前のレコードを持つ2つの代数データが存在すると、同じ名前の「関数」が2つ存在することになってしまう。型推論の機が強なので何とかなってしまうこともある(それでも、コードを読む側は自で強型推論をすることを強いられているんだ!)が、型推論で解決できない時にこれを避けるには一方のレコードの名前を変更するなどの対応方法が考えなければならない。しかし、ライブラリなど他人が作ったものを利用する場合はそうも行かない。

となると、対応方法はモジュール名を付加して区別する(import qualified)くらいしかなくなるのだが、関数を使うたびにモジュール名を付加しないといけないとすると、その手間はかなりのものになる。モジュール省略名を設定すれば、手間は軽減されるが、省略名の管理をしないとどのファイルでどの名前をどう省略したのかわからなくなってしまう。さらにファイルごとにモジュール名を付ける関数と付けない関数の区別など考え始めた日には、泥沼にはまってしまうのは想像に難くない。

名前空間犠牲になったのだ参照透過性の犠牲にな…

2. 日本語でおk

コンピューターOSプログラミング言語も多くはアメリカが発祥の地である。従って、文字については英語を前提としたASCIIなどの仕様策定がなされており、日本語などへの多言対応については後付けになることが多い。特にコンピューターのリソース制限がきつかった昔のコンピュータープログラミング言語にはこの傾向が顕著である。その結果、日本語を使用するためには文字化けなどと格闘する羽に陥ることが日常茶飯事であった。

こういった反を踏まえて、後発の製品では文字コードUnicodeに統一するなどネイティブに多言対応することが一般的になってきた。モダンな言では日本語文字列も英語文字列も特に区別なく利用可であることはごく当たり前になりつつある。

しかし、Haskellはそうではない。Unicodeは使えることは使えるのだが、ソースコード中では10進数コードポイント表記で書かなければならず、「ニコニコ大百科」は '\12491\12467\12491\12467\22823\30334\31185' となる。日本語でおkといわざるを得ない。

もちろんUnicodeのUTF-8をサポートするパッケージは存在するが、ネイティブ対応でない以上、いちいち気を配る必要があるという点には変わりがないのである。

3. 英語でおk

ところで Haskellの演算子を見てくれ こいつをどう思う?

>>=

すごく…モナドです…

>>=はHaskellを少しやると必ず出くわすことになるモナドに使うbindという演算子である。モナドについては簡単だという人からわけがわからないよという人まで評価が分かれるが、>>=という数学はもちろん他の言でも見たことがない記号演算子とこれの糖衣構文であるdo記法あたりが最初の関門になる人も多いのではないかと思う。

この記号に慣れたらHaskellの演算子学習はおしまいかというとそうではない。HaskellはあのPerlでさえ尻尾を巻いて逃げ出しそうなほどの多彩な演算子があるのだ。その一部を(コンマスペース区切りで)ご紹介しよう。

<$>, <*>, >>^, <<<, .|., &&&, :<, |*><*|

前項で述べた日本語対応などもはやどうでもいい英語でおkなので、せめて人間の言葉で書いてほしい…。そう思わずにはいられないのである。おまけにこういった変わった演算子は圏論に基づいているものが多く、抽的で意味はおろか読み方すらわからないときている。

Haskell演算子のには深い闇が眠っているのだ…。

4. 右? 左? ← ご存知、ないのですか!?

たいていのプログラミング言語は、原則として数式と同じように左から順に計算する。演算子も一部の例外を除いて左結合(@という演算子についてa@b@cとあれば、(a@b)@cと解釈する)で、右結合の(a@b@cとあれば、a@(b@c)と解釈する)ものは直感的に理解できるもので、そもそも結合して用いることが少なかったりする。

Haskellでは関数適用は左結合であり、左から計算する原則は変わらないが、演算子には右結合のものが結構ある。代表的なものには、関数合成"."とリスト結合":", "++"、そしてLispの反から括弧を減らすために導入された最低優先順位演算子"$"などがある。厳密には演算子ではないが、関数を表すときに用いる"->"も右結合である。

もちろんこれにはHaskellを知る方々から反論もあるだろう。f . g xは数学でも右から計算するし、数学同様に関数を左に引数を右に書く以上、f(g(h(x + y)))の括弧を減らそうと思えば、f $ g $ h $ x + yとして"$"は右結合としなければならないのは当然である。関数を表す"->"も意味を考えれば右結合なのは自然である(リスト結合は…おや、誰か来たようだ)。

しかし、Haskellは言仕様により括弧のほとんどを省略してしまったので、計算する順番を判断するヒントに乏しい。従って、演算子の優先順位だけでなく右結合か左結合かの知識まで確実にしないと、関数合成などに高い頻度で現れる右結合に対応してコード読み解くことすらできず、「ご存知、ないのですか!?」と言われてしまうことになる。これはコードを読む者にとって大きな負担となるのではないだろうか。

話は演算子の結合にとどまらない。"->"と"<-"の違いといわれて、すぐに答えられるだろうか。確かにこれくらいなら少しHaskellに慣れてくれば容易に答えられるかもしれない。ではこれはどうだろう。

">>="と"=<<", 左記と">=>"と"<=<", "<<<"と">>>", 左記と"^<<"と"<<^"と"^>>"と">>^"

ちなみに">>="は左結合だが"=<<"は右結合、他は向きが違ってもみんな右結合である。

おわかりいただけただろうかHaskellの演算子は読みや意味が分かりにくい以外にも右だか左だかまで容易にはわからないようになっているのである。

使わなければどうということはない? それは誤解である。あなたが使わなかったとしても、他の人が使うことは止められない。そして、あなたが解説をめて開いたページのサンプルコードにその記号が突如として立ちはだかる可性は十分にあるのだ。

5. フフフ…モナドは四天王の中でも最弱…

以下は、2014年末の時点でのWikipediaでのモナド(プログラミング)の記事の冒頭である。

計算機科学におけるモナド英: Monad)とは、計算機科学者のEugenio Moggiによって提案されたモジュール性を持たせた表示的意味論の組みを言う。プログラムとはクライスリ圏の射である(a program is an arrow of a Kleisli category)、という要請から合成規則としてクライリトリプル(Kleisli triple)というモナドと等価なものが用いられる。システムへの適用であるプログラミング言語Haskellで用いられるものがよく知られている。

!? おまえは何を言っているんだ? まるで意味がわからんぞ!

それでもGoogle先生なら…Google先生ならきっと何とかしてくれる…わからない単を片っ端から調べれば…そう、まずはこのクライスリ圏から…

と思ってクライスリ圏とは何かを調べようとするわけだが、Google先生ですらクライスリ圏のことはよく知らないらしい。Wikipdiaにも2014年12月1日までは項すらなかった。2016年10月現在、記述はあるもののやはり数学者にしか分かりそうにない記述のままである。

2016年10月現在インターネット上において様々な人によってモナドに関する解説が試みられており、中途半端な理解に基づくものや、途中で挫折してしまっているものも見かけるが、上記2014年以前の状態にべたらかなり善されたと言える。

しかし、Haskell圏論の話はモナドでおしまいではない。アプリティヴ、ファンクター、アロー、モナド変換子と、まだまだ先があるのである。残念ながら、ほとんどの記事がモナドの解説で尽きており、こういったところに関しては解説が少ないか、あっても上級者向けの内容であったりする。

これらの概念がモナドより難しいかといえば、必ずしもそうではないかもしれない。だが、ゲームボスキャラを思い浮かべてほしい。十分に攻略情報が与えられて最強隠しボスに挑むよりも、攻略情報なしで初見中ボスに挑む方がはるかに難しい場合があるのではないだろうか。

そういう意味では、解説が充実しているモナドHaskellに出てくる圏論の中で最弱であると言っても過言ではないのである。

ただ、モナドの解説も2015年以降、1年くらいの間にだいぶ充実してきたことを考えると、他の用の解説も何年か待っていたら時間が解決してくれるのかもしれない。Google先生次回作にご期待下さい

6. ここからが本当の地獄だ

Haskellは静的型付けである。しかも、安全が他の言よりも徹底しており、一般的にはHaskellの長所とされている。しかしながら、このことが災いをもたらすこともあるのだ。

安全が厳格であるがゆえに、あるライブラリ仕様バージョンアップに伴って変更されると、のちょっとした仕様変更によりそのライブラリ依存していたライブラリが使用不になることが、稀によくある

すると、その依存していたライブラリ依存していたライブラリが使用不になり、というように連鎖反応的に広汎なライブラリが使用不可能になる。どの言にもあることだが、Haskellは小さなライブラリが多数存在して複雑な依存関係を構成していることが多く、わざわざDependency Hell(依存関係地)という言葉まで用意されている。

以外にも、ライブラリ開発者の数が少ないので一人だけで開発していたら独断で仕様変更ができてしまうとか、ユーザーも少ないので仕様変更しても苦情が来る量が少ないとか、他の言べて互換性よりも仕様を洗練することを重視する文化があるとかなのかもしれないが、原因はともかく依存関係地実在することだけは間違いない。

もっともHaskellもこの問題について手をこまねいて見ていたわけではない。cabalという標準のパッケージ管理コマンドがあり、依存関係をチェックしながらインストールしてくれる(なんとこのcabal、パッケージインストールする機はあるが、アンインストールする機がない: cabal-devというものにはあるらしい)。cabalバージョン1.18以降ではサンドボックスが導入され、あるプログラムのためのパッケージバージョンの変更が別のプログラム開発に影を及ぼさないようすることが可になった。

また、依存関係が正しく解決されたライブラリバージョンの組み合わせを開しているStackageというものも提供されるようになってきた。しかし、そのサポート期間は最長で1年と短く心許ない。

あなたは参照透過性にモナド圏論など慣れない概念や見慣れない記号の地をくぐり抜けて、ようやくひと通りHaskellと標準ライブラリが使えるようになり、今度は本格的に外部ライブラリフレームワークを使用したプログラミングに乗り出そうとしているところかもしれない。だが、これまであなたが地だと思ってきたものは、所詮はあなたの努が及んでいなかったということに過ぎず、そこに理不尽さはない。しかし依存関係地は「正しく」ライブラリを利用する者にも等しく訪れる。ここからが本当の地獄だ


【スポンサーリンク】

携帯版URL:
http://dic.nicomoba.jp/k/a/haskell
ページ番号: 1115675 リビジョン番号: 2424461
読み:ハスケル
初版作成日: 09/01/25 21:28 ◆ 最終更新日: 16/11/03 19:39
編集内容についての説明/コメント: 関連商品を更新することを強いられているんだ!
記事編集 / 編集履歴を閲覧
このエントリーをはてなブックマークに追加

この記事の掲示板に最近描かれたお絵カキコ


Haskellのマーク

この記事の掲示板に最近投稿されたピコカキコ

ピコカキコがありません

Haskellについて語るスレ

1 : とある言語の例外発生(エクセプション) :2009/11/18(水) 22:51:12 ID: 3CJgnrnSlc
マウス適当Haskellマークを書いてみました。orz

タイトル:Haskellのマーク
画像をクリックして再生!!
  Twitterで紹介する

2 : ななしのよっしん :2010/11/21(日) 16:52:56 ID: xUYT5VhR2s
副作用なしでここまでできるってのがすごい。本当に便利な言
3 : ななしのよっしん :2011/06/12(日) 12:19:13 ID: xri0dhzg3I
最近始めたよ
4 : ななしのよっしん :2012/05/09(水) 03:29:32 ID: 02HKRuAn0m
>>2
副作用はありますよん。IOモナドさんが隠してくれているのです。

元々の問題は、純関数型言語であるがために既存の変数を変更できないのですよ。

でも入出っていうのは入/出の状態を変更することにあたるわけで、どうしても変更は必要だったのです。

そこで、モナドの性質
・値をデータの中に入れてしまえばモナド則を満たしている以上中身を取り出せない
・入れたままならなんでもできる
っていうのを使って中身を隠蔽しているのです。

そうすれば、入/出だけの世界に変更を閉じ込めることができるので純世界は侵されないってはなしですね。
5 : ななしのよっしん :2012/06/03(日) 21:16:42 ID: I/Bh2ERgL9
>>4
HaskellがIOにモナドを使ってるのと再代入ができないのはあんまり関係ないと思う。
HaskellがIOについて抱えてる問題は
プログラム全体で参照透過性を保たなきゃけない
遅延評価のせいで式が評価されるタイミングが定まらない
のふたつで、それを解決するためにモナドを使ってるじゃないのかな。


あと、
>・値をデータの中に入れてしまえばモナド則を満たしている以上中身を取り出せない
>・入れたままならなんでもできる
この二つはモナドの性質としてはあまり適切じゃないかと。
(省略しています。全て読むにはこのリンクをクリック!)
6 : 4 :2012/08/02(木) 01:46:36 ID: 02HKRuAn0m
>>5

参照透明を保ったままIO(≒再代入)を行うために状態を持ちながら引っっていくのがIOモナドで、関係はまぁくもないとは思うのです
ちなみに評価の順番はArrow(関数合成)を使っても制御できまして、
これだと並列化もわりと簡単にできるのでモナドよりも楽しい気がするんですけど。

あーそうですね、たしかに別に操作可ではあります
言いたかったのは、モナドは中身を取り出さずに中身をいじれるっていう話です、ごめんなさい
意味上で持って来られないもの(MaybeとかListとか)は操作が認められてないだけで、Identityとかなら普通に取り出せますしね・・・
7 : ななしのよっしん :2014/04/12(土) 16:42:47 ID: htVHxpg1SX
上の例,
factorial :: Int -> Int
factorial 0 = 1
factorial n = n*factorial (n-1)
も入れるのはどうだろうか
8 : ななしのよっしん :2014/07/21(月) 18:54:19 ID: iRfsxkJIqP
ピュアであろうとするのはいいが、簡単なことをわざと複雑にしてる感がC++に通じてあんまり好きになれない。
Ocamlあたりで妥協した方が良くね?と思う今日この頃。
9 : ななしのよっしん :2014/09/14(日) 13:57:16 ID: C43jYURYjV
Rubyで書いてみて、イラっとしたらHaskell
そんな感じの使い分けでで楽しく付き合えてる
ニコニコニューストピックス
電ファミwiki
  JASRAC許諾番号: 9013388001Y45123
  NexTone許諾番号: ID000001829