趣味でやるHaskell入門2 〜実用編〜
趣味でやるHaskell入門2〜実用編〜
前回の課題とかの答え進数の数字を表す文字列を整数に直す関数を作りましょう。
再帰と 文で実装
を使って実装
おまけ(標準の関数をいろいろ使ってみる)
おまけ
リストの中身を足し合わせる
はリスト順序の逆転 は の二項演算子化リスト内包表記。集合論でよく見る書き方と同じ。
hoogleを使ってみよう前ページの「おまけ」のように標準の関数だけ見てもいろいろ揃っている。
これらの関数を検索するのに関数の検索サービス「 」がある。
ちなみに もある。
標準の関数をサーチするのには 外部のパッケージも含めた関数をサーチするときには
がそれぞれ向いている。
hoogleの使い方「関数名」で検索
…
「型」で検索
…
外部パッケージの関数を検索する場合、検索ワードの後ろに パッケージ名をつける。
ちなみに は行列計算のパッケージ。
検索結果をクリックすれば (パッケージ管理サイト)でドキュメントを見られる。
型クラス
型クラスは複数の型を性質ごとにまとめたもの。
や で型を調べると、
のように、 という記号が使われていることがある。 記号の左にある が型クラスの一つで、「数値とし
て扱える型」がまとめられている( というのは が 型クラスに属するという条件。 や は
問題ないが、 型の値に を作用するとエラーになる)。
型クラスも で のように詳細を調べることができる、
型クラスの例
などが定義可能な型の集まり。
文字列に変換可能な型の集まり。
で比較可能な型の集まり。
型クラスShow,Eqについて
など、データ型定義のあとに をつけると、一部の型クラスでは勝手に登録してくれる(この場合
型は勝手に クラスと クラスに登録され、 関数 が使えたり で比較できたり
する)。すべての型クラスが対応しているわけではない。
型クラスの例(日本語では関手という)
データ型の中には や など、引数のようなものを1つもつものがあるが、例えばそういうデータ型を
とすると、
(ちなみに二項演算子版の もあり、 と同じ)
が定義できるもの(例えばリスト は が定義できるので に属する)。
ちなみに型クラスの中には のドキュメントに注意が書かれているので自分で新しく型を登録するとき
は気をつけなければならない例えば クラスに を登録するとき、恒等写像を 、関数合成演算子を
とすると と を満たさなければならない。
発展:型クラスって何がしたいの
学部における理系の初年次教育を受けた方ならわかると思いますが、高校まではベクトルを「矢印」であると
して勉強していたのが、大学に入ってからは時間に依存しないシュレディンガー方程式(Ĥψ ψ)のように関
数をベクトルであるかのように計算したり、フーリエ変換を考えるときに波をベクトル、規格化された正弦波等
を単位ベクトルとしてどの周波数の波がどれくらい含まれているかを成分表示したりするなど、「ベクトルとして
計算できるものをベクトルとして扱う」機会があります。
ではこのように既存の型が「どのように計算できるか」を分類するためのものとして型クラスを用意して
います。例えば皆さんが新しい型を クラスに を使わず自力で登録するとき、「この型は文字列
に変換できるよ!」と宣言するだけではなく、「どうすればこの型を文字列に変換できるか」の方法が必ず1つ
は存在することも に教えてあげる必要があるということです。
発展:Functorが何したいのかわからん第一回で説明したとおり、 の基本は「型の定義」と「関数の定義」。
(引数に1を足す関数)
ここで、今ある「型と関数の世界」を「 圏」とすると、 圏における型と関数の定義がすべてリスト化(あ
るいは 処理化や 化)した世界を考えることができる。このとき型の定義は、
だとか、
といったように、始域と終域がそれぞれ変化する。
ここで クラスに属していることにどういうメリットがあるかというと、 関数に を作用させるだけ
で、ここでいう や のような型の関数が必ず1つは存在することが保証されているという
点にある。すべての型と関数がリスト化された世界「 圏」に「行きたくなった」ときは、型の定義の始域と終域
に を、関数の定義に を加えるだけで簡単に目標を達成できるというふうに考えると良い。本来は や
など、型を引数に新しい型を生成するものと、それに対応する の組み合わせを (あるいは
関手)と呼ぶのが妥当だが、まぁ型クラスの仕様上こういう定義になってるよというわけです(ちなみに 圏も
圏の一部なので無限に してリストのリストのリストのリストみたいな重ねがけはできます)。
終域例:Int整数の集合
発展:興味のある人向けFunctorの概要図
始域例:Int整数の集合
関数
終域例:[Int]整数のリストの集合
始域例:[Int]整数のリストの集合
関数
型コンストラクタ( とか とかも)
本来は型コンストラクタと の組による二つの「型と関数からなる世界」の対応付けを
と呼ぶのが妥当だが、利便性とか考えてああいう定義になってるよということ。
(あと、今は型と関数について話しているので、実際に引数に適用する値がどうなるかは置いておいてください)
今いる世界
新しい世界
型コンストラクタ( とか とかも)
やっぱり手続き型言語をしたいです……。
( と画面に表示)
( の内容を読み込む)
(上の行で読み込んだ内容を画面に表示)
みたいなことを でしたくなったとして、実はそういう機能をサポートする型クラスがあります(
と )。
……の前に IO a 型 をやりましょう。型の「結果」を持つ 処理の集合
ファイルパスをとって、ファイルの中身を「結果」とする 処理を生成。
型には しか入らない(「結果」に特に意味はないということを表現している)。文字列を引数にとり、「文字列
を画面に表示する、結果が な 処理」を生成。
めっちゃ今更だけど ができますね。
Applicativeクラス大体の特徴: クラスに属していて、かつ次に示す2つの関数を定義可能
( の二項演算子版が )
例:リスト は に属する。
(要素数3のリスト) (要素数2のリスト)=(要素数6のリスト)
値についたデータ型の「構造」を合成できるのでは……?
ファイル先頭に import Control.Applicative が必要
Applicativeクラスクラスであれば、次の2つの関数を使える
例
リストの要素数という「構造」を合成した上で、中身だけ片方のものを使用。
ファイル先頭に import Control.Applicative が必要
ApplicativeクラスとIO処理リスト ではリストの要素数が合成されたが、 型では 処理が合成できるのでは?
は表示したあと改行しない。 は表示したあと改行する。
や は一方の結果を捨てるが、どちらを捨てるかを選べる。
まぁマジな話、最初は と で 処理が合成できることだけ覚えて帰ってください!
Monadクラスクラスに属していて、かつ次に示す2つの関数を定義できる。
(ぶっちゃけ の と同じ。 )
(二項演算子版が )
( いらなくね?って思うかもしれないけど昔は「 に属していて」という条件がなかったのでその
ときには必要だった)
余談: という関数もあります。数学的には と で定義するらしいです。
ファイル先頭に import Control.Monad が必要
IO String型の値から文字列を取り出したいけど 型の値を簡単に 型に直せてしまったら「どこで 処理したかわからないけど実行のたびに
結果が変わるヤバい関数」が生成できてしまう。そこで「 は外さないまま の中身を参照できる関数がほし
い!」ということで 関数( )を使います。
の中身を の引数に直接ブチ込むなら を外す必要はない!ということで がこういう定義
になっています。
do糖衣構文は便利だけど使いすぎると何がなんだかわからなくなります。
( は と同じ結果になります)
そこで、 糖衣構文を使ってこのように書き直します。
文字列結合演算子 により結合した結果を画面に表示。
ワイルドカードパターンにより結果は放棄される。
この場合 構文全体で 型になる。当然 でも良い。
AlternativeとMonad Plusに加えてもう一つ演算子 が定義されているのが
に加えて以下略なのが
たいていエラー処理に使われます。
ちなみに には には という「必ずエラーになる値」が用意されています。
型について、
でも良い。
型について、
が存在すればその中身、なければ と表示する。
ちなみに には 関数があり、引数が なら な
ら になる関数がある(ガード文とは別)。
それぞれ Control.Applicative と Control.Monad のimportが必要
let文と同じく局所定義ができる
定義 その定義を用いる式
糖衣構文の中では をつけなくて良い
ちなみに は で できる関数で
の代わりに使って「結果を捨てる」ことができる
実際にプロジェクトを作ってみましょう
… コンパイラ。某牛さんではなくグラスゴー大学の作った コンパイラ。
… パッケージ管理システム。
従来はこの2つで開発されていましたが、 の性質上あるパッケージが関数を統廃合したときそれに依
存している他のパッケージが使用できなくなるみたいなことがしょっちゅうあったので、
というサイトが「このバージョンの組み合わせなら安全だよ」という情報を発信してい
ました。これを自動で参照してパッケージを導入したりコンパイラのバージョンを調整してくれたりするツール
が今みなさんが使っている です。
(余談だけど業務で使うようなパッケージはコンパイルされて とかのパッケージにもなってるので、人によっ
ては を使わずに コンパイル済みパッケージで開発したりしています)
stackによるプロジェクト作成
(パッケージ名)(テンプレート名)
パッケージ名は通常小文字から始まる(大文字でも良いっぽい)。
テンプレート名は通常 を使うと良いが、今回に限りテンプレート名は空白にしておいてください。
例えば と実行したとする。
stack.yamlとpackage.yaml
ソースファイルの場所( と別れているのはそれぞれ実行ファイル用、外部からパッ
ケージとして参照する用、動作テスト用。 テンプレートを使うと後ろ2つは省略される)、必要な外
部パッケージ(と必要であればそのバージョン)を書く。
(さっき言った が提供している この組み合わせのパッケージなら安全だよ!なリストの
バージョン)を指定、 に登録されていないパッケージはバージョンを指定するよう要求される場合があ
るのでその時のバージョンもここで指定
ところで、もし がエラーを起こすときは、 を実行して最新の を導入するか、それでもだ
めなときは のバージョンを少し下げて新しい が出るのを待ってください
警告全部乗せセット
の の下に、それぞれ
を追加しておいてください。
警告(エラーではない)を全部出してくれます。
パッケージ”dlist”を入れてみよう!
とは……
標準の 型は … という形でできていて遅延評価と合わせることで先頭から要素を取
り出したり、先頭に要素を追加することには向いているが、例えばリスト結合演算子 を利用して
(要素が何十万とある文字列)
とかやると左側の文字列が今まで評価されてなかったのを全部結合してから の結合を行うのでめっちゃ遅
くなるという問題が生じる。
とはいえ遅延評価は便利なので「右に要素を追加したり右から要素を取り出すのに特化した遅延評価リスト」
の必要性はある。ということで 型があり、ログの書き込みなど、後ろから要素を追加するのに利用され
る。
実際に入れてみる
コマンドでプロジェクトのディレクトリに移動して、
の一番上にある のところに
を追加( のようにバージョンを指定できるけど今回はやりません)
dlistパッケージを使ってみる
の にアクセスすればどういうモジュールを提供
しているかわかる(バージョン の時点で のみ)。 のリンクをクリックすればどういう関
数を使えるかの説明が書いてある。これらの関数を使いたいときはファイル先頭、モジュール名のあとに
を追加する。あるいは使いもしない関数を するのはバグのもとなので
のように使いたい関数の名前を指定した書き方を追加する。
import の使い方いろいろ
内で指定した関数しか しない
接頭辞をつける。例えば他のモジュールから別の 関数を しているときにこれを追加すること
で、 と書いて と書いてもいいんだけどどのモジュールで定義されている
関数なのかはっきりさせることができる。 つけないと をつけなくてもエラーにならない。
は標準で される定義。 を使うことで や他のモジュールから したくないものを
指定できる。
直積型の定義に使われる関数も できる
参考:
モジュールの分け方ディレクトリ内に コマンドとかをつかって や というディレクトリを作る。
その中にファイルを作る(例えば )と他のファイルから
のように、外部パッケージのモジュールと同様 できる。
例えば学校の課題で ( のソフト)を使った課題を作りたいときに、学校の課題に関係なく を使う
プロジェクト全体で使える関数は モジュール に、学校の課題でし
か使わない関数は モジュール に分けておけば他の用事で
を使う必要が出たときに作業時間を短縮できる。
発展:DListを使ったデータ型作成
型に登録
は「結合法則のある二項演算が一つ定義できる型」のクラス(文字列とか)。 は
に加えて「空っぽのデータ」が定義できるもの。これにより 情報1 情報2 … 最後の情報
(それぞれの情報の はすべて1)、みたいな感覚でどんどん後ろにデータを追加できるうえ、 関数で
何個情報を追加したか の要素数を計算しなくても出せる。
バージョンの古い では問題が発生するので注意!(参考
: )
前ページの補足
……
ghc拡張
今の所 のコンパイラはほぼ のみが利用されていますが、 では 自体の仕様に含まれてい
ないがあると便利な機能を利用することができます。
ファイルの先頭、モジュール名の前に 拡張名 を追加すると利用できます。
例
… バイト列の型 など 型でないものも のようにダブルクオーテー
ション表現できる(どの型かは型推論で決定される)
… 一部の型クラス などはデータ型定義のあとに を追加すれば自動で
登録してくれるわけだけど、データ型定義のあとでなくても、あるいは外部パッケージのデータ型についても
みたいに書けば登録してくれる。
… そのまんま。データ型定義のあとに と書けば自動で クラスに登録し
て 関数が使えるようになる。
標準で入っている パッケージには 〜みたいなモジュールがありますが、 拡張用のサポートなので
それ以外の用途で使うのは推奨されません。
外部パッケージいろいろ
高速なパーサ(文字列解釈)ライブラリ。
型(文字列から 型を読み込む文字列解釈器の集合)が クラスに属しているので とか で
結合しまくって、
(読み込みたい文字列) (取り出したい結果のデータ型) 結果を表すデータ型
という関数で取り出す。
を利用したゲーム用・シミュレーション用のパッケージ。ゲーム用だと他には というパッケー
ジもある。
行列計算用のパッケージ。 の と同じく と を別にインストールしないと動かないよ
( なら など)
外部パッケージいろいろ
略して 。厳密には一つのパッケージではなく 用の複数のパッケージの集まり。 のデータベースを指
定しておくと のデータ型を自動生成してくれて の構文中でデータベースへの書き込みや読み出
しなど大体の処理ができる。ちなみに の日比野さんが開発に携わってるよ。
なぜか標準の複素数 型 に割り算が定義されてないのでそのへん定義してくれてる
型を用意してくれてるやつだよ。
†††ディープラーニング†††ちなみに というパッケージ管理システムがあると便利だよって書いてあるけど でも を
にすれば使えるよ(数物系とか仕事関係以外の用途で使う場合パッケージが不足してい
る領域というのはまぁ存在していて、そういうときに全然更新されてない古いパッケージを利用することもある
と思うけど、そういうときは を 最後に更新された日付にするといいよ)。
簡潔に書くためのTips再帰でループ処理を書いてもいいけど、 などで用意されている関数を利用すれば簡潔に
かける場合が多い!
または リスト要素の和 要素の積 …… ループ処理により
一つの値が生成
の が必要
……繰り返し処理の中でついでにエラー処理もできる( の要素数より の要素数が少なくなりうる)
リスト内包表記
の式 リスト で数学と同じような表記法ができる。例えば で2のべき乗が入ったリストが
生成する。 みたいな書き方もできる。
Tips続き
関数 … Σ を例に
として、
第一引数に 型のリスト、第二引数に 型のリストを与えると
タプル 型のリストを返す関数
ちなみに 型は パッケージで提供されている型で、名前に反してリストの拡張版みたいなパッ
ケージです。線形代数を扱いたい場合は パッケージを利用してください。
(ちなみに の性質しか使ってないからを を全部 に直しても動くよ)
その他の補足
処理を遅延 にする(標準では 型 が遅延 で、結果の 型は必要となるまで読
み込まれず、必要になった分しか読み込まれない。大容量のファイル向け)。そういうわけで
…とかやると、ほかの 処理も同じようにできる。バグが起きたときにどこでバグったかわからなくなるなどの欠
点があり、あまり使用は推奨されない。一度私は無限長の乱数リストを生成する 処理 を定義す
るときに使いました。
デバッグ用。 ( 型じゃないけど 処理が起こる)など、デバッグに便利な関数が揃って
いる。もちろんデバッグ以外の用途に使わないこと。
今後の学習に使える参考文献
●
東大理学部数学科で過去に行われたっぽい授業の教材。 の言語設計に使われている数学について
ざっくり知りたい人はこっちを見てね。ちなみに 使ってなかったりするから気をつけてね。
●
前回説明したやつ。書いた人は見ての通り。
の順にやるといいのではないでしょうか。多分前回と今回の知識があれば演習問題もできると思います。ちな
みに は 使わない前提の設定ファイルです。
● すごい たのしく学ぼう
前回説明したやつ。もっと詳しくいろいろ書いてるよ。
その他の参考文献
ベーシック圏論 普遍性からの速習コース Tom Leinster 丸善出版 今回扱ったfunctor等の説明はここから参考にしました。
周辺領域
は 上で動作するタイル型ウィンドウマネージャ。マウスをほぼ使わずに操作でき、
軽量なので使いやすいです。 は 用のツールバー。どちらも設定ファイルを で書く。その
うち 用の解説を書きます。
上で動く っぽい言語と 上で動作する 環境。 で アプリ作ってる人がいた。
っぽい とコンパイルしたら が出てくる 環境