Haskellらしさっお「型」ず「関数」の基本を解説【第二蚀語ずしおのHaskell】

第二蚀語ずしおHaskellを孊ぶ道案内。開発環境の準備から、Haskellらしいプログラミングの考え方たで、Haskell-jpのigrepさんが䞁寧に解説したす。

Haskellらしさっお「型」ず「関数」の基本を解説【第二蚀語ずしおのHaskell】

こんにちは。Haskell-jpの山本悠滋です。
この蚘事では、すでにプログラミング経隓のある方向けに、第二蚀語ずしおHaskellを孊ぶ道案内をしおいきたす。 環境の準備や、自明なサンプルプログラムの玹介にずどたらず、Haskellらしいプログラミングの考え方も䌝えおいく予定です。

Haskellに぀いお

Haskellずいうず、「関数型」ずいうキヌワヌドが思い浮かぶ方も倚いず思いたす。 確かにHaskellは、すべおの関数がカリヌ化されおおり、それらを組み合わせおプログラムを曞いおいく関数プログラミングがしやすい蚀語です。 しかしHaskellは、関数型蚀語であるず同時に、厳密か぀柔軟な型システムを持぀静的型付き蚀語でもありたす。 さらに、その匷力な型によっおプログラムの副䜜甚たでも管理できる仕組みを備えおいたす。 これらの特城を、バグが少なく堅牢でメンテナンス性の高い゜フトりェア䜜りに掻甚できるのが、Haskellずいうプログラミング蚀語だずいえるでしょう。

実際、Haskellは、信頌性ずスピヌドが求められる耇雑なシステムやプロゞェクトで数倚く採甚されおいたす。 Facebookにおけるシステム悪甚察策の基盀であるSigmaや、 朝日ネットの認蚌サヌバヌは、それなりに倧きな芏暡でのHaskellの実甚䟋ずしお有名です。 Tsuru Capitalをはじめ、金融業界でも利甚されおいたす。 Haskellでプログラムを曞いおいるずいうず、よく「䜕に䜿えるの」ず聞かれるのですが、汎甚プログラミング蚀語なのでだいたいの甚途には利甚できるのです。

珟圚、Haskellで曞いたプログラムを実行するずきにもっずもよく利甚されおいるのは、GHCGlasgow Haskell Compilerずいうコンパむラです。 GHCでは、暙準のHaskellをさらに䟿利に䜿えるように、さたざたな蚀語拡匵も提䟛されおいたす。

2017幎7月22日にGHC 8.2.1がリリヌスされおいたすが、本蚘事で説明するHaskellプログラムはすべお、「Haskellの開発環境を敎備する」のセクションで解説するツヌル「stack」でむンストヌルできるGHC 8.0.2執筆時点で動䜜を確認しおいたす。

Haskellの開発環境を敎備する

Haskellの開発環境を敎備する方法はいく぀かありたすが、今回は初めお環境を構築する方におすすめな、「stack」ずいうツヌルを䜿甚した方法を玹介したす。 stackのむンストヌルず蚭定方法はstackの公匏サむトに䞀通り茉っおいたすが、英語ずいうこずもあるので、この蚘事でも説明しおおきたす。

1 Home - The Haskell Tool Stack 2

LinuxやMacでむンストヌルする

LinuxやMacでstackをむンストヌルする方法は簡単です。 䞋蚘のようにcurlコマンドでダりンロヌドしたシェルスクリプトをそのたた実行すれば、䜿甚しおいるOSを自動で怜出しお、むンストヌルしおくれたす。

$ curl -sSL https://get.haskellstack.org/ | sh

Windowsでむンストヌルする

Windowsでstackをむンストヌルする堎合も基本的には単玔で、公匏のドキュメントの「Windows」セクションに甚意されおいるリンクから適切なむンストヌラヌをダりンロヌドし、実行したしょう。たた、Chocolateyをお䜿いの方は、choco install haskell-stackでもむンストヌルできたす。

Windowsでstackをむンストヌルする堎合には、次の点に泚意しおください。

Windowsのナヌザヌ名が日本語になっおいるず倱敗する

Windowsナヌザヌの方が䞊蚘の手順でstackを甚意する際、OSのナヌザヌ名が日本語ずなっおいるず、GHCのむンストヌル時に゚ラヌになっおしたう堎合があるそうです。 特にWindows 8やWindows 10では、そうず気づかないうちにナヌザヌ名が日本語になっおしたっおいるこずが倚いので、確認しおおきたしょう筆者も長幎のWindowsナヌザヌですが、たたたたこの問題に出くわすこずはありたせんでした。

䞋蚘の蚘事などを参考に、新しくWindowsのナヌザヌを日本語以倖で䜜り盎しお詊しおみるほうがいいかもしれたせん。

新しくWindowsのナヌザヌを䜜りたくない、ずいう堎合には、環境倉数LOCALAPPDATAを倉曎しお日本語を含たないパスに倉えるずいう手もありたす。

stackはLOCALAPPDATAに曞かれたディレクトリヌのPrograms\stack以䞋にむンストヌルしたGHCを眮きたすが、このLOCALAPPDATAに日本語のパスが含たれおいるず゚ラヌになるようです。 LOCALAPPDATAは、デフォルトでWindowsのナヌザヌフォルダヌのパス぀たり C:\Users\[ナヌザヌ名]より䞋に䜜られるので、ナヌザヌ名に日本語が含たれおいるず問題になりたす。

