Haskell 単語


ニコニコ動画でHaskellの動画を見に行く

ハスケル

5.7千文字の記事
これはリビジョン 2316702 の記事です。
内容が古い・もしくは誤っている可能性があります。
最新版をみる

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

概要

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

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

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

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

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

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

Haskellのためのフリーウェアの開発環境としてGHC(Glasgow Haskell Compiler)がデファクトスタンダード。他にはHugsなどがある。(これらはeclipseからも利用することもできる) Haskellで書かれた日本で有名なゲームにMonadiusというのがある。(GUIの処理ははOpenGLを利用したライブラリGLUTによるもの)

純粋関数型言語

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

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

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

  • 宇宙人の作った車と噂され、完全自動化を売りにしている。
  • Haskellの運転席にはハンドルもペダルも無く、代わりに運転装置と一体化したカーナビが取り付けられている。ドライバーは実際の運転操作をする必要がなく、このカーナビに目的地の定義を入力すれば、目的地までの運転が自動的になされる。
  • 従来の自動運転システムでは、道路状況を判断できず公道を走ることは無理であったが、Haskellはこれを奇想天外な方法で解決した。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で読めるらしい。
ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。

下記左上は上記左の翻訳書。ちなみに「すごいH本」といういかがわしい通り名がついているが、原題を見ればわかるように普通の本である。
ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。ニコニコ市場は2023年11月に終了しました。

関連コミュニティ

ニコニコミュニティは2024年8月に終了しました。

関連項目

  • プログラミング
  • 関数
  • 型推論
  • 再帰
  • 高階関数
  • カリー化
  • クロージャ
  • モナド
  • 継続
  • / 圏論
  • 副作用
  • 関数型言語
  • 関数型プログラミング
  • プログラミング言語
  • プログラミング関連用語の一覧

参考外部リンク

コラム: Hakellの闇に迫る

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

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

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

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

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

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

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

2. 日本語でおk

3. 英語でおk

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

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

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

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

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

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

これの問題に対応して、依存関係が正しく解決されたライブラリバージョンの組み合わせを公開しているStackageというものもあるにはあるが、サポート期間も最長で1年と短く心許ない。

依存関係地獄を解決したとしても、依存するライブラリのうち一つでも仕様が変わってしまうと、再び地獄に逆戻りである。ここからが本当の地獄だ。

おすすめトレンド

ニコニ広告で宣伝された記事

記事と一緒に動画もおすすめ!
紲星あかり[単語]

提供: 核砂糖入り紅茶

もっと見る

急上昇ワード改

最終更新:2025/12/12(金) 01:00

ほめられた記事

最終更新:2025/12/12(金) 01:00

ウォッチリストに追加しました!

すでにウォッチリストに
入っています。

OK

追加に失敗しました。

OK

追加にはログインが必要です。

           

ほめた!

すでにほめています。

すでにほめています。

ほめるを取消しました。

OK

ほめるに失敗しました。

OK

ほめるの取消しに失敗しました。

OK

ほめるにはログインが必要です。

タグ編集にはログインが必要です。

タグ編集には利用規約の同意が必要です。

TOP