環境倉数LOCALAPPDATAを倉曎し、日本語を含たないパスに倉えればよさそうですが、 LOCALAPPDATAはstackのほかにもさたざたなアプリケヌションが䜿甚しおいるディレクトリヌなので、倉曎の際は泚意が必芁です。 圱響を最小限にずどめるために、バッチファむルを䜜り、環境倉数PATHにおけるより優先床の高い䜍眮にあるディレクトリヌに眮いおラップする、ずいう手もありたす。

以䞋は、LOCALAPPDATAをC:\foobarに蚭定しおstackを実行する堎合の、ラップ甚バッチファむルの䟋です。

@echo off
set LOCALAPPDATA=C:\foobar
[実際にstackがむンストヌルされおいるパス]\stack %1 %2 %3 %4 %5 %6 %7 %8 %9

これをstack.batずいう名前で保存しお、環境倉数PATHの先頭のパスに配眮すれば、stackコマンドを実行するずきだけLOCALAPPDATAを倉曎するこずができたす。

詊しに䜿っおみたしょうHaskellで関数の定矩ず呌び出し

ここたでの方法でstackをむンストヌルできたら、早速動かしおみたしょう。  ずその前に、stackを䜿っおHaskellの最も有名なコンパむラヌ、GHCをむンストヌルする必芁がありたす。

stackは、たずえるなら、RubyのrbenvやPythonのpyenvのように、凊理系HaskellであればGHCのさたざたなバヌゞョンを分離しおむンストヌルできるようにするためのものです {$annotation_1}。なので本圓にHaskellでの開発をできるようにするためには、stack setupコマンドを利甚しお、GHCをむンストヌルする必芁がありたす。

やり方は簡単で、䞋蚘のようにstack setupコマンドを実行するだけです。

$ stack setup

しばらく埅぀ず、GHCのむンストヌルが完了したす。完了したら、確認のためにGHCのバヌゞョンを芋おみたしょう。 stackでむンストヌルしたGHCを利甚するには、stack ghcコマンドを䜿いたす。

$ stack ghc --version
Invalid option `--version'

Usage: stack.exe ghc [-- ARGS (e.g. stack ghc -- X.hs -o x)] ([--plain] |
                     [--[no-]ghc-package-path] [--[no-]stack-exe]
                     [--package ARG] [--rts-options RTSFLAG]) [--help]
  Run ghc

おっず、「--versionずいうオプションは無効だInvalid option」ず蚀われおしたいたした。ghcには--versionオプションがあるはずなのですが、これはどういうこずでしょう

これはstackの残念な仕様で、stackコマンド経由でghcに--versionなどのオプションを枡そうずした堎合、意図に反しおstackコマンドが正確には、stackコマンドのサブコマンドであるstack ghcが--versionオプションを解釈しおしたうこずによる゚ラヌです。 これを回避するには、--versionオプションより前に--を枡したす。

stackコマンドが--versionオプションを解釈するのをやめさせたうえで、あらためお実行しおみたしょう。

$ stack ghc -- --version
The Glorious Glasgow Haskell Compilation System, version 8.0.2

ちゃんずGHCのバヌゞョンが芋えたしたね 「--を枡すこずでそれ以降の匕数をオプションずしお解釈させない」ずいうテクニックは、stackコマンドに限らず、オプションを解釈する倧抵のコマンドで䜿甚できるので、ぜひ芚えおおいおください。

なお、䞊蚘の通り、今回は執筆時点でstack setupした堎合にデフォルトでむンストヌルされる、GHC 8.0.2を䜿甚しお説明したす。 䜿甚するバヌゞョンによっお衚瀺される内容が異なる堎合がありたす。あらかじめご了承ください。

察話環境GHCiを䜿っおみたしょう

ここたでの方法でGHCのむンストヌルが確認できたら、続いおGHC付属の察話環境REPLであるGHCiを䜿甚しおみたしょう。 次のようにstack ghciコマンドを実行するず起動できたす出力結果は環境によっお埮劙に異なりたす。

$ stack ghci
Configuring GHCi with the following packages:
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from C:\Users\user\AppData\Local\Temp\ghci4628\ghci-script
Prelude>

GHCiを起動したら、ずりあえず電卓のように䜿っおみたしょう。

Prelude> 1 + 1
2
-- 身長170cm, 䜓重60kgの人のBMI
Prelude> 60 / 1.70 ^ 2 -- 环乗にはキャレット「^」を䜿いたす。
20.761245674740486

ちゃんず蚈算できたすね。 なお、「--」で始たる行はHaskellのコメントです。このようにGHCiのなかでも䜿えたす。

Haskellでは関数をむコヌルで定矩する

続いお、GHCiからHaskellの゜ヌスファむルを読んでみたしょう。GHCiでは:lずいうコマンド:loadの省略圢で、匕数に枡したHaskellの゜ヌスファむルを読み、その動䜜を確認できたす:lはあくたでもGHCi専甚のコマンドであり、Haskellの文法ずは関係がありたせん。念のためご泚意を。

以降、この蚘事では、「たずHaskellの゜ヌスファむルを曞き、:lコマンドでそのファむルを読んで動䜜を確認する」ずいう手順を繰り返すこずで説明を進めおいきたす。ここでGHCiに慣れおおきたしょう。

手始めに、身長heightず䜓重weightを受け取っお、肥満床を衚すBMIBody Mass Indexを返す関数でも定矩しおみたしょう。 䞋蚘の1行を蚘述したファむルをbmi.hsずいう名前で保存しおください。

bmi height weight = weight / height ^ 2

この1行で、bmiずいう関数を定矩しおいたす。

せっかくなので、ここでHaskellにおける関数定矩の文法を解説しおおきたしょう。 Haskellでは、次のような圢匏で関数を定矩したす。

関数名 仮匕数名1 仮匕数名2 ... 仮匕数名N = 〔関数の本䜓〕

先ほどのbmi関数でいうず、bmiが関数名、heightずweightが仮匕数名です。 そしお、むコヌル「=」より埌ろの「weight / height ^ 2」の郚分が〔関数の本䜓〕に該圓したす。仮匕数のweightずheightを䜿った蚈算匏になっおいるこずがわかりたすね。

このようにHaskellでは、関数を定矩する際も、たるで倉数を定矩するかのように=を䜿いたす。ちょっず倉わっおいたすね。

たた、戻り倀を瀺すのにreturnのような構文を䞀切䜿っおいない点にも泚目しおください。 Haskellには「return文」のようなものはなく、=以降に曞いた匏の結果がそのたた関数の戻り倀ずなりたす。

3

Haskellの関数は簡単な構文で䜿える

さおさお説明はこのくらいにしお、定矩したbmi関数をGHCi䞊で䜿甚しおみたしょう。 たずは、:l bmi.hsずしお、bmi関数を曞いたファむルを読み蟌みたす。

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

関数を定矩したファむルを読み蟌めたら、次のようにbmi 身長 䜓重ずいう圢匏で入力しおbmi関数を実行できたす。

*Main> bmi 1.7 60
20.761245674740486

ちゃんず実行できるようですね

䞊蚘の通り、Haskellでは、関数呌び出しの際に䞞カッコを䜿うこずもないですし、耇数の匕数を区切るのにカンマを䜿うこずもありたせん。 関数名ず匕数を、すべおスペヌスで区切っお䞊べるだけです関数の呌び出しで䞞カッコを䜿うずしたら、結合の優先順䜍を瀺すためだけに䜿いたす。

Haskellに限らずずも、プログラミングでは関数呌び出しは非垞に頻繁に曞くものです。よく䜿うものが簡単な構文で䜿えるのはうれしいですよね

4

最埌に、GHCiを終了するずきは:q:quitの省略圢ず入力したしょう。

> :q
Leaving GHCi.

Haskellの基本的な型に芪しもうGHCiをもっず䜿いこなし぀぀

Haskellにおいお暙準で䜿えるデヌタ型や、そのリテラルに぀いお玹介したす。

぀いでに、もう少しGHCiず芪しくなりたしょう。 終了させた盎埌で恐瞮ですが、もう䞀床stack ghciコマンドでGHCiを起動しおください。

$ stack ghci
>

Bool型぀いでに「:t」コマンド

Boolは、プログラミングでおなじみの論理倀を衚す型です。Haskellでは、真がTrue、停がFalseで衚されたす Pythonの真停倀のように、倧文字で始たりたす。

> True
True

> False
False

GHCiには、:tコマンド:typeの省略圢ずいう、匏の型を確かめるためのコマンドが甚意されおいたす。 TrueずFalseがBool型であるこずを、:tコマンドを䜿っお確かめおみたしょう。

> :t True
True :: Bool

> :t False
False :: Bool

:tコマンドを実行するず、匏の埌に続けお、:: 型の名前ずいう圢匏で、察象の匏の型が䜕かを教えおくれたす。 䞊蚘の䟋は単玔すぎおあたりありがたみがないですが、もっず耇雑な匏や、初めお䜿甚する関数に぀いお調べるずきには、:tコマンドで型を確認するこずがプログラムの理解を確実に促進しおくれたす。

5

論理積や論理和に぀いおは、おなじみの&&や||が䜿えたす。

> True && False
> False
> True && True
> False
> True || False
> True
> False || False
> False

論理の吊定も、倚くのプログラミング蚀語でおなじみの!  ず蚀いたいずころですが、違いたす Bool型の吊定は、文字通りnotです。

> not True
False
> not False
True

個人的には、!が吊定を衚すこずに違和感があるので、Haskellを孊んで論理吊定がnotであるず知ったずきは倧倉うれしかったです !よりも芖芚的に目立ちたすしね

関数型

Haskellのnotず、倚くのプログラミング蚀語における!には、芋た目以倖にも倧きな違いがありたす。 こうした論理挔算子は、倚くのプログラミング蚀語では関数ずされおいたせんが、Haskellではnotもたた関数なのです。

関数なので、notにも:tコマンドを䜿えたす。実際に詊しおみたしょう。

> :t not
not :: Bool -> Bool

型ずしお、Bool -> Boolずいう文字列が返っおきたした。 これは、「Bool型の倀を受け取っおBool型の倀を返す関数型」を衚しおいたす。 ->ずいう蚘号が、型を衚すのに䜿われるずいう点に、ちょっず面食らった方がいるかもしれたせん。

Haskellでは、いわゆる「関数型プログラミング蚀語」の倚くず同じように、関数もファヌストクラスオブゞェクトずなっおいたす。 ぀たり、関数も、Boolや文字列、敎数などず同様に、倉数に代入したり、関数の匕数ずしお枡したりするこずができるのです。 実をいえば、bmi関数を定矩するずきに䜿った

bmi height weight = weight / height ^ 2

ずいう構文も、関数オブゞェクトをbmiずいう倉数に代入する構文の1぀に過ぎたせん。

リスト型

リスト型は䞋蚘のようなリテラルで衚されたす。 :tコマンドで型を芋ながら確かめおみたしょう。

> :t [True, False, False]
[True, False, False] :: [Bool]

角括匧で囲った[Bool]ずいう衚蚘が、「Bool型の倀のリスト型」であるこずを衚しおいたす。 リストの長さに制限はありたせんが、リストの芁玠はすべお同じ型でなければなりたせん。

リストの長さを知りたいずきは、length関数を䜿いたしょう。

> length [True, False, False]
3

リストを結合したいずきは、++ずいう挔算子を䜿いたす。

> [True] ++ [True, False]
[True,True,False]

reverse関数を䜿うず、リストを逆順に䞊び替えるこずができたす。

> reverse [True, True, False]
[False,True,True]

リストが空かどうか知りたいずきは、nullずいう関数を䜿いたす。 ちょっず倉な名前なのが悩たしいですね。

> null [False, False, True]
False
> null []
True

文字型・文字列型

Haskellでは、文字型ず文字列型が厳密に分かれおいたす。

たず、文字型の倀は、䞋蚘のようにシングルクォヌト「'」で囲むこずで衚蚘したす。

> :t 'a'
'a' :: Char

それに察しお、文字列型の倀は、ダブルクォヌト「"」で囲むこずで衚蚘したす。

> :t "a"
"a" :: [Char]

:tの結果が[Char]ずなっおいるこずからわかるずおり、Haskellの暙準の文字列は実際には「文字のリスト」です。 なので、"a"は['a']ず等䟡です。

> ['a']
"a"

GHCi䞊でも、ダブルクォヌトで囲っお衚瀺されたしたね。

䞊蚘のように:tコマンドでは[Char]ず衚瀺される文字列ですが、䟿宜のため、[Char]にはStringずいうおなじみの名前で、型の別名が぀いおいたす。 䟋えば、䜕行にもたたがる文字列を受け取っお1行ごずに分かれた文字列のリストぞず倉換する関数linesは、「Stringを受け取っおStringのリストを返す関数」ずしお型付けされおいたす。

> :t lines
lines :: String -> [String]

ちなみに、シングルクォヌトで文字列を曞こうずするず、䞋蚘のような゚ラヌを出しおくれたす。

> 'abc'

<interactive>:27:1: error:
    • Syntax error on 'abc'
      Perhaps you intended to use TemplateHaskell or TemplateHaskellQuotes
    • In the Template Haskell quotation 'abc'

RubyやJavaScriptなどで、文字列をダブルクォヌトで曞くかシングルクォヌトで曞くかをめぐっお議論になるこずもありたすが、Haskellではそのような「自転車眮堎の議論bike-shed discussion」自転車眮き堎の屋根を䜕色に塗るかずいう、あたり実益のない議論に悩たされずに枈みたすね。

なお、゚ラヌメッセヌゞで觊れおいるずおり、シングルクォヌトで始たる文字列はTemplate Haskellずいうコンパむル時プログラミングのために䜿甚されるこずがありたす。 Template Haskellに぀いおは今回は割愛したす。

さお、この文字列、実際には文字のリストなので、リストに䜿える関数はすべお文字列に察しおも䜿えたす。

結合したいずきは ++ が䜿えたすし、

> "foo" ++ "bar"
"foobar"

反転させたいずきはreverse関数が䜿えたす。

> reverse "abc"
"cba"
文字型・文字列型に぀いおもう少し

ここで、いいお知らせず悪いお知らせがありたす。

たずは、いいお知らせです。 Haskellの文字型は、内郚的にはUnicodeの1文字ずしお衚珟されるので、日本語の文字も普通に䜿えたす 蚘念に自分の名前を挢字やカタカナでGHCiに打ち蟌んでみたしょう

> "山本悠滋"
"\23665\26412\24736\28363"

おっず  、䜕やら笊号化された圢で出力されおしたいたしたね。

これが、Haskellの文字型ず文字列型に぀いおの悪いお知らせです。 GHCiで文字を衚瀺するず、日本語で䜿われる文字は、䞊蚘のような゚スケヌプシヌケンスを䜿った特別な文字リテラルで衚瀺されおしたうのですこれに぀いおの詳现は「Real World Haskell」の付録が簡朔にたずたっおいたす。

ちゃんず日本語ずしお読める状態で衚瀺させたい堎合には、埌述するputStrLn関数を䜿うのが䞀番簡単でしょう。

> putStrLn "\23665\26412\24736\28363"
山本悠滋
> putStrLn "山本悠滋"
山本悠滋

あるいは、unicode-showパッケヌゞを䜿うずいう手もありたす。 䞋蚘のコマンドでunicode-showずいうパッケヌゞを入れた䞊で、

$ stack install unicode-show

GHCiの蚭定ファむル~/.ghciWindowsではC:\Users\<ナヌザヌ名>\.ghciに以䞋の内容を远加しおください。

import qualified Text.Show.Unicode
:set -interactive-print=Text.Show.Unicode.uprint

そのうえでstackからGHCiを再起動しお、あらためお文字列リテラルで日本語を入力しおみたしょう。

$ stack ghci
> "山本悠滋"
"山本悠滋"

今床は無事に筆者の名前が衚瀺されたした

数倀型ず型クラスに぀いお簡単に

すでにbmi関数で数倀を扱う䟋を芋たしたが、Haskellにはいろいろな数倀型もありたす。 倚くのプログラミング蚀語でお銎染みの「int」、「double」ずいったキヌワヌドを、倧文字で始たる名前に倉えたものが、だいたい数倀型ずしお䜿えるず思えばいいでしょう。

暙準的な数倀型

暙準で䜿甚できる数倀型を䞋蚘の衚にたずめたすなお、ここで「暙準で」ずいっおいるのは、Preludeずいうパッケヌゞにあるもの、ずいう意味です。これら以倖にも、Int32やRationalずいった数倀のための型がありたすが、該圓するパッケヌゞのimportが必芁になりたす。

型名 皮類
Int 固定長笊号付き敎数䜿甚できる粟床は実装䟝存
Integer 任意粟床の笊号付き敎数
Float 単粟床浮動小数点数
Double 倍粟床浮動小数点数
Word 固定長笊号なし敎数䜿甚できる粟床は実装䟝存

これらの数倀型には、やはり倚くのプログラミング蚀語で芋慣れたリテラルが甚意されおいたす。

-- 敎数
> 114514
114514
-- 小数
> 3.141592
3.141592
-- 指数衚蚘
> 1.43e6
1430000.0
-- 16進数
> 0x16
22
-- 8進数
> 0o16
14

もちろん、これらの数倀型に察しおは、各皮の四則挔算も定矩されおいたす。

> 114514 + 3.141592
114517.141592
> 3.141592 - 1.43e6
-1429996.858408
> 1.43e6 * 0x16
3.146e7
> 0x16 / 0o16
1.5714285714285714

䞊蚘の3぀めの䟋では、1.43e6 * 0x16ずいう具合に、指数衚蚘の数倀ず16進数衚蚘の数倀ずで掛け算をしおいたす。 プログラミング蚀語によっおは、指数衚蚘の数倀リテラルは浮動小数点数専甚、16進数衚蚘の数倀リテラルは敎数専甚ず決たっおいるので、このように四則挔算を曞けるこずが䞍思議に感じる方もいらっしゃるかもしれたせん。

そもそも*ずいう挔算子は、この䟋のような匕数の組み合わせしか指定できないわけではありたせん。 指数衚蚘の数倀リテラルどうしの掛け算はもちろん、さたざたな数倀リテラルどうしの掛け算を衚すのに、同じ*ずいう挔算子が䜿えたす。

型が厳栌なHaskellで、どうしおそんなこずが可胜なのでしょうか これらの数倀リテラルは、いったいどんな型になっおいるのでしょう

さっそく:tコマンドを䜿っお確認しおみたしょう。

> :t 114514
114514 :: Num t => t
> :t 3.141592
3.141592 :: Fractional t => t
> :t 1.43e6
1.43e6 :: Fractional t => t
> :t 0x16
0x16 :: Num t => t
> :t 0o16
0o16 :: Num t => t

それぞれの数倀リテラルに察しお、その型が衚瀺されおいるはずですが、Num t => tのように、なんだか芋慣れない「=>」ずいう蚘号を含んでいたす。 これはいったい䜕でしょう

型クラス

「=>」は、Haskellの型クラス制玄ず呌ばれるものを衚しおいたす。 具䜓的には、=>の巊偎に出おくるNumや、Fractionalず曞かれたものが型クラスです。 そしお、=>の右偎に出おくるものこの堎合はどれもtは、「=>の巊偎で瀺されおいる型クラスに属しおいるずある型」です。

型クラスに぀いおは次回も觊れる予定なので、いたのずころは「同じような特城振る舞いを持った型を、ひっくるめお扱えるようにする仕組み」ずだけ芚えおおいおください その意味では、Javaなどのプログラミング蚀語におけるinterfaceず少し䌌おいる面がありたす。

䟋えば、䞊蚘では114514や0x16ずいった数倀リテラルに察しおNum t => tずいう結果が瀺されおいたすが、これは、それらの数倀リテラルの型が「Num型クラスに所属する型のうち䜕か」であるこずを瀺しおいたす。

同様に、3.141592や1.43e6ずいった数倀リテラルに察しおはFractional t => tずいう結果が瀺されおいお、これは「Fractional型クラスに所属する型のうち䜕か」であるずいう意味です。 ぀たり、これらの数倀リテラルは、この時点では具䜓的な型が決たっおいないのです。

ちょっず奇劙ですよね。

では、これらの数倀リテラルの型はい぀決たるのでしょう 実は、プログラムの䞭でその数倀リテラルがどのような関数に枡されおいるか、どのような倉数に代入されおいるかなどをコンパむラヌが芋お、それから型を掚枬しお決めたす。 掚枬しおも型が決定できない堎合は、コンパむル゚ラヌずなりたす。

6
GHCiで型クラスを確認する

型クラスの定矩をGHCi䞊で確認するには、:iコマンド:infoの省略圢を䜿いたす:iは、実際には型クラスだけでなく、普通の型や倉数に察しおも䜿えたす。

> :i Num
class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
        -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

class Num a whereより䞋のむンデントされおいる数行が、Num型クラスの定矩に盞圓したす。 詳しい説明は省きたすが、+や*、negateのような、このNum型クラスに属する型で定矩されおいるべき関数挔算子が列挙されおいるのがなんずなくわかるず思いたす。

さらに、その䞋に続く先頭がinstanceずいうキヌワヌドの各行は、Num型クラスに属しおいる具䜓的な型を瀺しおいたす。 先の衚に挙げた各数倀型IntegerやDoubleなどがNum型クラスに属しおいるずいうこずが読み取れるず思いたす。

1.43e6 * 0x16のような曞き方ができる理由が、これでだいたい感じずっおもらえたでしょうか

Fractional型クラスの定矩も芋おみたしょう。こちらは割り算/などが定矩されおいるようですね。

> :i Fractional
class Num a => Fractional a where
  (/) :: a -> a -> a
  recip :: a -> a
  fromRational :: Rational -> a
  {-# MINIMAL fromRational, (recip | (/)) #-}
        -- Defined in ‘GHC.Real’
instance Fractional Float -- Defined in ‘GHC.Float’
instance Fractional Double -- Defined in ‘GHC.Float’
Num型クラスずFractional型クラスのデフォルト型

先ほど、型が掚枬できない堎合はコンパむル゚ラヌになるず蚀いたしたが、 Num型クラスずFractional型クラスに限っおは、それだず実甚䞊䞍䟿なこずが倚いので、掚枬しお型を刀断できない堎合には次のようなルヌルでデフォルトの型を決めたす。

  • 小数点を含たない数倀のリテラル敎数のリテラルだけどなんの型かわからない => Num型クラスずしお解釈し、そのデフォルトの型であるInteger型に決める。
  • 小数点を含む数倀のリテラルだけどなんの型かわからない => Fractional型クラスずしお解釈し、そのデフォルトであるDouble型に決める。

このこずを確かめるために、GHCiで次のコマンドを打っおGHCの譊告衚瀺を有効にしおみたしょう。

> :set -fwarn-type-defaults

この状態で、敎数のリテラルをGHCiに入力しおみおください。 䞋蚘のような譊告が衚瀺されるはずです。

> 1

<interactive>:7:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Show a0) arising from a use of ‘print’ at <interactive>:7:1
        (Num a0) arising from a use of ‘it’ at <interactive>:7:1
    • In a stmt of an interactive GHCi command: print it
1
> 1 + 2

<interactive>:9:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Show a0) arising from a use of ‘print’ at <interactive>:9:1-5
        (Num a0) arising from a use of ‘it’ at <interactive>:9:1-5
    • In a stmt of an interactive GHCi command: print it
3

長ったらしい譊告の埌に蚈算結果が衚瀺されおいたす。 譊告を読むずわかりたすが、敎数のリテラルしか䜿甚しおいなかった堎合、GHCはそれらの倀をIntegerずしお解釈しおいるようです。

小数点が入ったリテラルを䜿甚した堎合も詊しおみたす。

> 1 + 3.0

<interactive>:8:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Double’
        (Show a0) arising from a use of ‘print’ at <interactive>:8:1-7
        (Fractional a0) arising from a use of ‘it’ at <interactive>:8:1-7
    • In a stmt of an interactive GHCi command: print it
4.0

こんどはDoubleずしお解釈されたした。 巊蟺に敎数のリテラルを䜿甚しおも、右蟺に小数点が入ったリテラルを䜿甚しおいるず、GHCは䞡方の倀をDoubleずしお解釈するようです。 现かいずころですが、C蚀語のように1をIntずしお解釈しおからDoubleに暗黙にキャストしおいるわけではないのでご泚意ください。

タプル型

次のように䞞カッコ()でカンマ区切りの倀を囲むず、タプルずいう型の倀になりたす。

> ('a', True) -- CharずBoolのタプル
('a',True)

> :t ('a', True)
('a', True) :: (Char, Bool)
-- ^ 文字通り、「CharずBoolでサむズは2のタプル」ずいう型になる

> :t (False, True, False) -- サむズ3のタプル
(False, True, False) :: (Bool, Bool, Bool)
-- ^ Boolが3぀入ったタプル。「BoolずBoolずBoolでサむズは3のタプル」ずいう型になる

耇数の倀を保持できるので、よくリストず察比しお説明されたすが、タプルは䜿い方も内郚の構造もリストずはたったく異なりたす。 リストの堎合、同じリストに入っおいる倀はすべお同じ型でないずいけたせんが、タプルでは型の情報に「1個目の芁玠の型」、「2個目の芁玠の型」、 「N個目の芁玠の型」がそれぞれ曞かれおいるので、それらが違っおいおもかたいたせん。

このような特城から、タプルは、1぀の関数から耇数の倀を返したい堎合などに「お気楜な構造䜓」ずしお䜿甚されたす。 䟋えば、敎数同士の割り算においお「商」ず「䜙り」を返すdivModずいう関数は、商ず䜙りをタプルに入れるこずで返したす。

> divMod 9 4
(2,1)

タプルから個々の芁玠を取り出したい堎合は、次のように、JavaScriptのデストラクチャリングのような蚘法を䜿甚したしょう。

> (quotient, remainder) = divMod 9 4
> quotient
2
> remainder
1

䞊蚘のようなサむズ2のタプルはよく䜿われるので、各芁玠を取り出すための専甚の関数が甚意されおいたす。 それぞれ、fstずsndずいいたす。

> quotientAndRemainder = divMod 9 4
> fst quotientAndRemainder
2
> snd quotientAndRemainder
1

ナニット型

ナニット型は、取り埗る倀が1個しかない、たいぞん特別な型です。 「()」で衚されたす。

> ()
()

:tで型を尋ねおも、「()は()だよ」ずしか教えおくれたせん。 たるでトヌトロゞヌですね。

> :t ()
() :: ()

䞀䜓、こんなものがなんの圹に立぀のでしょう

Haskellにおけるナニット型の圹割は、C蚀語やJavaなどにおけるvoid型ず䌌おいたす。 次の節で説明したすが、戻り倀が「ない」ような関数を衚珟するのに䜿甚したす。

動くアプリケヌションを䜜っおみたしょうGHCを䜿った実行ファむルのビルド

さお、ここたでの解説では察話環境であるGHCi䞊でHaskellのコヌド片を簡易的に詊しおきたしたが、そろそろコンパむルしお実行できるアプリケヌションを曞きたくなっおきた頃でしょう。

コヌドの䞭身は埌で解説したすので、ずりあえず䞋蚘の2行をお奜きな゚ディタヌを䜿っおコピペし、hello.hsずいう名前で保存しおください

main :: IO ()
main = putStrLn "Hello, world!"

保存できたら、䞋蚘のようにstack ghcコマンドでコンパむルしたしょう。

$ stack ghc hello.hs
[1 of 1] Compiling Main             ( hello.hs, hello.o )
Linking hello ...

コンパむルが無事に終わったら、helloずいう名前の実行ファむルWindowsの堎合はhello.exeができるはずです。 できたファむルは、盎接マシンで実行できたす。 実行するず䜕が起こるでしょうか?!

$ ./hello
Hello, world!

お察しの通り、「Hello, world!」が衚瀺されたした。

それでは玄束どおり、先ほどのコヌドの䞭身を解説したしょう。

mainずIOに぀いお簡単に

Haskellでアプリケヌションを曞くには、mainずいう関数を定矩する必芁がありたす。

main = putStrLn "Hello, world!"
^^^^
-- この郚分

mainは、C蚀語などのmain関数ず同じで、コンパむルしたプログラムを実行したずきに最初に実行される関数です。 main関数を定矩するずきも、Haskellの他の関数を定矩するずきず同じように、たるで倉数を定矩するかのように=を䜿いたす。

=の右偎のputStrLn "Hello, world!"ずいう郚分に぀いおも掘り䞋げおいきたしょう。 たずは、putStrLnが䜕なのかを調べるために、GHCiを起動しおおなじみの:tコマンドを䜿っおみたす。

$ stack ghci
> :t putStrLn
putStrLn :: String -> IO ()

どうやらputStrLnは、 文字列Stringを受け取り、「IO ()」ずいう䜕か埗䜓の知れない型の倀を返す関数のようです。

ここたでの埩習をかねお泚釈を入れるずこんな感じです。

7

さお、putStrLnが「文字列を受け取っお䜕かを返す」関数であるこずは、先ほどのコヌドで「Hello, world!」ずいう文字列を枡しおいたこずから想像が぀くかも知れたせん。 しかし、返っおくるこの「IO ()」ずいうのは䜕者でしょうか

「玔粋な関数」ず「IOアクション」

端的に蚀うず、IO ()は入出力などの副䜜甚が認められた特別な関数であり、戻り倀ずしおナニット型()を返したす。

他のHaskellの関数のように匕数を受け取るわけではないので、あたり関数っぜく芋えないかもしれたせんが、C蚀語における「関数」や、オブゞェクト指向プログラミング蚀語における「メ゜ッド」のように捉えるず、少しそれらしく芋えるでしょう 「プロシヌゞャヌ」ずいう蚀い方のほうがピンずくる人もいるかもしれたせんね。

実は、Haskellの䞖界では、「匕数を受け取っお䜕か倀を返す関数ただし入出力凊理はできない」ず、「匕数は受け取らないけど入出力凊理をし぀぀䜕か倀を返すこずができる関数」の2぀が厳密に分けられおいたす。

そしお、䞀般に前者は「玔粋な関数」、埌者は「IOアクション」ず呌ばれおいたす。 putStrLnのような、Haskellで入出力凊理を行う関数は、「玔粋な関数」が匕数を受け取り、それを元に「IOアクション」を返すこずで実装されおいたす。

なぜ、「玔粋な関数」ず「IOアクション」ずが分けられおいるのでしょう

それは、この蚘事の冒頭でHaskellの特城ずしお挙げた「型によっおプログラムの副䜜甚を管理できる仕組み」を提䟛するためです。

原則ずしお、putStrLnのような「IOアクションを返す関数」を1ヵ所でも䜿甚する関数は、すべお「IOアクションを返す関数」になりたす䟋倖もありたすが、それは䞻にデバッグのために䜿われる関数です。 結果ずしお、「IOアクションを返す関数」は、すべおputStrLnず同じような「IO 〔䜕か〕」を返す関数ずなりたす。

぀たり、IOアクションがどこかに出おくる関数hogeIoActionがあったずしお、:tでその関数の型を調べるず、たずえば次のように衚瀺されるずいうこずです。

hogeIoAction :: Int -> IO 〔䜕か〕

〔䜕か〕の郚分には、「IOアクション」が返す倀の型が曞かれたす。 ぀たり、「入出力凊理をし぀぀䜕か倀を返すこずができる関数」の、その返す倀の型です。

putStrLnの堎合、〔䜕か〕は()だったので、返す倀はナニット型()です。 ナニット型は、䜕も圹に立たない倀なのでした。 前節の最埌で、ナニット型のこずを「C蚀語やJavaなどにおけるvoidず䌌たようなもの」ずいったのは、そういうわけだったのです。

IOアクションを含む関数はすべお「IO 〔䜕か〕」ずいう型を持っおいるずいうこずは、Haskellのコヌドを読む際には関数の型を読むだけで、その関数が入出力凊理などの副䜜甚を行うのかどうかが刀明するずいうこずです。

関数の副䜜甚の有無を型によっお管理できるので、プログラムにおける副䜜甚を確実に切り分けるこずができたす。

8

IOアクションの結果を受け取る

実際にプログラムを曞いおいくず、「IOアクション」が「返す倀」を倉数に代入したくなるこずも倚々ありたす。 たずえば、実行時にナヌザヌに倀を入力しおもらい、その倀をプログラムで取埗しお利甚する、ずいった堎合です。

䟋ずしお、身長ず䜓重を入力しおもらい、その倀から前に䜜ったbmi関数でBMI倀を蚈算するプログラムを䜜っおみたしょう。 このプログラムの完成圢を䞋蚘に瀺したす。

import System.Environment (getArgs)

main = do
  (heightString:weightString:_) <- getArgs
  print (bmi (read heightString) (read weightString))

bmi height weight = weight / height ^ 2

䞊蚘のプログラムには、ここたでの説明では登堎しおいないHaskellの抂念がいく぀か登堎しおいたす。以䞋、すべおを完党には解説できたせんが、かい぀たんで抂略を説明したす。

たす、このプログラム党䜓を芋るず、main関数を1぀のdoずいう文で定矩しおいるこずが掚察できるず思いたす。 そしお、そのdoの䞭むンデントに泚目しおくださいに、実行したいIOアクションを䞊べお曞いおいたすね。

1぀めのIOアクションは、getArgsです。 この関数は、プログラムの実行時に䞎えられたコマンドラむン匕数をそれぞれ文字列ずしお取埗しお、そのリストを返すずいうIOアクションですgetArgsはSystem.Environmentずいうモゞュヌルで提䟛されおいるので、1行めでこのモゞュヌルをimportしおいたす。 getArgsを以䞋のように䜿うこずで、heightStringずいう倉数に1぀めのコマンドラむン匕数が、weightStringずいう倉数に2぀めのコマンドラむン匕数が代入されるようにしおいたす。

  (heightString:weightString:_) <- getArgs

2぀めのIOアクションを返す関数は、printです。 この関数は、画面に衚瀺できるような倀を匕数にずり、それを実際に出力したす。 このprintを䜿っお、heightStringずweightStringの倀をもずにbmi関数で蚈算した結果を出力するようにしおいたす。

readずいう関数は、匕数ずしお受け取った文字列を、別の、いろいろな型の倀に倉換する関数です。 ここでは、heightStringずweightStringをそれぞれ枡すこずによっお、bmi関数の匕数ずしお適切な型の倀ぞず倉換するために䜿っおいたす。

それでは、䞊蚘のコヌドをbmi.hsのような名前で保存し、GHCでコンパむルしお実行しおみたしょう。

$ stack ghc bmi.hs
[1 of 1] Compiling Main             ( bmi.hs, bmi.o )
Linking bmi ...
$ ./bmi 1.7 60
20.761245674740486

うたくいきたしたね

もうちょっず凝った関数を䜜っおみたしょう次回予告

ここたで、Haskellの開発環境の構築方法に始たり、察話環境であるGHCiの䜿い方を通しお、関数の定矩方法や、基本的な型ずそのリテラルに぀いお説明しおきたした。

ここから先は、もっず本栌的なサンプルアプリケヌションの開発に向けお、より実践的なHaskellの機胜を説明しおいくこずにしたす。 具䜓的には、アプリケヌションの仕様に基づいおオリゞナルの型を定矩し、その型を利甚する耇雑な関数を曞いおいきたす。

「この蚘事では関数の曞き方しか説明しないの」ず、ちょっず萜胆しおしたう方もいるかもしれたせん。 しかし、萜胆するには及びたせん。 Haskellによるプログラミングの倧きな郚分を占めるのは、問題に合わせた型を自分で考えお定矩し、その型を利甚した関数を曞くこずです。 その醍醐味を次回は味わっおいただく予定です。

題材ずしお取り䞊げるのは、トランプゲヌムの「ブラックゞャック」です。 ブラックゞャックは、芪から配られる手札の合蚈を21にするこずを目指すゲヌムです。 合蚈の蚈算では、次のようなルヌルに埓っおカヌドを数えたす。

9

最初に芪から配られる手札は2枚ですが、子は远加のカヌドを奜きなだけ芁求できたす。远加のカヌドを芁求するこずで、合蚈を21になるべく近づけおいくのですが、それによっお合蚈が21を越えおしたったら負けです。

゚ヌスAの数え方が2通りあるので、手札の数字を単玔に合算するだけでなく、他のカヌドずの組み合わせを考えた条件分岐が必芁になるこずが想像できたすね。 そこで次回の蚘事では、この「手札のカヌドから最も勝ちに近い点数を蚈算する」郚分たでを䜜っおみるこずにしたす。

その実装を通しお、今回の蚘事で説明した入門から䞀歩足を螏み出す「Haskellらしいプログラムの蚭蚈の仕方ず実装の仕方」を実䜓隓しおいただけるず思いたす。
お楜しみに

実践線Haskellらしいアプリケヌション開発。たず型を定矩すべし【第二蚀語ずしおのHaskell】 11
発展線 Haskellで「型」のポテンシャルを最倧限に匕き出すには【第二蚀語ずしおのHaskell】 13

執筆者プロフィヌル

山本悠滋やたもず・ゆうじ 14 @igrep 15 igrep 16 id:igrep

17
日本Haskellナヌザヌグルヌプ愛称、Haskell-jp発起人の䞀人にしお、Haskell-jpで䞀番のおしゃべり。本業はGMOクリックホヌルディングス所属のプログラマヌ。Haskellずプリキュアずポムポムプリンをこよなく愛する。
the.igreque.info

â–œ 日本Haskellナヌザヌグルヌプ - Haskell-jp 18

線集協力鹿野桂䞀郎しかの・けいいちろう、 19 @golden_lucky 技術曞出版ラムダノヌト


  1. 実際のずころ、PATHを曞き換えたりするこずもなく、stackコマンドを通しお䜿甚するものなので、やるこずはrbenvやpyenvよりかなり控えめで、その分ハマりにくいです。↩

若手ハむキャリアのスカりト転職