Old/sampou.org/Programming_WayToHaskeller
Programming_WayToHaskeller
Programming:WayToHaskeller
Haskeller への道
- Hello, World! プログラム
- 実行ファイルにしてみよう
- runhugs
- やはりブラックボックスなのか?
- さらに echo.lhs とかね
- do しましょ
- あっちゃの世界の引数とこっちゃの世界の引数
- モナドを遠目に見る
- cat.lhs とかもみとく
- 【ちょっとひとやすみ】ピュアな怠け者
- リストもモナド?
- main は関数じゃねーのか?
- IO a の a と [a] の a
- thunk はどう書くのか?
- 型宣言をみてみよー!
- 型推論より厳しい型宣言の必要性
- たらいまわし関数
- 続たらいまわし
- デバッグの手段を知ろう
- 【ちょっとひとやすみ】メイビー?
寝がえり
勉強よりまず処理系のインストール
まずはHaskellをインストールしてみる訳だけど、面倒なのはいきなりくじけ る。私のモットーはまずは動かしてみる。だ。(今決めた) もう何年も前から PC-UNIXの世界ではサードパーティー製のソフトをワンタッチでインストール できる仕組みがあったりする。 Linux だと rpm か? FreeBSD なら ports collection、 NetBSD だと package など。まぁ、NetBSDなんかだとバージョ ンが古かったりする可能性もあるが、気にしない。(そりゃ単に私がちゃんと 最新化管理してないからか?)
私の生活環境は NetBSD1.6.2 なので package から make してインストールしてみる。
インストール作業そのものが好きな人には何の刺激もないが、まずバカチョン でインストールできるHaskell処理系にはどんなんがあるんかいな?
[email protected]> pwd
/usr/pkgsrc
[email protected]> ./pkglocate Haskell
(snip)
lang/ghc/DESCR:GHC: The Glasgow Haskell Compiler.
lang/ghc/DESCR:The Glasgow Haskell Compiler is a robust, fully-featured, optimising
lang/ghc/DESCR:compiler for the functional programming language Haskell 98
(snip)
lang/hugs/DESCR:The Nottingham and Yale Haskell interpreter and programming environment.
lang/hugs/DESCR: a Haskell interpreter and programming environment for developing
lang/hugs/DESCR: cool Haskell programs. Sources and binaries are freely available
(snip)
lang/nhc98/DESCR:nhc98 is a fully-fledged compiler for Haskell 98, the standard lazy
lang/nhc98/DESCR:compiler for an earlier version of the language. Written in Haskell,
lang/nhc98/DESCR:With hmake, a replacement for the other makes used in Haskell
どうやら Hugs、 GHC、nhc98 ってのがそうらしいので、全部入れちゃう。ここはHaskellの話題じゃないの でインストールの詳細は書かない。とにかくすでにインストールしてくれた先 人のおかげで失敗することなんてないでしょ。
[email protected]> which nhc98
/usr/pkg/bin/nhc98
[email protected]> which ghc
/usr/pkg/bin/ghc
[email protected]> which ghci
/usr/pkg/bin/ghci
[email protected]> which hugs
/usr/pkg/bin/hugs
はい。処理系が手に入りましたね。
ど・れ・に・し・よ・う・か・な
とりあえず、石橋を叩かず渡る派の私はこれらのコマンドをまんま呼び出して みる。まぁいざとなれば外から kill すりゃいいじゃないの。
[email protected]> ghc
ghc-5.04.3: no input files
Usage: For basic information, try the `--help' option.
ありゃ、だめだ。
[email protected]> nhc98
nhc98: error: no source, object or archive file specified
お前もかブルータス。
[email protected]> ghci
___ ___ _
/ _ ? /? /?/ __(_)
/ /_?// /_/ / / | | GHC Interactive, version 5.04.3, for Haskell 98.
/ /_??/ __ / /___| | http://www.haskell.org/ghc/
?____/?/ /_/?____/|_| Type :? for help.
Loading package base ... linking ... done.
Loading package haskell98 ... linking ... done.
Prelude> ^D Leaving GHCi.
お、いけそう。
[email protected]> hugs
__ __ __ __ ____ ___ _________________________________________
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2001
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Report bugs to: [email protected]
|| || Version: December 2001 _________________________________________
Haskell 98 mode: Restart with command line option -98 to enable extensions
Reading file "/usr/pkg/share/hugs/lib/Prelude.hs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
Type :? for help
Prelude> ^D [Leaving Hugs]
ほほー、こいつが hugs ね。
実は ghc と nhc98 てのはコンパイラらしい。オンラインマニュアルの最初に compilerって出てたりもするんで多分そうだろう。今のところ、あまり細かい ことは気にしない。 一方 ghci と hugs ってのが何やらプロンプトが現れたので interactive に遊べそう。
最初はプロンプトからちまちま入力してみるのがいい。すぐに反応も見れて楽 しめた方が勉強する気にもなるってもんだ。とりあえずプロンプトが出て来た 奴で進めることにしましょう。
hugs ってのが軽くてよさそうってのをどっかで読んだような気がするんで hugs で GO!
Preludeってなんぞや?
インタプリタのプロンプトって
[email protected]> gosh
gosh> ^D
[email protected]> stk
Welcome to the STk interpreter version 4.0.1 [NetBSD-1.6.2-i386]
Copyright 1993-1999 Erick Gallesio - I3S - CNRS / ESSI <[email protected]>
STk> ^D Bye.
みたいに「俺は gosh だぜ!」とか「我輩は STk なり!」って主張するか、 もしくは全く主張なく“>”みたいに印字するものと思ってた。なぜか Haskell は “ghci>” とか “hugs>” とかってんじゃなく、“Prelude>” ってのがプロン プトになってる。Prelude とは何ぞや? と気になるもの。辞書引くと前奏曲と か序幕とかなんだけど、芝居がかった名前ですぞ。
ヘルプ
この WiKi の HowTo:List? とか HowTo:Maybe? とか見ると入力事例が あるんで、真似してみましょう。ただ、ところどころプロンプトがなぜか List> とかって変わってるのが気になる。
どうも事例の雰囲気をじーーーーっっと見ているとプロンプトのところに出て いるのはモジュールの様なものかな〜そんな感触である。 (念のため断ってお くが現時点で本当に私は入門者なので正しいかどうか不明だから信用しないよ うに)
まずは起動メッセージのところに :? ってしたらヘルプが見れる様なことが書 いてあったので、メッセージを確認して、色々試してみよう。
Prelude> :?
LIST OF COMMANDS: Any command may be abbreviated to :c where
c is the first character in the full name.
:load <filenames> load modules from specified files
:load clear all files except prelude
:also <filenames> read additional modules
:reload repeat last load command
:project <filename> use project file
:edit <filename> edit file
:edit edit last module
:module <module> set module for evaluating expressions
<expr> evaluate expression
:type <expr> print type of expression
:? display this list of commands
:set <options> set command line options
:set help on command line options
:names [pat] list names currently in scope
:info <names> describe named objects
:browse <modules> browse names defined in <modules>
:find <name> edit module containing definition of name
:!command shell escape
:cd dir change directory
:gc force garbage collection
:version print Hugs version
:quit exit Hugs interpreter
Prelude> [Leaving Hugs]
おお?なんか scheme48 を思い出してしまった。えっと、勘が正しければこう かな?えいっ!
Prelude> :module List
ERROR - Cannot find module "List"
あらハズレだ。エラーが出るところを見ると Prelude ってのがモジュール名かと思ってたのはハズレかもしれん。ならば!!と言うことで
Prelude> :load List
Reading file "/usr/pkg/share/hugs/lib/List.hs":
Reading file "/usr/pkg/share/hugs/lib/Maybe.hs":
Reading file "/usr/pkg/share/hugs/lib/List.hs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
/usr/pkg/share/hugs/lib/Maybe.hs
/usr/pkg/share/hugs/lib/List.hs
List> <= コレ!!
おおっ、なんかイけたみたいだよ。じゃあ、調子に乗って Maybe にも。
List> :load Maybe
Reading file "/usr/pkg/share/hugs/lib/Maybe.hs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
/usr/pkg/share/hugs/lib/Maybe.hs
Maybe> [Leaving Hugs]
ぱちぱちぱち。って、まだ何の式も打ち込んでないぞ。いいのか?
で、これって分かると思うけどファイルの path ですよね。じゃぁ現物見ましょ う。なんせインストールをバカチョンでやったんで、どこに何のファイルが入っ たかなんて押えてないしね。
-----------------------------------------------------------------------------
-- Standard Library: List operations
--
-- Suitable for use with Hugs 98
-----------------------------------------------------------------------------
module List (
elemIndex, elemIndices,
find, findIndex, findIndices,
nub, nubBy, delete, deleteBy, (??), deleteFirstsBy,
union, unionBy, intersect, intersectBy,
intersperse, transpose, partition, group, groupBy,
inits, tails, isPrefixOf, isSuffixOf,
List.hs の中身です。しっかと module List の文字が光って見えますね。光っ て見えませんか?てなわけで、やーっぱモジュールじゃん。しかもいっぱい haskell のコードがあるんで、いずれ勉強に役立つやもしれず。
それで、もう一度ようく、 :load の結果を見てみると、 :load List の結果 として、どうやら Prelude.hs/Maybe.hs/List.hs の三つが出現している。
そこでロードした後に、もしかしたらモジュールを飛び回れるかも?とか思い 試してみる。
Prelude> :load List
Reading file "/usr/pkg/share/hugs/lib/List.hs":
Reading file "/usr/pkg/share/hugs/lib/Maybe.hs":
Reading file "/usr/pkg/share/hugs/lib/List.hs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
/usr/pkg/share/hugs/lib/Maybe.hs
/usr/pkg/share/hugs/lib/List.hs
List> :module List
List> :module Maybe
Maybe> :module Prelude
Prelude> :module List
List> :module Maybe
Maybe> :module Prelude
Prelude> [Leaving Hugs]
ぱちぱちぱちぱち。でも原理はよく分からないね。ただ、List ってモジュー ルが Maybe ってモジュールを必要とするらしく、芋づる的にロードされるら しいってのは分かる。 :module ってのは Gauche とかで言うところの select-module みたいなもんかなぁ。
ちなみに ghci だと、
Prelude> :module List Prelude List> :module Maybe Prelude Maybe> :module List Maybe Prelude Maybe List> Leaving GHCi.
こんな風にプロンプトに複数の module をロードしてやることでプロンプトに も複数のものが表示されますね。意味は知らんが、直観的にはなんとなく分か るような気がしますよね?この辺もその内分かるようになるでしょう。多分。
Emacs を使いたい
さらに調子に乗って関数なんぞを定義してみよう。 まずはなんてったって、クイックソートでしょう。 Haskell の入門だと大抵 出ているので、試してみましょう。
quicksort [] = []
quicksort (x:xs) = quicksort [y | y <- xs, y<x ]
++ [x]
++ quicksort [y | y <- xs, y>=x]
こんなコードだね。んじゃレッツトライ!
Prelude> quicksort [] = []
ERROR - Syntax error in input (unexpected `=')
ありゃりゃ?もしかしてデータ型の定義が先にいるのかしらん。
Prelude> quicksort :: [a]->[a]
ERROR - Undefined variable "quicksort"
Prelude>
むむむ、だめだぞコレ。なんだろう???
というちょっとわざとらしいフリをしてしまったけど、実際最初は結構迷った りします。私の場合にも結局教えてもらったんですよね。 実はプロンプトから直接定義したりできないんだってさ。これってちょっと、 いや、かなり不便かも。対話的にイロイロできないってのはコーディングから デバッグまでのサイクルがひょいひょい回らないんで、個人的にはひどく嫌な わけだ。
実は hugs から :e を使うと環境変数 EDITOR で設定されたエディタが立ち上 がる。こいつを使ってみよう。使えればまぁなんとかなるかも知れない。
その前に、 lhs っていう拡張子のファイルについて簡単に説明しちゃいます。 hugs のオンラインマニュアルを見ると literate scripts と紹介してある。 これは基本的に haskell 処理系に対しては、全部コメント扱いになるファイ ルなのだが、行頭から > という風に
リダイレクト記号と空白から始まる行だけはコードと認識されるんだって。 例えば、こんな感じに書くんだとさ。
begin tag ...
> quicksort [] = []
> quicksort (x:xs) = quicksort [y | y <- xs, y<x ]
> ++ [x]
> ++ quicksort [y | y <- xs, y>=x]
end tag ...
使い道としては コメント扱いにするエリアに HTML のタグや WiKi のタグ、 TeX のタグなんぞを書いておけば、そのまま test.lhs を文書などに貼り付け ることが出来ちゃうのだ。(わー便利ー) もちろん普通に test.hs なプログラムを書いておいてもいいんだけどさ。
じゃぁ早速、少しプログラムにバグを仕込んでおいて、試してみましょう。
Prelude> :load test.lhs
Reading file "test.lhs":
Parsing
ERROR "test.lhs":3 - Program line next to comment
うむ、では正しいコードに修正してみる。
Prelude> :e test.lhs
Reading file "test.lhs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
test.lhs
エディタで修正後、保存して抜けるとこんな感じになるので、ちょいと quicksort を実行してみましょう。
Main> quicksort [2,9,1,0,3,6,2,7,6,4,8,9]
[0,1,2,2,3,4,6,6,7,8,9,9]
Main>
わーい。ぱちぱちぱち。素晴らしい!
ちなみによーく見るとプロンプトが Main になっておりますね。これがどうも user のためのモジュールっつうか評価環境なんだろうか。そんな雰囲気。
さてと。。。
それでも scheme + emacs でコードを書いていた私としては、どうもイマイチ 感は拭えない。 なんせエディタとプロンプトを行ったり来たりしないといけないしさ。
もちろん、NetBSD の package から make した hugs だと readline が生きて いるらしく、hugs のプロンプトからちゃんと行編集も可能だったりするんだ けど、やっぱりねぇ。。。 というわけで emacs で画面を分割して使えないんかな?というと、実は使え ます。ハイ。
まずは準備としてここに emacs のための haskell modeってのがあるんで、 emacs 使いの方は引っ張って来るよろし。でもってインストールしましょう。 これも別に難しくないので installation-guide を見て使えるようにしてくだ さい。
そしたら配布物に入っている .emacs を自分の .emacs にコピぺしましょう。 もしあなたが、hugs じゃなくて ghci を使っているなら
;(add-hook 'haskell-mode-hook 'turn-on-haskell-simple-indent)
;(add-hook 'haskell-mode-hook 'turn-on-haskell-hugs)
(add-hook 'haskell-mode-hook 'turn-on-haskell-ghci)
こんな風に最後の方を修正してください。 hugs を ghci に変えた行を追加し て haskell-mode-hook ってのに add-hook します。 hugs の方はコメントア ウトしちゃっていいです。 もち、 hugs を使っている人はそのまま使えちゃいます。
[email protected]> emacs test.lhs &
って emacs を立ち上げたらコードを開いているバッファ上で C-c C-l って打 ち込んでみればいいんだって、ワクワク。 (コントロールキーを押しながら c(シー)とl(エル)です)
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
Type :? for help
Prelude> :load /home/cut-sea/script/haskell/test.lhs
Reading file "/home/cut-sea/script/haskell/test.lhs":
Parsing........................................................................
Dependency analysis............................................................
Type checking..................................................................
Compiling......................................................................
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
/home/cut-sea/script/haskell/test.lhs
Main>
どうでしょうか?画面分割されて、こんな風に Main プロンプトが出なかった でしょうか?ここまでくればかなり便利になりますね? emacs の編集機能を 駆使しつつ、適当に C-c C-l ってやれば良いんです。だいぶ便利じゃぁあり ませんか?
ちなみにghciの方を有効にして同様に試すと、
Prelude> :load /home/cut-sea/script/haskell/test.lhs
Compiling Main ( /home/cut-sea/script/haskell/test.lhs, interpreted )
/home/cut-sea/script/haskell/test.lhs:2:
Warning: No 'main' defined in module Main
Ok, modules loaded: Main.
*Main>
あり? main が無いって叱られます。 でも、 quicksort を実行するとちゃんと評価可能ですんで、あまり気にしな いで良いのかも知れません。まぁ warning だしね。メッセージからする とHaskellってCみたく main がいるのやも知れんな。 ともあれ便利な開発環境が手に入ったところで、そろそろ腰を据えて取り掛か ろうかしらん。
【ちょっとひとやすみ】「非正格」って?
Haskell は非正格純粋関数型言語っていう冠がついてたりする。いーじゃない ですか。なんかこう難しそうで。会社で「非正格純粋関数型言語Haskellって のがあってさ〜」なんて話そうもんならなんか恰好よさげじゃないですか。え え。もしかしたら女の子の瞳がハートマークになるかも知れませんよ。
ところが、「非正格」って何さ?って聞かれて「いや、まあそのなんだ」とか なんとかしどろもどろになると途端に株が下がるかもしれない。
どうも正格関数ってのは
f _|_ = _|_
ってなる様なfって関数で、そうじゃないものを非正格って言うんだとさ。ちなみに _|_ は本当は一文字の記号でボトムとか読むらしい。
上司:「おいおい、良くわかんねぇよ。 いきなり_|_とかワケわかんない記号出して誤魔化そうとするな。 おまえは大体そういう・・・」
はい、そうですね。ごもっともでございます。
_|_ は未定義値
です。つまりこんな感じですね。
妻:「ねぇ、あなた!いつもいつも仕事仕事って、私と仕事とどっちが大切なの!?」 夫:「そんなん決められっかよ!比較できるもんじゃねーだろ!!」
コレですコレ!ドラマにありがちなシチュエーションだけどさ。妻は " 私 > 仕事 " っていう式を夫に評価させようとするわけだけど、夫はそんな式は評価できねぇよ!って評価できません値を返すよね? あと、水割りならぬトリカブト割り “1/0” なんてのを飲ませると 死んじゃったり。これも無限大の値を返せないんで _|_ になる。いや、無限大を意味するものを返してもいいけどさ。話が続かないから返せないことにしてよ。
上司:「おい、田中君いつもの仕事、あれやっといてよ。 必要な資料はコレとコイツでいいんだよな。」 田中:「はい、分かりました」
ってな会話の後、田中君はいつもの仕事って関数に渡されたデータを適用する んですね。
itumono_sigoto kore koitu => kekka
翌日も同じ様なやり取りが繰り返されます。
上司:「おい、田中君いつもの仕事、あれやっといてよ。 必要な書類はコレとコイツでいいんだよな。 あっ、あとん〜とコレコレ! 必要になるかどうかわかんねぇけど一応渡しとくわ、 あとよろしく。」 田中:「はい、分かりました(おいおい必要かどうかわかんねぇものってなんだよ)」
そう、実は今田中君が渡されたコレコレってのは「会社と家庭どっち取るんだ よ!?」とか、“トリカブト割り”みたいに田中君がもし扱ってしまったら大変 なことになっちゃうアレです。
まさに
itumono_sigoto kore koitu korekore
ってのは
itumono_sigoto kore koitu _|_
な訳ですね。
これに対して正格な田中くんは仕事するまえに、渡されたデータを必要かどう か判断するべく最初に評価するんだけど、コレコレを見た瞬間に「そんなん決 められっかよ!」とか死んじゃったりとかしちゃいます。
itumono_sigoto kore koitu _|_ => _|_ (これがそういう状態なわけです)
それに対して非正格な田中君であれば、必ずしもそうなるとは限りません。も ちろん「必要になるかどうか分からないけど」って渡されたコレコレってやつ が本当に必要になってしまって、田中君が見てしまうと結果は同じなんだけど、 もし結果的に必要なければ、田中君はちゃんと_|_以外の値を返せるんですよ ね。
それがつまり、
正格関数ってのは f _|_ = _|_ ってなる様なfって関数で、そうじゃないものを非正格関数という。
これなんですね。ちなみに正格ってのは strict つまり厳格なとかって意味だ けど、まぁ真面目なやつが変に不必要な情報に頭悩ませてしまって、それが元 で仕事を放り出す結果になってしまい、そうじゃないnon-strict(非正格)な やつはなぜか問題なく仕事をしちゃうことがままあるのは因果なもんですね。
さらには文脈によっては“非正格な”評価ってのを “怠惰な”評価とかって言わ れることがあるんですが、怠惰なヤツの方が仕事をしちゃう可能性があるって のも・・・。(冗談ですってば)
はいはい
Hello, World! プログラム
さぁ寝返りもうてるようになった(どういう基準だよ?)ところで、地べたは いずっちゃいましょう! やっぱりプログラミングを始める場合には、どんな言語でも Hello, World で しょう。 LL Weekend 2004 ではLL侍にブラックボックスと称された Haskell の Hello, World です。ちょっと緊張しますね。あんな人達の集まる場でブラッ クボックスって言われる様なシロモノを私なんかが書けるわけねーんじゃねー かって?!でも今の世の中便利なもので、google で検索するとありました。 このサイト 面白そうなんで、しばらくは参照させてもらってお勉強を進めましょう。
> main = putStrLn "Hello, World!"
これ結構簡単じゃねぇすか?これを emacs 上で評価してやると、
Main> main
Hello, World!
こんな感じで挨拶してくれましたよ。どもこんちわ。m(__)m
> main = putStrLn "Hello, ほげさん!"
と書き換えて評価してやると、日本語も大丈夫! すごいすごい。
ふと、これに型を記述しておいてやろうと思ったんだが、よう分からん。こう いう時はわざとエラーを発生させてやるのが正しいやり方です。嘘ですよ、も ちろん。(^^;
Main> main 123
ERROR - Type error in application
*** Expression : main 123
*** Term : main
*** Type : IO ()
*** Does not match : a -> b
あえて不必要な引数 123 を渡してやると Type error ってエラーが出ました。 その下に Type ってとこに IO () って出てますね。 フツーは逆で型の指定っ てチェックのためにするんだろうけど、初心者の内はこういうナサケナイ使い 方もアリでしょ。じゃぁ Haskell の教えてくれた型を書いてみましょう。
> main :: IO ()
> main = putStrLn "Hello, ほげさん!"
んで、C-c C-l してから評価してやると、
Main> main
Hello, ほげさん!
わーい、ぱちぱちぱち!
実行ファイルにしてみよう
・・・で?(^^;; ここでさぁ、やっぱり emacs 上だけじゃなくて実行ファイルにしたいじゃん? やってみましょう。・・・さて、どうやるんだろ?
#! /usr/pkg/bin/hugs
> main :: IO ()
> main = putStrLn "Hello, ほげさん!"
こんな感じに hello.lhs で保存してみて、パーミッションを実行可能にしてみた。
[email protected]> ./hello.lhs
__ __ __ __ ____ ___ _________________________________________
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2001
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Report bugs to: [email protected]
|| || Version: December 2001 _________________________________________
Haskell 98 mode: Restart with command line option -98 to enable extensions
Reading file "/usr/pkg/share/hugs/lib/Prelude.hs":
Reading file "./hello.lhs":
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
./hello.lhs
Type :? for help
Main>
あらら・・・。一応 Main> にプロンプトが変わってはいるんだけど、だめか。っつーか、hugs ん中に入っちゃってるじゃん。(T^T) もしかしたら hello.hs ファイルにせにゃならんかな?
#! /usr/pkg/bin/hugs
main :: IO ()
main = putStrLn "Hello, ほげさん!"
今度は lhs じゃないので、各行頭の > を外してます。・・・が、無駄な努力 でした。結果は同じ。
すごすごと man hugs ってしてみたけど、何かそれっぽ いのもないぞ。困ったな。とりあえず、先頭の #! 行を取り除いて ghc でコ ンパイルしてみることにしようか。
[email protected]> ghc hello.hs
[email protected]> ll
total 370
drwxr-xr-x 2 cut-sea users 512 Sep 18 22:36 .
drwxr-xr-x 4 cut-sea users 512 Aug 5 21:05 ..
-rw-r--r-- 1 cut-sea users 287 Sep 18 22:36 Main.hi <= コレ
-rwxr-xr-x 1 cut-sea users 353908 Sep 18 22:36 a.out <= コレ
-rwxr-xr-x 1 cut-sea users 50 Sep 18 22:36 hello.hs
-rwxr-xr-x 1 cut-sea users 79 Sep 18 22:16 hello.lhs
-rw-r--r-- 1 cut-sea users 31 Sep 18 21:43 hello.lhs~
-rw-r--r-- 1 cut-sea users 1956 Sep 18 22:36 hello.o <= コレ
-rw-r--r-- 1 cut-sea users 364 Aug 31 21:42 test.lhs
-rw-r--r-- 1 cut-sea users 203 Aug 31 21:08 test.lhs~
[email protected]> file *
Main.hi: raw G3 data, byte-padded
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for NetBSD, dynamically linked (uses shared libs), not stripped
hello.hs: ISO-8859 text
hello.lhs: a /usr/pkg/bin/hugs script text executable
hello.lhs~: ASCII text, with no line terminators
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
test.lhs: ASCII text
test.lhs~: ASCII text
おおっ!出来てる出来てる。 file でチェックすると executable な ELF バイナリが!
[email protected]> ./a.out
Hello, ほげさん!
わーい!ちゃんと走りましたよ〜(T^T)感無量。
でもさぁ、やっぱりスクリプトで動かしてみたくないっすか?あと、Main.hi ってのもよー分からんファイルだし。なんだこりゃ?謎がいっぱいあるけど、 あんまり細かいこたぁ気にしなぁい気にしなぁい。
しばらくコンパイルするんじゃなくて、ソースのまんま実行する方法ないか探 してみましょう。 google よ!教えておくれっ!!
runhugs
さんざん色々試して探しまくった挙げ句にようやく出会ったのが、これでした。 この中にちゃんと書いてありまんがな。もう感涙!何十年も会ってなかった肉 親に会ったかのような感動モノの出会いです。
説明するよかコードを書く方がめんどくさくないので書いちゃいます。
[email protected]> cat hello.lhs
#! /usr/pkg/bin/runhugs
> main :: IO ()
> main = putStrLn "Hello, ほげさん!"
literate 形式のプログラムにしておいて、hugs じゃなくて runhugsを呼び出 してやるんだってさ。っつーわけで、えいやっ!
[email protected]> ./hello.lhs
Hello, ほげさん!
動いてくれましたよ〜。あぁむせび泣き。
やはりブラックボックスなのか?
ではじっくり hello.lhs を読んでみましょうか?たった2行で何がじっくり じゃぁっっ!!ってツッコまれそうですけど、いいんです。何事も最初が肝心 です。でもヤバそうになったら無理せずに裸足で逃げ出します。コレ肝心!挫 折するより、経験値積み上げてから出直しってやつです。(伏線だったり)
#! /usr/pkg/bin/runhugs
> main :: IO ()
> main = putStrLn "Hello, ほげさん!"
lhs 形式になっていることで、#!行がHaskellには無視されるわけですね。 hs 形式じゃないのは、まさにこのためですね。
- さぁshellから実行されたぞ〜
- 最初に#!があるじゃん!
- 何々?/usr/pkg/bin/runhugsを呼べってか?
- /usr/pkg/bin/runhugs召喚!
- へいへ〜い!呼ばれて飛び出てじゃじゃじゃじゃーん!(by runhugs)
- 何々?コメントコメント・・・(最初の2行を読み飛ばし)
- おっと、“>”があった!!この行は本体だねぇ
- んじゃまぁ、評価すっか
とりあえず、こんな具合でしょうか。でもさ、これって不思議じゃないですか? Schemeで言えば(define main …)があるだけですよ? (main)とかって関数呼 び出ししないで走りはったがな、このシト(^^;ナシテ?
(注)Schemeでもmain関数を定義してあるだけで、最初にmainがcallされる仕 組みがある。でもこれってやっぱ特例だよね?
nobsun(2004/09/22 10:38:27 JST): う〜ん。そんなことないと思うけど。コ ンパイラを持つ言語なら普通、エントリポイントになる関数の名前は仕様で決 まっているよね。main とは限らないかもしれないけど。
Haskell では、プログラム(算譜)は、定義が書いてあるだけだよね。で、 Haskell のプログラムの実行は、式を評価することだったよね。インタープリ タ内部に居るときには、評価する式を指定することが普通だけど、インタープ リタ外部に居るときには、評価する式を決めておいた方が便利でしょ。それが main というわけ。
実は上のコード書いてたときにも
#! /usr/pkg/bin/runhugs
> main :: IO ()
> main = putStrLn "Hello, ほげさん!"
> main <= コレは間違いっす
なんて書いてて、きっちりエラーを喰らってたんで、気づいちゃいたんだけど 知らんプリして、ここまで延ばしてました。 LL Weekend でも Haskeller な 方が「Haskellの場合はmainから始まります」って何度かコード解説してた様 に思います。しかも先程神の声が main がエントリポイントになるんだよ っ て囁いてたんで間違いありません。
じゃぁ次にHaskellに教えてもらって追加した行です。
> main :: IO ()
・・・すんません。早速わかりません。降参デス。なんで main 関数のデータ 型が IO なの?しかも IO のバックに変なシトが取り憑いてます。ナニこれ? nil(?.?)
IO () ってどんなデータ型?
nobsun(2004/09/22 10:24:21 JST): ものすごく、はしょって言うと、main は プログラム外部へ出力するアクションだからです。外部へ出力するアクション は IO () 型です。
お?神の声が聞こえてきました。そういうことらしいです。 この部分はなんとなくボスキャラが潜んでそうなので、そういうものって事で 素通りすることにして、次いきましょう次。
> main = putStrLn "Hello, ほげさん!"
これはなんとなくわかる気がする。 put string line の略でしょうね。この 部分は調べてる最中に他で出会ったパターンでは
> main = putStr "Hello, ほげさん!\n"
こんなんもアリらしいです。 put string の略と思われます。これも分かりま すよね。動作的には明示的に改行を入れるかどうかの違いでしょうか。最初の 印象としては関数名としては PutStrLn とか PutStr の方が良くないか?と思っ たりしますが、 やさしいHaskell入門 とかを見ると以下の様な文面に出会います。
[読者のみなさんは気づいたでしょうか、特定の型を表わす識別子は大文字で はじまっています。たとえば、Integer や Char です。また、 inc のような 値を表わす識別子は小文字ではじまっています。これは単なる習慣ではありま せん。Haskell の構文規則でそうすることになっているのです。また、先頭の 文字だけではなく、そのほかの文字も、大文字か小文字かということは重要で、 foo と fOo と fOO はすべて違う識別子として区別されます。]
だそうです。まぁ関数もファーストクラスデータオブジェクトなわけで、それ を束縛しただけの識別子である putStrLnやputStrも小文字スタートって訳な んでしょうね。
では、ほんのちょびっとだけ遊んでみましょう。
> main = putStr ("Hello," ++ " ほげさん!\n")
結果としてはまるで変わりませんけどね。 ++ ってのはとりあえずは文字列を 連結してくれる中置演算子と思っておきます。はい。そんなことより重要なの はカッコです。これが無いと型が違うって叱られます。
nobsun(2004/09/23 00:54:22 JST): 括弧なしで、
> main = putStr “Hello,” ++ " ほげさん!\n"
と書くと何故、叱られるかというと。関数適用の結合力は、二項演算子 ++ の 結合力より強いので、上は
> main = (putStr “Hello,”) ++ " ほげさん!\n"
と解釈されてしまいます。(putStr “Hello,”) の型は IO () で、" ほげさん! \n" の型は String です。これは、++ の型 [a] -> [a] -> [a] というのに矛 盾します。で、エラー。
Haskellの型推論のシステムが、上のように型を推論してくれて、プログラマ の意図と実際に書かれたプログラムとの齟齬を教えてくれます。こんなのが実 行時エラーだったらやだよねぇ。型推論マンセー!
WiLiKi:Shiro (2004/09/23 11:24:53 JST): でもでも、最初の関数の型が [Char] -> [Char] だったら、エラーは出ないよね。プログラマの意図を完全 に表現しきるのは無理なんだから、結局程度問題ってことにならない? (いや、 型推論があるのはうらやましいんだけどさ。) 議論になりそうなら別ページに 行きましょう。
- 続きは Type にてやりましょう (2004/09/24 15:53:18 JST)
> main = putStr ((("Hello," ++ " ほげさん!\n")))
無いとまずいんだけど、余分にある分には一向に構わないみたいで、こんな風 にカッコ連発しても大丈夫です。なんかCの式みたい。
ちょっと興味が出てきたので ++ に寄り道してみましょう。 ++ は中置演算子 なんだけど、敢えて前置演算ぽく使うことも出来ちゃいます。
> main = putStr ((++) "Hello," " ほげさん!")
こんな風に(++)ってカッコで囲ってやるだけですが、やっぱりputStrに対して は引数全体をカッコでくるんでやる必要がある。カッコを外す方法は無いんか いなと思うのだが、こんな風にすると出来るらしい。
> main = putStr $ (++) "Hello," " ほげさん!"
この $ってやつを使うとカッコを省略できるみたいだ。正直に白状すると、コ レも LL Weekend 行った時に仕入れたネタだったりする。いや、セッションじゃ ないんだけどね。(^^)v さらに調子に乗ってこうやってみる。
> main = putStr $ (++) "ども〜, " $ (++) "どもども," " ほげさん!"
これは
> main = putStr ((++) "ども〜, " ((++) "どもども," " ほげさん!"))
これと同じことのようだ。
nobsun(2004/09/23 01:09:27 JST): $ は ++ より結合力が弱いから
> main = putStr $ "ども〜, " ++ "どもども," ++ " ほげさん!"
でも、おっけぇよん。
これ見てようやくGauche の部分適用の関数名の末尾に $ がついている意味が 分かったりとかね。
ソウダッタンダァー
さらに echo.lhs とかね
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
> module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
続いて echo.lhs も読んでみましょう。これも このサイト から拝借しちゃい ます。
まず実行してみましょーかね。レッツエバル! emacs 上で走らせると、いや、 走ってないんだけど・・・
Main> main
Main> main 1 2 3
ERROR - Type error in application
*** Expression : main 1 2 3
*** Term : main
*** Type : IO ()
*** Does not match : a -> b -> c -> d
Main> main "1 2 3"
ERROR - Type error in application
*** Expression : main "1 2 3"
*** Term : main
*** Type : IO ()
*** Does not match : a -> b
Main>
あでででで。ダメですね。最初なんも引数与えてないのですが、そうするとな んも言わないんですよね。 引数を 1 2 3 って与えてやると、エラーが出ましたね。 Does not match : a -> b -> c -> d ってあるのは引数 1 2 3 って適用する形で呼び出したから型 がちゃうよって叱ってるんだよね。 これは 1適用 -> 2適用 -> 3適用 -> mainの返値である何か てな間違った呼 び出しになってるよ〜って主張されておられる。ふむ。
で、浅知恵なんだけど、 main “1 2 3” って引数を一個のものにしてみたんだ けど、 Does not match : a -> b ってやっぱり間違ってるよって叱られます ね。多分やっぱり IO () ってのが分からないとダメなような気がします。い ずれにせよ、mainの引数は無いような気がします。だって -> が型に無いもん ねぇ。
考えててもしょうがないからコマンドラインから実行してみましょう。
[email protected]> ./echo.lhs
[email protected]> ./echo.lhs 1 2 3
1
2
3
[email protected]> ./echo.lhs はろ〜 もーにんぐ 娘。
はろ〜
もーにんぐ
娘。
[email protected]>
こんな風に引数で与えたモノ(文字列臭い気がするけど、まだ不明)が一行ずつ 印字されますね。ふーん。じゃあ、これをふまえて!コードの方を読んでみま しょうか。
まず最初の “>” で始まる行の内先頭三行はコメントだそうです。 Haskellの コメントは – で始まる所から行末尾までがコメントになります。あと、ブロッ ク的にコメントを入れる手段もあるそうです。今まであんまり言語仕様書って 見たくなかったんだけど、この辺りから参照させてもらった方がよさげかな〜。 と。いや、Lisp?を初めて勉強する時に、いきなりCLtL2ってマクラみたいな本 を手に取ったことがあって心的外傷にね、ちょっとね。うん。 この辺を参照するとネストしたコメントは {- から -} までよんって書いて ます。「おいおい、literate 形式だったら “>” で始まってない行はどうせ コメント扱いだろうが〜」ってツッコミありそうですが、丁重に無視。
> module Main where
あ、なんかモジュールだ。・・・ぢぐぢょうぅぅ!!(T^T) またこれ かよ。しかも案の定理解できねぇ。なんとなく名前空間を分ける、正確には制 御するか?そういうモンらしいのは分かったけどさ。とりあえず、Gaucheに もあるモジュールと同じようなもんと思ってみるか。・・・ってオレGauche のモジュールシステム使いこなしてねーじゃんっ!切腹!!
しかもこの辺で道草食ってたら
(^^) :: (Fractional a, Integral b) => a -> b -> a negative exponent allowed
なんてのを見つけて「やるじゃんHaskell(なにがやねん)」とかボソッとつ ぶやいてみたり。こんなんが、 $ や ++ や !! なんかに混じってコードに入っ てたら、なぜスマイルマーク??って幻惑されることうけあい!なかなかイカ ス(死語)じゃん。
おっと、いかんいかん。モジュールだった。こういう時は必殺技
コードにエラー仕込んで処理系を怒らせてヒントをもらおう作戦です。
先の一行を削ってHaskellの怒りっぷリを静かに拝見させていただきましょう。
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
コメントアウトして(うわ、簡単だわこりゃ)、んで、まずはロード!!
Main> :load /home/cut-sea/script/haskell/echo.lhs
Reading file "/home/cut-sea/script/haskell/echo.lhs":
Parsing.................
ERROR "/home/cut-sea/script/haskell/echo.lhs":7 - Program line next to comment
Prelude>
ありゃ?なんで? Program line next to comment ってどういう意味だ?コメントの次にプログラム行が来てる?確かにそうなってますよ。 あれ、もしかして7行目ってコメントアウトした行自身か。ってことはもしかして “>” 行って途切れたらダメなの?
じゃあ今度はこうしてみよう。
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
・・・うーん。予想が外れました。ちゃんとロードできるねぇ。なんでだろ。 で、色々試したんだけどようやく分かりました!はい。さっきの誤訳してま した。すまねぇだ。 Program line next to comment はコメントの隣、つま り隣接する行にプログラム行があるって仰っているんですね。 module Main where の前後に空行を入れたり入れなかったりしてようやく判明しました。
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
これでおっけーです!ドン!
[email protected]> ./echo.lhs 1 2 3
1
2
3
わーい!!ぱちぱちぱち・・・・って違うっ。そうじゃなくて、 module Main where が何してるか調べてたんじゃん。無くても良いって事だよ? いや、実は予想してたんだけどさ、マジ要らないみたいね。多分単に Main モ ジュール内で定義するよって言っているだけなんでしょう。無きゃないでい いんでしょう。したらば!
モジュールヘッダ部(moduleで始まる行)が無い場合、`module Main(main) > where’があるものとして処理されます (ここ参照)
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
> module Foo where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
って Main を Foo モジュールにしてみよう。
Foo> main
Foo>
ちゃんと emacs 上ではロードできるし何か走りそう・・・って思ったら。
[email protected]> ./echo.lhs 1 2 3
runhugs: compileExpr: invalid module
おーいっっ。無効なモジュールだって叱られちゃいますね。 main ってのがエ ントリポイントとして働くのは Main モジュールだけらしい。ちなみに Main を Prelude にしてやると、
[email protected]> ./echo.lhs 1 2 3
runhugs: Error occurred
Reading file "./echo.lhs":
Parsing
ERROR "./echo.lhs" - Module "Prelude" already loaded
すでに Prelude モジュールはロードされとるって叱られます。これはなんと なく分かる気がしたんだけどさ〜。さらに Main を List にしても
[email protected]> ./echo.lhs 1 2 3
runhugs: Error occurred
Reading file "./echo.lhs":
Parsing
ERROR "./echo.lhs" - Module "List" already loaded
ナゼ??とりあえず Main じゃなきゃマトモに動作してくれないのは分かった けどエラーメッセージに謎が残っちゃいましたね。ボーゼン。
えっと、Main じゃなきゃダメだってことだけ押えといて、次の行にいきましょうか。
> import System
ハイ!これは System モジュールをインポートしてるんだね。多分以降のコー ドで System モジュールで定義された関数を使ってるんでしょう。おおよその アタリをつけて :find で見てみましょう。えーっとー、 :find getArgs か な?・・・ビンゴ!
先生!すんません!割り込みますけど気になったんでちょっと試させてくださ い。 :find getArgs ってしたら getProgName ってのが見えるんですよね。こ れ試させてください。
#! /usr/pkg/bin/runhugs
> module Main where
> import System
> main = do args <- getProgName <= ここを変えた
> mapM_ (putStrLn) args
でロードしてみると・・・
Dependency analysis............................................................
Type checking
ERROR "/home/cut-sea/script/haskell/echo.lhs":9 - Type error in application
*** Expression : mapM_ putStrLn args
*** Term : putStrLn
*** Type : String -> IO ()
*** Does not match : Char -> IO ()
System>
んーと、何か型が違うって叱られるな。再度 :find getProgName すると
getArgs :: IO [String]
getArgs = do argc <- primArgc
mapM primArgv [1..argc-1]
getProgName :: IO String
getProgName = primArgv 0
仲良く並んでこうなってます。なんとなくだけど getArgs は引数を複数取れ るんで IO [String] になってるんだろう。一方 getProgName は get Program Name の略だろうからプログラム名は一個しかない。だから IO String になっ てます。ってところじゃないかしらん。
類推すると、 IO () は IO を通して何の受渡しもしないってことじゃなかろーか?
とりあえず、 型が違うって言われてるんでさらにイジイジしてみます。
#! /usr/pkg/bin/runhugs
> module Main where
> import System
> main = do args <- getProgName
> putStrLn args <= さらにここも変えた。
Main> main
Hugs
よし!さらにコマンドラインから実行!
[email protected]> ./prog.lhs
./prog.lhs
あ!・・・一瞬びっくりしたけど、そっか。そうだね。でも結構嬉しいぞ。初 めて自分で書いた気がするし。何の役にも立たんけどね。(^^;
じゃあちょっとした満足感が得られたところで元の echo.lhs のコード読み込 みを続行しましょう。
do しましょ
> main = do args <- getArgs
> mapM_ (putStrLn) args
はい、この部分ですね。実は emacs でコードをいじっていると気付くんだけ ど、どのシンボルでもって訳ではないんだけど、word の上にカーソルを持っ ていくとミニバッファのところに型を表示してくれます。実に親切ですね。す ばらしい。
じゃあ色々カーソルを移動させて調べてみましょう。すると getArgs が IO [String] であることや putStrLn が String->IO () であることが判ります。 じゃあ mapM_ にカーソルを持っていくと・・・
Monad m => (a -> m b) -> [a] -> m ()
って感じにボスキャラが姿を見せますのでこっちは無視しましょう。多分マッ プ関数の一種だと思うんだよね。動作からして。あとは、args が何の型も持っ てない(?)のが気になりますね。なんとなく getArgs の返値と同じ型になる んでねーか?と思うんですけど。さらに難解なのが do だったりします。
do { stmts [;] } stmts -> exp [; stmts] | pat <- exp ; stmts | let decllist ; stmts
って書いてますね。どうもこのあたりを先に見ておいた方がよさそうな感触 です。あくまでさらっとね、さらっと。あまり真剣になると挫折しちゃいそう なんで。
で、一行だと分かりにくいので結局助けを求めて、ここを見ると
exp -> do { stmts } (do expression)
stmts -> stmt1 ... stmtn exp [;] (n>=0)
stmt -> exp ;
| pat <- exp ;
| let decls ;
| ; (empty statement)
BNFとかとは違うけど、知ってると勘が働くな。
この場合には stmt(statement)がつらつら書かれてて、その内の最初の stmt が pat <- exp なんだな。もちろん pat が args になってて、 exp 式が getArgs で。その次の stmt が mapM_ (putStrLn) argsになってると。 pat <- exp は exp を評価した返値を pat に束縛してるみたい。しかも stmts の 定義をみると stmt はいくつ書いたって良いということだ。
#! /usr/pkg/bin/runhugs
> --
> -- simple echo command in Haskell
> --
>
> module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
> mapM_ (putStr) args
じゃあお言葉に甘えて、早速。
[email protected]> ./echo.lhs 1 2 3
1
2
3
[email protected]> ./echo.lhs はろ〜 もーにんぐ 娘。
はろ〜
もーにんぐ
娘。
はろ〜もーにんぐ娘。[email protected]>
やったね。
余談だけど、新たに行を追加する時に、わざわざ > … って書かなくてもい きなり tab キーを押すと勝手に > を追加してインデントしてくれます。 面白いのは、そのインデントです。今回のケースでは3種類のインデントを順 にやってくれます。おそらく レイアウト ってやつに連動しているんじゃねー かなーと思います。
最初のインデントに任せると、
> module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
> mapM_ (putStr) args
こうなってしまい、ロードすると
System> :load /home/cut-sea/script/haskell/echo.lhs
Reading file "/home/cut-sea/script/haskell/echo.lhs":
Parsing........................................................................
Reading file "/usr/pkg/share/hugs/lib/System.hs":
Parsing........................................................................
Dependency analysis............................................................
Type checking..................................................................
Compiling......................................................................
Reading file "/home/cut-sea/script/haskell/echo.lhs":
Parsing..................................................
ERROR "/home/cut-sea/script/haskell/echo.lhs":11 - Syntax error in input (unexpected symbol "mapM_")
System>
コマンドラインから実行すると
[email protected]> ./echo.lhs はろ〜 もーにんぐ 娘。
runhugs: Error occurred
Reading file "./echo.lhs":
Reading file "/usr/pkg/share/hugs/lib/System.hs":
Reading file "./echo.lhs":
Parsing
ERROR "./echo.lhs":11 - Syntax error in input (unexpected symbol "mapM_")
って構文エラー発生しちゃいます。二番目のインデントに任せると
> module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
> mapM_ (putStr) args
こうなって、ロードもコマンドラインからの実行も同じ結果です。最後のイン デントだと、
> module Main where
> import System
> main = do args <- getArgs
> mapM_ (putStrLn) args
> mapM_ (putStr) args
こうなって、ロード時とコマンドラインから実行した場合のエラーはってーと、
ERROR "/home/cut-sea/script/haskell/echo.lhs":13 - Syntax error in declaration (unexpected `}', possibly due to bad layout)
[email protected]> ./echo.lhs はろ〜 もーにんぐ 娘。
runhugs: Error occurred
Reading file "./echo.lhs":
Reading file "/usr/pkg/share/hugs/lib/System.hs":
Reading file "./echo.lhs":
ERROR "./echo.lhs":13 - Syntax error in declaration (unexpected `}', possibly due to bad layout)
こうなります。ってワケで結局自分でインデントしました(-,-#) エラーメッセージをちゃんと読んで、 レイアウトをちょいと眺めれば、そこ はかとなく漂う雰囲気で「はは〜ん」って外人風にうなずいている自分がいる ことでしょう。
じゃあ、ここまで来たら多少工夫した Hello, World! を作ってみましょう。
#! /usr/pkg/bin/runhugs
> module Main where
> import System
> main :: IO ()
> main = do arg <- getArgs
> putStr (head arg)
> putStrLn "さん、こんちわ!!"
一応 main のデータ型もつけました。
[email protected]> ./hello2.lhs ほげ
ほげさん、こんちわ!!
[email protected]> ./hello2.lhs ほげ ふが
ほげさん、こんちわ!!
こんな感じです。ん〜っん、ん、ん(^-^)いいですねぇ。 なんかいっぱしにHaskellでコード書けてる気分になって来ましたぞ。
さらに調子に乗っちゃいましょう。
> module Main where
> import System
> main :: IO ()
> main = do [arg:rest] <- getArgs
> putStr arg
> putStrLn "さん、こんちわ!!"
なんとなく、 pat がパターンになるならこういうのも出来るんじゃねーか? と思ったんです。つまり、 String の配列だから最初の String にマッチすん じゃない?ってゆーわけですね。 getArgs の返値が [“arg1”,“arg2”, …] という風になりますからね。これって結局 [x:xs]ってのでマッチさせたら x に最初の引数が渡されるだろうと。そういうこってす。・・・が、前置きが長 い割に失敗しました、コレ。
Dependency analysis............................................................
Type checking
ERROR "/home/cut-sea/script/haskell/hello2.lhs":7 - Type error in application
*** Expression : putStr arg
*** Term : arg
*** Type : Char
*** Does not match : [Char]
調子に乗りすぎると鼻っパシラをメキっていわされました。はひ。多分なんかを勘違いしてるんでしょう。
[arg:rest] :: [String] == Char? arg:rest :: [Char] arg :: Char, rest :: [Char] になってしまいます。 それを putStr に渡すと…
Haskell とよく似た Clean なんかではリストを [1:2:[]] などと書くようですが。– Y. Hanatani
なるほど、そうか。神のお告げによると、型も単純に置き換えを考えればいい と。ってことは・・・こうか。
#! /usr/pkg/bin/runhugs
> module Main where
> import System
> main :: IO ()
> main = do arg:rest <- getArgs
> putStr arg
> putStrLn "さん、こんちわ!!"
これで思惑通り動作!しかし、このパターンが書けるってのは、強力だのう。
てなわけで do を使ったら、なんかブロック構造みたいのが使えるっちゅうこ とで、ちゃんちゃん。
あっちゃの世界の引数とこっちゃの世界の引数
今まで考えたことなかったんだけど Haskell触っているうちに気になってきた んで、ちょっと実験してみましょう。C言語で少し書いてみます。
#include <stdio.h>
int main ()
{
static cycle = 1;
printf("Hello,world! %d \n", cycle);
cycle += 1;
main();
}
そう、実は main を再帰的に呼び出したこと無かったです。まずは第一段階と してコレが出来るか確認してみます。
[email protected]> cc test.c -o test
[email protected]> ./test
Hello,world! 1
Hello,world! 2
Hello,world! 3
Hello,world! 4
Hello,world! 5
Hello,world! 6
Hello,world! 7
Hello,world! 8
Hello,world! 9
Hello,world! 10
^D
おおっ、行けますな。うん。まあアセンブラレベルで考えりゃmainラベルへジャ ンプするだけだから行けるだろ。じゃー本題だ。
#include <stdio.h>
int main (int argc)
{
printf("Hello,world! %d \n", argc);
main(argc+1);
}
さあ、これはどうだ?なんせ再帰呼び出しがオモロイことになっとるぞ!?
[email protected]> cc test2.c -o test2
[email protected]> ./test2
Hello,world! 1
Hello,world! 2
Hello,world! 3
Hello,world! 4
Hello,world! 5
Hello,world! 6
Hello,world! 7
Hello,world! 8
Hello,world! 9
Hello,world! 10
^D
[email protected]> ./test2 1 2 3 4 5
Hello,world! 6
Hello,world! 7
Hello,world! 8
Hello,world! 9
Hello,world! 10
Hello,world! 11
Hello,world! 12
Hello,world! 13
^D
[email protected]> ./test2 foo bar moo dar goo
Hello,world! 6
Hello,world! 7
Hello,world! 8
Hello,world! 9
Hello,world! 10
Hello,world! 11
Hello,world! 12
Hello,world! 13
^D
まぁ予想した通りに動いてくれたじゃないの。でも、やっぱし奇妙っちゃ奇妙だよ〜。 そのココロは?って聞いたらC言語はなんと答えてくれるじゃろ。
こうなるとCにおけるmainの引数argcやargv,envpといった引数とそれがスター トアップルーチンで渡されるメカニズムってやつとCプログラマが引数を渡す メカニズムってやつが混同してません? そういう気になるってHaskellに洗脳されかかってますか?ワタクシ?
じゃあもう少し愚考してみちゃおうか。
#include <stdio.h>
int main (int argc, char **argv, char **envp, int rest)
{
printf("Hello,world! %d\n", rest);
main(0, NULL, NULL, rest+1);
}
これはどうでしょう?これも自分的には初体験。四つ目の引数を渡しちゃいま した。さすがにこれはコンパイルエラーかなぁと思ったら、
[email protected]> cc test3.c -o test3
[email protected]> ./test3
Hello,world! 7597888
Hello,world! 7597889
Hello,world! 7597890
Hello,world! 7597891
Hello,world! 7597892
Hello,world! 7597893
Hello,world! 7597894
Hello,world! 7597895
Hello,world! 7597896
Hello,world! 7597897
^D
[email protected]> ./test3 1 2 3
Hello,world! 7597888
Hello,world! 7597889
Hello,world! 7597890
Hello,world! 7597891
Hello,world! 7597892
Hello,world! 7597893
Hello,world! 7597894
Hello,world! 7597895
Hello,world! 7597896
Hello,world! 7597897
^D
ほう?これまた奇っ怪なっ。(^^; いやいや機っ械なというべきか。まぁこれが main じゃなくて hoge なんて関 数なら単に初期化してないからとかなんとかって考えて初期化しちゃうんだけ ど、今回は初期化なんかしちゃったら、ある意味ここで気になってる肝心の部 分、つまり外部からの引数の受け渡しと内部での引数の受け渡し云々について 思いをめぐらすことができねぇっす。
じーっと考えるとやっぱりC言語の方が変に思えます。なんとなく気持ち悪さ の根源らしきところと、 Haskellのプログラムロード時とコマンドラインから の実行時の引数の与え方、渡り方ってのにかすった手応えがあるんだけど。
どうやらHaskellはある意味で main を特別扱いしてないんだね。もちろん、 main に与える引数の扱いについてって意味でだけど。
Haskellでは、あの世(外界)との専用チャネルはイタコ(何者よ?)を使う 様にしてて、決して関数呼び出しにおける引数渡しのチャネルは使わない様に してる、ってゆーか、それはもう別口なんだなぁ、きっと。確かにいっしょく たにすると無理が出る気がする。C言語でも何かキテレツな扱いになってたし さ。 でも、そうすっと入出力なんかも同じだな。ファイルディスクリプタっちゅー 専用のチャネルを使ってやり取りするんだよねぇ。 DB アクセスだってそーだ な。ソケットだってそーだ。・・・ってことは単にC言語の(ってかHaskell 以外の言語の)コマンド引数だけが扱いが違ってたの?そ、そーなんか? (@[email protected];)
WiLiKi:Shiro(2004/09/24 18:39:53 JST): C/C++の処理系の場合、_start という隠れたルーチンがあって、それがgetArgs相当の動作を行った上で、「普通の関数」であるmainを呼ぶことになっていますな。(_start は言語規格内には無いですが、その存在は規格でimplyされていると思います。特にC++のstatic変数のconstructor/destructor関連。) この_startが、Haskellではmainになっている、と思えば良いのでは…
こんな感じかしらん。
#!/usr/local/bin/runhugs \begin{code} import System import IO import List main :: IO Int main = _start _start :: IO Int _start = getArgs >>= cmain cmain :: [String] -> IO Int cmain args = mapM_ (putStrLn . greeting) args >> return 0 greeting :: String -> String greeting = concat . flip intersperse ["もうかりまっか、","はん"] \end{code}
でもって、
% ./greeting.lhs カットシ ノブオ もうかりまっか、カットシはん もうかりまっか、ノブオはん
モナドを遠目に見る
神の啓示された高度なコードが(シャレ?)あるので、そっちを見てみることにする。
#!/usr/local/bin/runhugs
\begin{code}
高度なコード
\end{code}
まず、いちいち “>” な行にしなくても \begin{code} から \end{code} で括っ てやると同じことが実現できるってことらしい。
import System
import IO
import List
うーむ、いきなり三つもモジュールをインポートしましたね。 System はさっ きも出て来てたからいいとして、 IO ってやつは手ごわそうだ。 List はリス ト演算だから concat や intersperse をロードしようとしてるんだろう。 :find concat してやると List モジュールのソースが出て来る。そんなかに intersperse もある。
で、IO については分からない。 return かな?と思ったら、:find return し ても Prelude が引っ張られる。うーっむ。getArgs は System だしなぁ。
じゃあ例のナサケナイ作戦で確認してみましょう。
#! /usr/pkg/bin/runhugs
> import System
import IO <= こんな風にはずしてみる
> import List
>
> main :: IO Int
> main = _start
>
> _start :: IO Int
> _start = getArgs >>= cmain
>
> cmain :: [String] -> IO Int
> cmain args = mapM_ (putStrLn . greeting) args >> return 0
>
> greeting :: String -> String
> greeting = concat . flip intersperse ["もうかりまっか","はん"]
>
んで、ロード!コマンドラインから実行!・・・問題なく動作しますな。って ことは import IO はいらなそうね。じゃぁ、まぁ要らないんだろうってこ とでうっちゃっておいて、以降のコードを読んでみましょう。
> main :: IO Int
> main = _start
C言語をエミュレートしてるんでしょう。 main は Int を返す関数にしてるの かな?で、main はってーと _start だよって丸投げしてます。どこぞの国の 建設業界みたいですね。
んじゃ、仕事をまかされた _start は?
> _start :: IO Int
> _start = getArgs >>= cmain
こんなんです。当然 main と同じなんだから型も同じじゃなきゃ矛盾しますな。 IO Int 型って書かれてます。
んで、注目の本体は?
> _start = getArgs >>= cmain
getArgs はさっきと同じですよね。 :find getArgs で復習することにします。 実は重度に忘れっぽいです、ワタシ。はい。
getArgs :: IO [String]
getArgs = do argc <- primArgc
mapM primArgv [1..argc-1]
あ、そうそうこんなんだった。primArgc ってのはその直前に書いてあるんだけど
primitive primArgc :: IO Int
primitive primArgv :: Int -> IO String
こんなんです。行頭の primitive ってのがなんとなく、その名称といい、型 しかなくて実体が無いところといい、 こっから先は関係者以外立ち入り禁止 みたいな香ばしい雰囲気を醸し出してます。根性無しの私はすごすごと引き下 がることにします。えぇ、根性無しですとも。
NOTABENE: primitive とか、primHogeHage は Hugs 特有のものです。 Haskell 98 の仕様にはありません。また、GHCでも見えないはずです。
でも Argc/Argv と言えばなんとなく分かると思います。 primArgc/primArgv の型がなんでこうなるのかは、まるで分かりません。しょーがないです。今の 私の経験値ではスライムとかゴブリンあたりが相手です。体力の限界!千代の 富士っす。
それでもこのコードからすっと、
getArgs :: IO [String]
getArgs = do argc <- primArgc
mapM primArgv [1..argc-1]
- primArgc からの返り値は多分 Int で引数の個数だろ。
- それを argc に束縛してるね。(argc <- primArgc)
- mapM が微妙に mapM_ と違うけど気にしない
- [1..argc-1] は argc が仮に 10 なら [1..9] だから 1 から 9 までのリストになる
- primArgv って関数に引数の個数-1までの数値を与えて評価させてる。
- primArgv は?
- Int -> IO String ってなっとるってことは数値をもらって文字列を返してる。
- その返り値が各引数そのものになってんだね。
- マップ関数だからそれがさらにリストになるんで、文字列リスト[String]だね。
って感じでざっくりと分かる気がします。んじゃ、元のコードに戻りましょう。
> _start = getArgs >>= cmain
これでした。 getArgs は [String] を返すんだけど、その次の >>= ってのが 確かバインドとかって演算だったと思います。まるで分かりません。また降参 です。ただ、周辺のコードとなによりも動作そのものから推測するに getArgs の返した値を cmain に渡しているんじゃないかと思ったりします。
んじゃ、なんで cmain (getArgs)みたいになってねーんだよ?わざわざ >>= なんて記号でごまかすなよ〜っ!って思いませんか? 思いますよね?じゃぁ試してみましょう。
> _start = cmain (getArgs)
んで、ロードしてくれっ!
ERROR "/home/cut-sea/script/haskell/greeting.lhs":13 - Type error in application
*** Expression : cmain getArgs
*** Term : getArgs
*** Type : IO [String]
*** Does not match : [String]
うーむ、世の中辛口です。型が違うって言われます。確かにちゃんと見ると getArgs は 何か -> [String] じゃなくて IO [String] なんでした。 単なる [String] が渡ってるんじゃないんですねぇ、どうやら。しかも返す返 すって言ってたけど、何か -> [String] じゃなくて IO [String] ってことは getArgs が [String] を返すっていう表現は非常に怪しい感触ですねぇ。つま り関数呼び出しの引数や返り値とは別口くさいです。 広い意味で情報の渡し方には色々あり得て、関数の引数を通じた渡し方とか返 り値を使った渡し方ってのは、その内の一種に過ぎないってことでしょうか。
まぁ今後も色々実験することで、そのうち敵の姿が捕らえられる様になるでしょ う。楽観主義の O 型です。 IO 型じゃないです。・・・ハァ。
おっと自分の親父ギャグで自らダメージ受けて気死してる場合じゃないよっ。 まぁ、 >>= ってのは引数のメカニズムで返り値を渡すんじゃなくてなんかイ タコのチャネルを駆使して cmain に渡している様なものと解釈することにし ましょう。その方向の理解で押し切れない時がきたら、その時点で自分の認識 を修正していくと。
んじゃ、cmain はどうなっとんの?
> cmain :: [String] -> IO Int
> cmain args = mapM_ (putStrLn . greeting) args >> return 0
うーん、型は[String]これはコマンドライン引数のリストですねぇ、こいつを 受け取って IO Int を返す、と。なんだか意識が遠のいて来ましたよ。トホホ。
実体だけみると、 args ってのが [String] になるんでしょう。こいつにマッ プ関数を適用してますな。コマンドライン引数の各引数に対して作用させる関 数は (putStrLn . greeting) ってことのようです。
この . ってのが関数合成ってやつでしょう。基本的には
(f . g) arg == f (g arg)
と同じことだと思います。ってことはコマンドライン引数の一つに対して、 greeting を適用してそいつの返す値に対して putStrLn を適用すると。って こたぁ greeting は当然 String を受け取って String を返すんでしょう?
> greeting :: String -> String
> greeting = concat . flip intersperse ["もうかりまっか","はん"]
ビンゴ!正解!!すげぇ、型推論できたよ。んじゃ、もうちょっとだ。
List> concat "はろ" "世界"
ERROR - Type error in application
*** Expression : concat "はろ" "世界"
*** Term : concat
*** Type : [[d]] -> [d]
*** Does not match : a -> b -> c
あ、d? -> [d] か。
List> concat ["はろ","世界"]
"\164\207\164\237\192\164\179\166"
あり?なんで?突如日本語を解さなくなったぞ?!気分悪くしたか?
Main> putStr $ concat ["はろ","世界"]
はろ世界
ふうん。どうやら putStr とかを通さないとダメみたいっすね。
List> concat ["Hello,","World!"]
"Hello,World!"
ま、まぁいいか。こいつで動作確認しとこう。とりあえず、concat ってのは 連結ですね。
flip はってーと、:find flip すると
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
ふむ。型を見るとごちゃごちゃしてるけど、定義を見ると簡単ですね。
List> flip (++) "Hello," "World!"
"World!Hello,"
List> flip (++) "World!" "Hello,"
"Hello,World!"
とまぁこんだけの事の様です。単に関数の引数順序をひっくり返すってのをや るだけです。後で分かるけど、interperse の引数順と関係あるみたいです。
じゃぁラスト intersperse は確か HowTo:Listで見掛けましたね。リストの各 要素間にある要素を差し込んでたと思います。
intersperse :: a -> [a] -> [a]
intersperse sep [] = []
intersperse sep [x] = [x]
intersperse sep (x:xs) = x : sep : intersperse sep xs
そんな難しい定義では無いようです。じゃ、自分で書けって言われたら「無理! ごめんなさい!!」って即答ですけど。
List> intersperse " " ["Hello,","World!"]
["Hello,"," ","World!"]
んじゃ、これを組合せるトコにいきましょうかね。
> greeting = concat . flip intersperse ["もうかりまっか","はん"]
interperse ‘何か’ [“もうかりまっか”,“はん”]ってのが本来の使い方なので、 flip することで interperse [“もうかりまっか”,“はん”] ‘何か’ って形に持 ち込んでいると思われる。つまり
g = flip interperse ["もうかりまっか","はん"]
g arg = flip interperse ["もうかりまっか","はん"] arg
= interperse arg ["もうかりまっか","はん"]
= ["もうかりまっか",arg,"はん"]
って感じですね。まぁこんな風に Curry 化するために flip って関数を使っ てたんですな。そうして出来た関数 g と concat の関数合成なので結局は " もうかりまっか arg はん"っていう String になるわけかぁ。
・・・おっと、忘れてたっっ!!
> cmain args = mapM_ (putStrLn . greeting) args >> return 0
>> return 0 がまだでした。うぅぅ。(–; また出たよ、記号が・・・ >> って >>= とどう違うんだよ〜単に結果を左から右に引き渡さないだけか?
emacs 上で >>= とか >> とかにカーソルをあわせてみる。
>>= :: Monad m => m a -> (a -> m b) -> m b
>> :: Monad m => m a -> m b -> m b
となります。例のボスキャラです。正直分からないので、真正面から対決せず、 遠方からその立ち姿を眺めるだけにします。
> _start = getArgs >>= cmain
ここで getArgs が IO [String] 型で cmain は [String] -> IO Int 型です。 上の >>= の型は Monad m => って見たこと無いのが付いてますけど、:find >>= ってやれば分かるように型そのものを意味するものでは無いようですね。 単に m はモナドってやつを意味してるよってことらしい。
これで >>= の、この局面での型に当てはめてみると、
m は IO
a は [String]
b は Int
getArgs :: IO [String]
cmain :: [String] -> IO Int
>>= :: IO [String] -> ([String] -> IO Int) -> IO Int
↑ ↑
getArgs cmain
ってことで >>= は、やはり中置演算子くさいです。んじゃ、確認しましょう。
> _start = (>>=) getArgs cmain
こんな風に書き換えて、同じ動作をすれば、取り合えずは良いんじゃないでしょうか?
[email protected]> ./greeting.lhs カットシ ノブオ
もうかりまっかカットシはん
もうかりまっかノブオはん
ぱちぱちぱち。予想的中。そんじゃ >> の方も確認して心を落ち着かせてみます。
m は IO
a は String
b は Int
greeting :: String -> String
putStrLn :: String -> IO ()
(putStrLn . greeting) :: Sting -> IO ()
mapM_ :: (String -> IO ()) -> [String] -> IO ()
↑ ↑
(putStrLn . greeting) args
mapM_ (putStrLn . greeting) args :: IO ()
return :: Int -> IO Int <= return の型は a -> m a なんだけど b -> m b です
この辺は周囲の型との関係からね
>> :: IO () -> IO Int -> IO Int
↑ ↑ ↑
mapM_ ... args | |
return 0 _start
とまぁ、多分こんなんでしょうか?んーーーー、だからどーした系の後味。(–; 要は mapM_ … args の部分は IO の絡むアクションをするけど、値は渡さず () っていう(ユニット型っていうらしい)ものを渡して、 >> の右項は IO Int って感じで Int を返す?というか渡す?よと。 どうも左項の mapM_ … args の結果を無視して右項だけでテキトーに結果を 作っちゃってます。全然協調してません。 >> ってのは。えぇ、全然パスが通っ てないです。実際 m a -> m b -> m b って事なんで、 m a と m b の二つの 引数をもらってながら、 m b 型が返値になってるんで、m a が消失してて全 然脈絡がないっす。ひどいヤツです。 >> は。 こういう奴がクラスに一人いると、雨の日に遠足の準備をして学校に来てしま い、一日中授業で肩身の狭い思いをする生徒が出て来てしまうんです。パスを もらえなかった可哀想な return は、自分の判断でオヤツとか「遠足のしおり」 しか持って来てなかったりします。この場合は 0 です。どっから出て来た?っ て数字です。ホントひどい >> ですね。えぇ、私がまさにそうでした。>> な 奴でした。ごめんなさい。m(__)m
え、えっと、一方で >>= はってーと、
>>= :: Monad m => m a -> (a -> m b) -> m b
って事なんで、一応 (a -> m b) ってところで a から b への関係を持ってま す。一応パスが通ってます。(実は細かくツッコまれると自信ないんだけ ど・・・)そういう関数を取っているところが、私と、いや >> と違うところ の様です。
まだまだ、ナットクいかねぇってところも有りますが、無理は禁物。肩の力を 抜いて、いい加減なノリで勉強を進めましょう。ユースケサンタマリアに成り 切ると丁度いいです。多分。なんせ、敵はLL侍も恐れるモナドです。じっく り行きます。モナドは理解するより、どう使うのかを考えた方か幸せになれる と、 どっかに書いてありましたしね。
あ、最後に一応
> cmain args = (>>) (mapM_ (putStrLn . greeting) args) (return 0)
を確認しておきましょう。
[email protected]> ./greeting.lhs カットシ ノブオ
もうかりまっかカットシはん
もうかりまっかノブオはん
はい、ぱちぱちぱち〜!
cat.lhs とかもみとく
#! /usr/pkg/bin/runhugs
> --
> -- cat.hs
> --
> -- A simple cat command in Haskell.
> --
>
>module Main where
>
>import System
>import IO
>
>main :: IO ()
>main = do args <- getArgs
> case args of
> [] -> catFile stdin
> _ -> mapM_ (\a -> do f <- openFile a ReadMode
> catFile f
> hClose f) args
>
>catFile :: Handle -> IO ()
>catFile f = do eof <- hIsEOF f
> if eof then return () else
> do c <- hGetChar f
> putChar c
> catFile f
これです。ほとんどの部分はこれまでの読み込みで理解できます。新たに出て 来たのが、case ってやつと (\a -> do …) の部分の \ です。また記号。あ と、 catFile の型に現れる Handle や hIsEOF とか hGetChar hPutChar って やつ。名前からおおよそ予想はつきますけど、一応見て行きます。cat は基本 中の基本ですし。
case については emacs 上でカーソルをあわせると、
case exp of { alts [;] }
ってなってます。おそらく args の値に応じて場合分けしてるんでしょ。
[] だったら catFile って関数に stdin を引数に与えて呼び出します。要は コマンドライン引数が無かった場合は標準入力から読み込むんですね。一方 _ ってのは、なんでしょーか。訳分かんないのがぞくぞく出て来る。でも、どっ かで見たこと有ります。いろいろやって思い出しました。 :find head してみ ると、
head :: [a] -> a
head (x:_) = x
last :: [a] -> a
last [x] = x
last (_:xs) = last xs
tail :: [a] -> [a]
tail (_:xs) = xs
(snip)
null :: [a] -> Bool
null [] = True
null (_:_) = False
こんな感じです。どうやら任意の値にマッチするようなもののようです。特殊 なシンボルってより、慣用的に用いられるシンボルでしょうか。試してみましょ う。
> x:xs -> mapM_ (\a -> do f <- openFile a ReadMode
としたり、
> _:_ -> mapM_ (\a -> do f <- openFile a ReadMode
としてみたり、いずれも問題なく動作します。どうやら -> の右側で参照され ることのない任意のシンボルを _ で書きゃいい様です。実際後の例で、もし 右側で _ を参照したら _:_ のどっちなのか分かんないしね。ちなみに ワイルドカードって言うらしいです、コレ。
(\a -> do f <- openFile a ReadMode
catFile f
hClose f)
お次はこれですね。たびたび出て来る、この手の式はλ式らしいです。 \ a -> … ってのは引数が一個 a ってのを取る匿名の関数を作ってるみたい。
(lambda (a) (let1 f (openFile a ReadMode)
(catFile f)
(hClose f)))
Scheme風に書くとこんな感じでしょう。もち、単なる擬似コードなんですけど。 これもなんとなく a がコマンドライン引数の一つになるので String だと思 いますが (実はハズレ)、こいつを openFile の第一引数に与えて、第二引数 を ReadMode つまり読み取り専用モードにしている。返って来たファイルディ スクリプタらしいものが、f に束縛されます。 おおっ!!出て来ました。ファイルディスクリプタ!これの型は?これの型! emacs でカーソルをあわせても何にも出ません。しょーがないので :find openFile します。えいや!
primitive stdin :: Handle
primitive stdout :: Handle
primitive stderr :: Handle
primitive openFile :: FilePath -> IOMode -> IO Handle <= コレ
primitive hClose :: Handle -> IO ()
primitive hFileSize :: Handle -> IO Integer
primitive hIsEOF :: Handle -> IO Bool
ん〜、 取り合えず IO Handle ってのがファイルディスクリプタに相当する型 くさい。ってことは、 catFile f や hClose f ってのは IO Handle -> 何か なはず!!
・・・って思ったら、オレってホント学習能力ないです。うーん。そうか、 Handle か。ますます分からなくなった。ガク。
と、とりあえずファイルディスクリプタってのは Handle 型みたいだ。うーん。 そういや、上の方の primitive を見てみても stdin とか stdout なんかが Handle って書いてあるじゃねーの。
まぁいいです。とにかくこのλ式はファイル開いて書き出してクローズしてる と。そんだけです。で、「書き出して」の部分をやってる catFile はってー と、
>catFile :: Handle -> IO ()
>catFile f = do eof <- hIsEOF f
> if eof then return () else
> do c <- hGetChar f
> hPutChar stdout c
> catFile f
これですね。この辺もさして難しくないように思うんだよね。他の言語の知識 があれば、およそは分かった気になれそう。 if then else もあるんだぁって のがささやかなへぇ〜っです。 ただ一点、しつこいようですが、ディスクリプタを返す openFile の返り値が Handle でいーじゃん!単に Handle を返すんじゃダメなの?なんで?ってト コだけナットクできねっす。(–; do ってのはモナド演算の構文糖衣って書いてあるってことは、実質 >> とか >>= なんかで繋げられたブロックだと思うので、型としての辻褄がそうなって いるらしいのはいいんだけど、ここを IO Handle じゃなくて Handle にする と何か問題でも?!全体としてそれだと、どっかで矛盾が発生するのだろう か?ってゆー疑問を抱きつつ、そろそろつかまり立ち(だから、どういう基準 だよっ?)へGO!
こんな問題が・・・と言っても分かりづらいと思うので、簡単な説明を。
例えば、getChar :: IO Char の IO を外した readChar :: Char を定義してみます。
import System.IO.Unsafe readChar :: Char readChar = unsafePerformIO getChar main :: IO () main = putStrLn [readChar, readChar]
このコードを実行すると、最初に入力した文字が2回出力されます。 readChar で一度計算された値がキャッシュされるからです。
Haskell は、参照透明性(ある式の値をいつ評価しても変わらないという性質) を前提として作られています。そのおかげで、評価を必要になるまで遅らせた り、一度評価した結果をキャッシュしておいたりすることが可能になるのです。
しかし、そのせいで、readChar のように、評価するたびに値が異なる式をう まく扱う事ができなくなっているのです。
まぁ一応 cat.lhs も読めました。(^^)v ・・・まだ、自分じゃ書けないけどね。
【ちょっとひとやすみ】ピュアな怠け者
作成中。。。
つかまり立ち
(「つかまり立ち」を編集する)|(Haskellerへの道)
すでに勉強してきたネタも尽きたし、改めて やさしいHaskell入門とかモナドのすべてとか読み込まないといかん。
リストもモナド?
んで早速、ここを読んでたら、 リストもモナドなんだってさ。これって実は 以前も読んでたんだけど、それほど気にならなかった。でも、今の自分にはな ぜか衝撃的! だってさ、そうだとするとオイラはモナドなんぞチンプンカンプン・・・・ ってこたリストを分かってねーじゃんっ!!げげげ。(+_+;
んで、リストの型構築子は [] だっちゅーわけですな。 [a] ってな感じで a を [ と ] ではさんでやれば、 a を要素にもつリストです。
ほほぅ、こんなんは表記の問題だからさ、もしかしたら、実装者の気の向きよ うによってはList a ってな書き方してたとしてもいーわけよ。逆に IO a っ てやつも <<a>> って表記する様になってた可能性だってあるよね?
実際に、
Haskell の構文では[t] を[] t と書くことも許されています。
ってここにも書いとる。これって List ってのが [] になっただけじゃん? じゃーさじゃーさ、
main :: IO a
だっつー感じで書けるんだとすると、
main :: List a
って書けたっていーじゃんね?っちゅーことはさ、
main :: List a
↓
main :: [] a
↓
main :: [a]
なんだよね?・・・・・・・・・・・・・・・えぇぇぇぇええええぇぇっっっ!!? ちょい待て、 [a] ってリストだよん?これってどういう関数だ?
main は関数じゃねーのか?
どういう関数もこういう関数もリストはリストだわなぁ。
こことかには -> ってのも型構築子の一種で a -> b って書くと a を引数に 取って、b を返す関数って型になると書いてあった記憶がある。
同じように -> は型構築子です。与えられた 2 つの型 t と u に対して、 t->u は型 t の要素から型 u の要素への写像をおこなう関数の型です。
あ、コレコレ!この文章!・・・ってことはやっぱり、これ関数じゃないよ? main って名前なもんだから、何の疑いも持たずに関数だと思い込んでたけど、
main :: IO a
って、どこをどうみても関数らしい型宣言になってねーぞ?じゃあ何よ?
#!/usr/pkg/bin/runhugs
> main :: [a]
> main = [1,2,3]
ってしてみて、ロードしてみる。
Type checking
ERROR "/home/cut-sea/script/haskell/test2.lhs":3 - Cannot justify constraints in type annotation
*** Expression : [1,2,3]
*** Type : [b]
*** Given context : ()
*** Constraints : Num b
Prelude>
ありゃ?なんで? Type は [b] ってことは、別に [a] でも正しいような気が するんだけど、とりあえず型を外してみるか?
#!/usr/pkg/bin/runhugs
> main = [1,2,3]
これでどーだ!ロード!!
Main> main
[1,2,3]
Main> main 1 <= 例の常套手段で型を教えてもらってる
ERROR - Type error in application
*** Expression : main 1
*** Term : main
*** Type : [Integer]
*** Does not match : a -> b
あー、[a] じゃダメなんだ。[Integer] なのかぁ。間違っちゃいない気もする んだけどなぁ。 (Type参照:ほう、なるほど) まぁいいや。今はそれどころじゃ ないし。
#!/usr/pkg/bin/runhugs
> main :: [Integer]
> main = [1,2,3]
Main> main
[1,2,3]
おっけー!走るとは思えないけど一応コマンドラインから実行!
[email protected]> ./test.lhs
Program error: fromDyn failed. Expecting <<IO ()>> found <<[()]>>
runhugs: Error occurred
うむ。まぁいい。これは無視! とにかく、これと同じことなんじゃないだろうか?つまり、これまで悶々とし ててイマイチ釈然としなかったんだけど、 IO () とかを関数だと思ってたの が間違いなんじゃね?もしかすっと。
恐れ多くてとても口に出来ないので書いちゃいますけど。もしかするともしか してIO ()型の main は関数じゃなくて値 なんでないすか?
んじゃーどういう値よ?って言われたらアクションっていう値なんじゃないか?
Main> main
[1,2,3]
って感じで、main がたまたまリストの場合は評価された結果、リスト[1,2,3]っ てのがオメミエするわけだけど、 main の値がたまたまアクションの場合は評 価結果としてアクションを取って見せる、と。
全然話が違うけど ASCII コードの 07h ってコードに bell ってのがあるね。 ほとんど、どのコードも印字された時の表示形式があるけど、 bell は我々の 目には見えず、耳に聞こえる形で、その存在をみせてくれる。平たく言えば ビーッって泣きやがる。あ、良く見たら他にもあるわ。BS とか DEL とか。。。 そだ! BS や DEL なんか、まさにそのアクションでもって、その文字コード がそこにあることを教えてくれるよね?
・・・えっと、何言いたかったんだっけ?(^^;; ・・・ど、どーゆー理屈だか自分でも不可解だけど、値ってやつが全て印字さ れたときの表示形式を持ってなくちゃならないって決まってなくて、我々の視 覚以外の感覚器官に訴える形であっても良いわけよ。あるいは何も訴えなくたっ ていいわけで、cp.lhs なんかその最右翼じゃない? (まだ作ってねーけど) アクション型(勝手につけてるし・・・)の場合には、我々の目に見えるとか聞 こえるとかが本質じゃなくて、まさに何らかのアクションをするって形で、そ の存在を示すんじゃないかしらん。たまたま、Hello,World!って印字したりと か、ビーってビープ音を出すかもしれんけど。
数値だって本質は整数の何番目の数とか、そーゆー難しい抽象的な存在なわけ で、それ自体はそもそも目に見えるものじゃないんじゃないかな。多分。その 印字名として 1 とか 2 とかって表示が与えられてるにすぎねーのよ。人間に とって便利だからさ。うん。 逆に数値だって印字表現がなくてカワイイ女の子の声で「いち♪」とか「に♪」 とかって表現されたらどうよ?もしそうだったら数値は目には見えないけど、 やっぱりデータだよね?この論法、かなり無理くさいですか?そうですか・・・
Haskell では印字可能クラスというのあります。Show class です。Integerや Charなどは Prelude で、Show class のインスタンスとして宣言されています ので、評価結果が Integer や Char になるものはインタープリタで評価した ときに評価結果が表示されます。要素 a が Show クラスのインスタンスであ れば、[a] も Show クラスのインスタンスであることが宣言されていますので、 [Integer] なども印字可能です。
しかし、id とか head とか Prelude で Show クラスのインスタンスであるこ とが宣言されていない関数は、評価すると印字用の show 関数(Show クラスの メソッド)がないぞ、というエラーが出るはずです。
IO a の a と [a] の a
でもさ、そうすっと IO a って本来は、そのお姿を見せてくれないんだから、 IO () だろうが、 IO String だろうが、 IO [String] だろうが同じじゃねぇ か?つまり、String とか [String] とか IO にとっては違いは関係ないんじゃ ね?
・・・うーむ、脳味噌が沸騰しはじめた。ε=(+_+;=3
いやいやリストだって同じだ。確かに IO a において a は内部に隠されてて 見えないけど、 [Integer] とか [String] ってリストだって同じなんだよ、 きっと。
実際には List a の a は内部に隠し持ってて、本来リストとして見たときに は Integer とか String とかは見えないんじゃねーかな?
Main> [1,2,3]
[1,2,3]
Main> ["cut-sea","nobsun"]
["cut-sea","nobsun"]
こんな風に確かに 1 とか 2 とか “cut-sea” とか “nobsun” とか見えている けど、そりゃーたまたま(?) Haskell の実装においてリストの表示は、その 内部に隠し持っているメンバであるところの 1 とか 2 とか “cut-sea” とか “nobsun” とかを表示する様になってるだけなんじゃん?
もしかしたら、実装者になんかの気の迷いがあったりして、
Main> [1,2,3]
#<List 0x8121e60> <= こんな印字形式とかさ
Main> ["cut-sea","nobsun"]
#<List 0x8121e64>
みたいな、そういうリストの印字表現を持つような、そんな実装があったとし ても、それはそれでいいんじゃないかなぁ。 リストの先頭っていうかルートへのポインタを指すオブジェクトをこう、さ。うん。
そのポインタから継っている全体がリスト、そういう構造みたいなのがリスト であって、内部に持っている要素にアクセスする手段として car/cdr じゃねー や、 Haskell だと head/tail があって、これを使えば、全要素にアクセスで きるし、それにアクセスした時にリストが [Integer] なら Integer の 1 と か 2 とか、 [String]なら “cut-sea” とか “nobsun” が見えると。これは実 際には [Char] だから、そういう実装だと、 “cut-sea” とは表示されずに、 さらに #<List 0x8121e68>みたいな表示になっちゃうだろうから〜・・・ うぇっ!すんげー不便だ、なんだこりゃ!美的センスゼロじゃん。 ともかく、そういう実装だとしたらリストだって、結局 [a] の a ってのは内 部に 隠し持たれてるものであって、リストの印字表現に含まれなきゃならな いものじゃないんだよ〜、多分。リストの本質は head とか tail とかって演 算によってアクセスされたり、 :って演算によって構成されたりする、そうい う構造そのものが本質だと。
同じ様に IO a でも IO () ってのと IO String ってのと IO [String] って のは、 IO っていう(リストと同様に何らかの構造とか性質を持った)値として 存在してて、そいつが内部に隠し持っているデータとして () とか String と か [String] とかがある?
んじゃ、そういう目でもっかい echo.lhs を見てみるとしようか。
Main> main 1 2 3
ERROR - Type error in application
*** Expression : main 1 2 3
*** Term : main
*** Type : IO ()
*** Does not match : a -> b -> c -> d
Main> main
Main>
うむ。やっぱ関数じゃない。main の値が現れました。 おぉっ、見える!わたしにも値が見えるぞ!ラ○ァ!! ・・・えっと、とり あえず、自分の中ではナットク出来た気がするんだけどなぁ。
見えねぇもんが見えるなんて、なんか瞳孔おっぴろげて変な宗教にハマってる ヒトみたく、危ない方向につっ走ってますか、私?
thunk はどう書くのか?
うぅっ、こんなつもりじゃなかったのに、次々に疑問が湧き出す。 そもそもアクションが値なんて勢いで言ったけど、アクションて結局は関数の 評価プロセスそのものなんじゃないか? カッコがないと、いまいち違いが分かりにくいので、あえてScheme?で書くけど、
gosh> (define f (lambda () body))
ってな定義があったときに、
gosh> f
これを評価した結果得られるのは、関数実体つまり第一級のデータですねぇ。
gosh> (f)
これは関数を評価した結果生まれるアクションですねぇ。 UNIX システムで言 えば、 f の評価の結果として得られるのはプログラムで、 (f) の評価の結果 得られるのはプロセスだな。これ、トーゼン全くの別人さんです。はい。
前者は関数型言語におけるデータと言って差し支えないけど、その評価プロセ ス(?)は違うな。 [] a と IO a とのアナロジーからの話の展開に自信持っ ちゃってましたけど。 あぁぁあ((((( ;゜Д゜))))ガクガク。 要は何が言いたいかっちゅーと、アクションが値だってーのはかなり嘘くさい ぞ?私のゴーストがそうささやいとる。つまり勘です。はい。
そーすっとすぐさま気になるのは! ‘引数を持たない関数 thunk は Haskell ではどう書くのか?’ っちゅーことです。そういや、勉強はじめてからまだ一度も見てない気がする。
純粋関数型言語では同じ式は何度評価しても同じ値を返すっちゅーので、引数 という、関数の動作を変更する源が無い場合には、結局即値と同じモノって気 がしないでもない。
ell にはサンクという概念がありません。だって、Lazy なんだから。値は必要になるまで計算されません。敢て言うなら、全部サンク。
= sum [1..]
いてあっても、hoge の値は必要とされているわけではありませんよね。 、ここでクイズ、hoge の値は、何時、どんな場合に必要とされるでしょう? –nobsun
型は?その本体の定義は?っちゅー訳で探し回ってみる。 ・って、探している内に神の声で thunk って概念はないよ、とのこと。 む、thunk についての理解が足りてないのか??どうも神の問かけに詰まっ まった。
げーおーげー、まぁ落ち着け。ここは一つクールにいこう。・・・まず、 トが含まれている。と思う。
= sum [1..]
は’敢えて言うなら全部サンク’の例でしょう。 も停止しない式として書かれておる。
> hoge 123 <= 例の常套手段 R - Type error in application Expression : hoge 123 Term : hoge Type : Integer Does not match : a -> b
。 Type は Integer か。実は、
:: -> Int <= 苦しまぎれ
ってすると、
ing file “/home/cut-sea/script/haskell/test.lhs”: ing……………………………………………………. R “/home/cut-sea/script/haskell/test.lhs”:16 - Syntax error in type signature (unexpected `->’) ude>
構文エラーが出るし、
:: _ -> Int <= 悪あがき
すると、
ing file “/home/cut-sea/script/haskell/test.lhs”: ing……………………………………………………. R “/home/cut-sea/script/haskell/test.lhs”:16 - Haskell 98 does not support anonymous type variables ude>
叱られる。 Haskell では関数は引数が無きゃならんのかもしれん。と思 めてたとこだったりする。引数の無い関数ってのは、そのままデータだっ うことか。 言えば、あらゆるデータは全て thunk だってことでしょう。
ゃ、 thunk を復習してみるとしよう。
> (define (f x y) (* 2 x)) <= 第二引数は評価の必要無い関数を定義
> (f (+ 2 3) (read)) <= 正格評価を試してみる
<= 第二引数が停止しないので強制終了! 標準入力から何か打ち込みゃ良いけど疑似的にね
ERROR: unhandled signal 2 (SIGINT) k Trace: ___________________________________
(read) At line 9 of "(stdin)"
> (define (h x y) (* 2 (x))) <= thunk 用です。やはり第二引数評価は不要
> (h (lambda () (+ 2 3)) (lambda () (read))) <= 非正格評価
>
ですね。Haskell においてはデータは全てサンクだってことは、 Haskell 全ての値は (lambda () value) って感じにくるまれているのか。でもっ Haskell の式評価は (thunk) って感じで force しているのか。
ゃ、問題の hoge の値は何時、どんな場合に必要とされるか?ですな。
> (define (h x y) (* 2 (x)))
> (define hoge (lambda ()
(let loop ((n 1)) (+ n (loop (+ n 1)))))) <= hoge はこう
> (define five (lambda () 5)) <= Haskell での 5 はこうか?
> (h five hoge) <= Haskell での評価をエミュレート
> (h hoge five) <= hoge が必要な計算の例
<= したがって強制終了
ERROR: unhandled signal 2 (SIGINT) k Trace: ___________________________________
(loop (+ n 1)) At line 7 of "/home/cut-sea/script/scheme/test.scm" (loop (+ n 1)) At line 7 of "/home/cut-sea/script/scheme/test.scm" (loop (+ n 1)) At line 7 of "/home/cut-sea/script/scheme/test.scm"
>
む。・・・ムツカシイ(–;) ゃ、どんな場合に必要とされないか?を考えてみるか。
> (h (lambda () (+ 2 3)) (lambda () (read))) <= 非正格評価
>
場面で (lambda () (read)) は結局必要とはされなかったなぁ。実際私が しなくても計算は勝手に完了した。これはまさに (read) が評価されなかっ 拠だな。うん。
て値は (read) に無関係に決まった値ですねぇ。
り、ある式が結果として、ある値に至る場合を考えると、その値に hoge が影響を与える場合に hoge は必要とされるんですね!
・って分かったような気になったけど、コレってお題を言い換えただけ?(^^; ません。いっぱいいっぱいまで脳圧上げて考えましたが降参です。お許し下せぇ。
例えば、次のような関数を定義してみよう。
isLT7 :: Int -> Bool isLT7 = \ n -> n < 7
これは、与えられた引数が 7 より小さいかを判定する関数です。さて、 Haskell では、hoge も isLT7 も定義しただけでは、計算は何もおこりません。 これがまず、Scheme と違うところです。
では、計算はいつ起るかというと、式を評価しようとした時です。
isLT7 hoge
をインタープリタのプロンプトのところで評価すると何が起るかを考えてみましょう。
注意することは、 lazy evaluation では、必要になるまでは計算されないと いうことです。
つまり、なにを求められているか、それには何が必要か、という順で考えることです。
インタープリタのプロンプトのところに、isLT7 hoge と入力すると、
- インタープリタは、isLT7 hoge の値を印字しようとする
- 印字ためには、isLT7 hoge の値を(False または True に)確定しなければならない
- そこで、isLT7 hoge の関数適用を計算する
- isLT7 を定義にしたがって、置き換えると (\ n -> n < 7) hoge
- λ式の本体の仮引数を、実引数で置き換えると hoge < 7
- これが、確定したBool型を返すには、hoge と 7 の確定値が必要
- hoge の確定値を計算
- 7 の確定値を計算(これは 7 ですね)
- hoge < 7 の確定値計算
- isLT7 hoge の確定値を得たので、これを印字
となる(はず)ですね。つまり、hoge の(確定)値は 3.3. のところで始めて必 要とされるわけです。で、実際には、この値を確定しにいくところで、無限の 計算にはいってしまって、先へは進まなくなるわけです。このような状態の hoge の値は ⊥ です。
正格(strict)とは、x = ⊥ ならば f x = ⊥ となるような関数 f の性質 のことで、非正格(non-strict)とは、x = ⊥ であっても g x ≠ ⊥ となる ような 関数 g の性質のことを言います。
もうひとつ別の例を考えてみましょう。
from :: Int -> [Int] from n = n : from (n + 1) longerThan :: Int -> [a] -> Bool longerThan 0 (_:_) = True longerThan _ [] = False longerThan n (x:xs) = longerThan (n-1) xs hage :: [Int] hage = from 0
こんな定義をしておいて、今度は、
longerThan 3 hage
を評価すると何がおこりますか?
longerThan 3 (repeat (1 `div` 0))
ではどうでしょう?
(2004/09/30 11:48:42 JST): ごめん。(0/0)じゃなくて(1 `div` 0)にしてく ださいな。 (0/0) だと、⊥にならなくて,NaNになっちゃう。以下の (0/0) を 全部、(1 `div` 0) に置き換えちゃいます。
うぅむ。 何を求められているか?ってのが、すんげぇポイントなのかぁ。
これまで未定義値を含む式は誰がなんと言おうと未定義値!ってそういう頑固 オヤジだと思ってましたよ、あたしゃ。(いや、非正格を非正確に認識してま したねぇ。まだまだブルーだわ。) んが、どうも様子が変なんだよなぁ。それを求めて(要求して)いるヤツ(な んちゅーか継続?)ってのがいて、どの辺まで突っ込んだモノを求められて (要求されて)いるか?ってのが評価されるかどうかのミソ?
最後の例で repeat (1 `div` 0) って式がありますなぁ。こいつは (lambda () (1 `div` 0)) って thunk を一要素として無限のリストを構成する。
こいつは直感的には
[(lambda () (1 `div` 0)),(lambda () (1 `div` 0)),(lambda () (1 `div` 0))..]
おぉっ、 Haskell と Scheme? のダブルさんだ。(^^; ま、とりあえず、こんなんだねぇ?あくまで直感的にね。こいつをトップレベ ルが求めているのだとすると (lambda () (1 `div` 0)) の 印字をするところ まで求められる。っちゅーことは、これを印字したい、印字する必要が出てき たところで _|_ か。
ところが、上の例で repeat (1 `div` 0) の値を求めている longerThan って やつは、そこまで突っ込んだものは欲しがってないんだ!
型宣言をみてみよー!
ん、んん?・・・どあぁーーーーーっ!!! なんかめちゃくちゃ当ったり前のことに今ごろ気付いたかもしんない。もう、 まるで巨大恐竜が足を蚊に刺されてから何分も経って、ようやく気付いて「か ゆ〜」って思う様に私の頭脳はめちゃめちゃ遅延評価です。はい。(意味がち が・・・)
longerThan :: Int -> [a] -> Bool
これ!これを見ると [a] ってのがある。これは [Int] だっていいし、 [String] だって許される。もち、 [IO ()] だって、きっと許されるだろう。 多分。単にそういう任意の型を表すという側面でしか見れて無かったです、 えぇ。(^^;
これって別の言いまわしをすると、 longerThan は [a] っていうリスト構造 にのみ関心があり、 リスト構造までを要求しており、リストの内部までは要 求してない っていうことでも有りますよね?そうですよね?!そうだ!そう に決まった!!!
もう、目が血走って、今にも犯罪犯しそうな雰囲気マンマンにコーフンしちゃっ てます。こういう時にイキオイで書いた文章が間違ってると、死ぬほどはずい です。えぇ。でも私は懲りません。全然。漢(オトコ)って感じしませんか? そうですかしませんか。がっくり。
longerThan :: Int -> [a] -> Bool
この型宣言において、
longerThan 3 hage longerThan 3 (repeat (1 `div` 0))
この二つの式は、( hage も repeat も)ともに longerThan からは [a] ま でしか求められておらず、リスト構造さえ作って提供してくれりゃ、中に格納 されているデータが (lambda () (1 `div` 0)) だろうが、気にしな〜い気に しな〜い♪ってことだ。
もしも、もしもですよ?
longerThan :: Int -> [Int] -> Bool ↑ コレね
なんて型宣言になるような関数だったら、そんな longerThan 関数から呼ばれ た repeat (1 `div` 0) は [Int] って形までを求められる。つまり、 (lambda () (1 `div` 0)) の値を評価する必要を迫られる。
[(lambda () (1 `div` 0)),(lambda () (1 `div` 0)),(lambda () (1 `div` 0))..]
こいつを
[1/0の値,1/0の値,1/0の値..]
な形にまで簡約されることを求められる。ゆえにぃ、v(^^)v 1/0 の値を求め る段で多分 _|_ を返すと。
んじゃ、お題に戻ると、こういう回答になるんでしょうか?
hoge :: Int hoge = sum [1..]
と書いてあっても、hoge の値は必要とされているわけではありませんよね。
では、ここでクイズ、hoge の値は、何時、どんな場合に必要とされるでしょう?それは、 hoge を引数に持つ関数が評価される場面において、その関数の型宣 言で hoge が位置する引数の型が Int として型宣言している場合は hoge の 値は必要とされる。
仮に
fuga :: a -> Bool deya :: Int -> Bool
という二種類の関数の型宣言があって、 fuga および deya の値が要求されて いる場面において、 fuga hoge とある場合には hoge はその値までは必要と はしないし、 deya hoge とある場合には hoge はその計算された値を必要と する。と。
もちろん、再帰的に呼び出し元にも同じ事が言えて、 deya を引数に持つもの が、 Bool を必要としてれば、その値を要求されるけど、 deya を呼んでいる のが、 deya の引数のところの型を a として宣言しているなら、deya hoge であっても hoge は値を要求されることはないと。 結局 main などのエントリポイントから a とかって型変数に行き当たったら、 そっから先は値そのものは要求されない。多分 リストだとかほにゃららモナ ドと称される様な構造にしか関心はないんじゃね?一方で、末端構成員まで具 体的な型が宣言され続けている場合には値が必要になる・・・可能性がある? (if とかがあるんでちょっと自信なくした)のかと。
遡って確認すると、 isLT7 はどうなっとる?
isLT7 :: Int -> Bool isLT7 = \ n -> n < 7
これで、
isLT7 hoge
を評価すると、 isLT7 は引数 hoge に対して Int まで突っ込んだ情報を欲し がってる。つまり、hoge は計算された値を必要とされる。
・・・ってことで終了しねぇ計算がぁ〜!
あうあうあう じゃあ、
dorya :: Int -> Int dorya = const 1
と定義しておいて、
dorya (1 `div` 0)
を評価するとどうなる?
がぁん!なにやらイキオイで書いたのが、案の定間違っとるくさいぞ?(^^; やべぇ。ネット中引き回しの上さらし首だ。
const ってのは多分固定値を返すんじゃないかと思うんだけど、まぁここでは あまり本質的な問題じゃないな。いちおー、 const を調べておくか。
const :: a -> b -> a const k _ = k
あら?引数二つ持つのか。でも第二引数はシカトしとる。
とにかく、引数が dorya 本体に無関係だってのがポイントなんだ。にも関わ らず、その引数の型は Int に縛られとるがな。そんな御無体な・・・(–;
・・・ここで dorya ってのは Int -> Int ってあるけど、この型宣言は a -> Int でも良さげに思えますねぇ。っつーか私の理屈だと、それなら通るんだよ なぁ。でも Int -> Int となると、確かにダメだわ、こりゃ。早速、破綻しちゃ いましたねぇ。
デジャブったので、ずーっと上の方を見ると、
> main :: [a] > main = [1,2,3]
ってした時にもエラーを食らってますな〜。この時私は「間違っては無い気が する」って感想を漏らしておる。
ここでも同じく a -> Int で間違っちゃいない気がするのだが、多分 a -> Int ってすると全く同じエラー食らっちゃいそうだな。とにかく試してみるか?
> dorya :: a -> Int > dorya = const 1
こうしておいて、どりゃ?!
Main> dorya (1 `div` 0) 1
あー、そっか。 dorya の型としては a -> Int でいけるんだ、 const が a -> b -> a だから。うん。じゃさ、じゃさー Haskell の型推論システムとし てはどうなんじゃろ。予想は多分同じじゃねーかと思うんだけどね。ってわけ で型宣言を削って例の常套手段でお伺いを立ててみる。
Main> dorya ERROR - Cannot find "show" function for: *** Expression : dorya *** Of type : a -> Integer
・・・ということは、ユーザがコードに書く型宣言て Haskell の型推論と同 じか、もしくは、それより厳しい方向には宣言可能?なんでしょか。ゆるゆる にする方向には叱られたもんねぇ。多分厳しくする分には問題ないけど、ゆる ゆるにすると、本体で足し算してるのに、ゆるゆるの隙を突いて String とか IO () とか、足し算をすることが出来ないシロモノが入って来ちゃうからかな。
いずれにせよ、型宣言を見て値の計算が要求されるかどうかを見分けるのは無 理なんですねぇ。
んじゃ、 Haskell の型推論で見たらどうなんでしょ? Haskell の型推論がコー ド全体を解析した結果、ある関数の型の引数部分に a って型変数が残ったも のは、型を具体的に特定できなかったってことっすよね? ・・・あれ??、なんで特定出来なかったんだろ。(?_?;) とりあえず、その関数の評価においては、その型変数が残った引数は値を求め られず、先送りにされるんでないすかね?
あーでも、違うわ、こりゃ。 const ってのがまさに、それを物語っておる。
const :: a -> b -> a
これは全部型変数だけど、
Main> const (1 `div` 0) 0 Program error: {primQrmInteger 1 0} Main>
こんなんなります。つまり、 (1 `div` 0) の値を求めようとされてる。えぇ。 ガックリ、型変数 a だけどキッチリ値を求められることを要求されとるがな。
って思ったんだけど、いやいやこれってトップレベルか要求してるからじゃね? トップレベルに印字することを要求されるからか。例えば、
Main> length [const (1 `div` 0) "cut-sea"] 1
うむ。ここで (1 `div` 0) の値は最後まで評価されずに遅延されて、評価さ れず仕舞いだよね。結局誰も、(1 `div` 0) の値に関心を持たなかった、それ を要求しなかったからだ。 length の型は
length :: [a] -> Int
だしね。
あー違う!頭ぐるぐるんなる・・・。これって longerThan でやったのと変わっ てねーじゃん。同じトコぐぅるぐぅる回ってますよ〜いつもよりたくさん回っ てます。はい。
何を求められているか、何が必要かがポイントなんだった。落ち着けオレ!返 り値と引数をちゃんと区別せんといかんように思う。
const :: a -> b -> a
ここで、 最初の a と二番目の b は引数だ。この引数の値については const は必要としないのではないかな?
んで、返り値の a については const 式を引数として呼び出した側の関数がど う思っているかだよね?その呼び出し側が値を求めて(要求して)れば計算さ れるし、求めて(要求して)なければ計算されずにさらに先送り。と。
・・・ってことは、トップレベルってどういう関数になってんだろ?これって 絶対に値を要求してるけど、型は不問だよねぇ。 まぁ考えても無駄ってか、ハマルと挫折が待ってそうなんで逃げます。はい。 あっしの脳ミソの限界はるか彼方に越えてます。
ただ、一応メモっておこう。 main から末端構成員に向けて調べるってのは逆 だわ。少なくとも。 末端構成員から、そいつが値を求められる(要求される)のはどこか?ってのを main なんかのエントリポイントに向かって調べることになりそう。最後まで 値の計算を先送りにされた時に行きつくのがそこだしねぇ。
型の計算ってのは実行の前処理として行われるだけなんじゃないでしょうか。 型推論機構と評価機構は分けて考えるべきものなのでは。
型推論より厳しい型宣言の必要性
んで、結論は分かんないけど、疑問だけはどんどんでてくるわけよ。(^^; Int -> Int な dorya において、引数の型チェックを Int とする事に意味が あるような、そんな場面てどんな状況だろう。そもそもあるのだろうか。
Haskell ではそれを許しているってことは必要だからかなぁ、やっぱ。一つ言 えることは、振舞の上で、こんな違いが生まれますなー。
> dorya :: a -> Int > dorya = const 1
でもって、
Main> dorya "cut-sea" 1
いえーす!おっけ。じゃぁこっちね。
> dorya :: Int -> Int > dorya = const 1 Main> dorya "cut-sea" ERROR - Type error in application *** Expression : dorya "cut-sea" *** Term : "cut-sea" *** Type : String *** Does not match : Int
うん。まぁそだね。なんかの入力チェックか?でも弾いても弾かなくても結果 同じ気がする。むむむ(–; そろそろコードを・・・
まぁ、今の私がこーゆーもろもろの疑問を抱えてるって事を未来の自分に託し ておいて、 (問題先送り、これぞ遅延評価?)そろそろコードを書かんといか んすねぇ。はい。 多分 Haskell のコードを書かないと、いつまで立っても二足歩行に行けません。
その前に、上でさらっと流しちゃった部分を確認しときます。 `div` っての は、ここ にあるように関数値を中置演算にするものだそうです。あ、 div が じゃなくて注目はバッククウォートで括ってるってトコです。中置演算を前置 にするのが (++) みたいにするのに対して、その逆ですね。んじゃ、早速ため してみましょう。
Main> 1 `const` 2 1
できました。んじゃ、これはどーなんでしょ?
Main> "abc" `(++)` "xyz" ERROR - Syntax error in expression (unexpected `(') Main> (`const`) 1 2 ERROR - Syntax error in expression (unexpected `)')
あらら、どうも構文エラーになっちゃいますね。まぁ、そういうものみたいね。
あと、も一個確認してみます。なんとなく IO a 型の main において、 a は本体内にどんな影響があんのか(どんな風に現れるのか)が気になってます。
#! /usr/pkg/bin/runhugs > main :: IO () > main = putStrLn "んちゃ!"
これはうまく動作しますね。んじゃ、 main を IO Int にするとどうなるんで しょうか?
> main :: IO Int > main = putStrLn "んちゃ!" ERROR "/home/cut-sea/script/haskell/dummy.lhs":4 - Type error in explicitly typed binding *** Term : main *** Type : IO () *** Does not match : IO Int
型が違うって叱られました。なんとなく、こうかなぁ?
> main :: IO Int > main = putStrLn "んちゃ!" >> return 123
おー、おっけーみたいですね。じゃ、同じようにこんなんも出来そうです。
> main :: IO String > main = putStrLn "んちゃ!" >> return "うりゃ!"
さらに
> main :: IO [String] > main = putStrLn "んちゃ!" >> return (repeat "うりゃ")
おぉ? return は
return :: Monad m => a -> m a
だからいいのかな。repeat “うりゃ” 自体は値を必要としないんだね。なんか 実行前にはプロンプトに戻ってくんのか不安でしたけど。
さらにさらに
> main :: IO (IO Int) > main = putStrLn "んちゃ!" >> return child > > child :: IO Int > child = putStrLn "ぷびー" >> return 0
いや、単に IO (IO Int) を main の型にしてみたかっただけっすけど。 ここまでの所結果は全部
Main> main んちゃ!
って感じです。はい。いやぁ実に変わり映えしないですね。すんません。ん じゃ、これは?
> main :: IO a > main = putStrLn "んちゃ!" >> return child > > child :: IO a > child = putStrLn "ぷびー" >> return main
相互再帰的にしちゃろうと思ったんですけど、どうでしょう?
ERROR "/home/cut-sea/script/haskell/dummy.lhs":7 - Inferred type is not general enough *** Expression : child *** Expected type : IO a *** Inferred type : IO (IO a)
うーん。ダメでした。ちょっと調子に乗りすぎましたね。まぁ、いいです。 ずーっと振り返ってみると何となく分かった気がしないでもないですが、 IO () だけ return が無くてもおっけーなのがちょい気になります。
> main :: IO () > main = putStrLn "んちゃ!" >> return ()
これでイケるかな〜?まさかなぁって思ったら、
Main> main んちゃ!
あらら、大丈夫でした。なんか統一的に扱えるんですね。いちおー。 return ってのが生み出したものは、一体どこへ消えていくのでしょう?(?-?)
Hugsでは、インタープリタのプロンプトに IO a 型が渡されると、対応するア クションが実行され、結果の値は無視されるようです。ただ、‘:set +I’ とす ると、結果の値が表示されるようです。 (ここ参照)
ここまで >> ってやつを使ってましたが、これは例のパスを渡さない奴なんで、 右項側で好き勝手出来るんで、こうやって遊んでみるのには楽チンな感じです。 はい。
とにかく、 main が IO a 型なら、最終的に return a が返ればおっけーそう ですね。理解は出来てないですけど、一応押えておきます。はい。 return は
Monad m => a -> m a
なので、どっから m が IO だと知るのかが、謎ですけどね。もし良きに計らっ てくれるんだとすれば、
IO String ↓ [] String
って置き換えても良さげですね。っちゅーわけでぇ
> main :: [] String > main = return "んちゃ!"
んじゃ、ロード!
Main> main ["\164\243\164\193\164\227!"]
いいですね。
> main :: [String] > main = return "んちゃ!"
上のはこれと同じですんで、 return ってのは、モナドのインスタンスを作っ てるようなものってことでしょうか?そうだとすると、どんなプログラムも取 り合えず return 使って終端すりゃー万事おっけーですかい?
でも、String のリストを作るのかと思ったら、
> main :: [String] > main = return "んちゃ!" "ぷぽ?"
えい!
ERROR "/home/cut-sea/script/haskell/dummy.lhs":4 - Type error in explicitly typed binding *** Term : main *** Type : [Char] *** Does not match : [String]
型から言えば当然ですけど、さすがに [“んちゃ!”,“ぷぽ?”] を作ってくれる わけでは無いんすねぇ。(–; うーむ、 return って一体・・・
リストがモナドである事を示すために定義されているのでしょう。 一応、ここにリストのreturnの使用例があります
たらいまわし関数
えーっと、たらいまわし関数を作って結果を印字させるプログラムを組んでみ ます。なんせ、たらいまわし関数なら LL 中、 まぁ最速じゃねぇ?と言われ ている位です。たらいまわし関数を計算するコマンドは、やはり Haskell で 実装すべきでしょ。 それ以外考えられません。はい。 え?一体何に使うのかって?そんなちっちぇコト気にしちゃダメっす。もっと おーっきな目で見ないとね、おーっきな目で。
もち、現時点でホントに私の手元には影も形もありません。内容的にはそれほ ど難しいとは思ってないのですが、ここまでで出て来なかった関数なんかも必 要になりそうな気がしてます。
[email protected]> tak 12 6 0
みたいにコマンドラインから実行出来るようにしたいと思っているので、真っ 先に、どうやるんだろ?って浮かぶ課題が、
- 引数は文字列で渡されるので、こいつを数値に変換
- 計算した数値を文字列にして、結果を印字
この二点です。で、「やさしいHaskell入門」とかを斜め読みしてると、まぁ た横道それるんだけど、 ここに「アクションは Haskellの値です」って書い てある。ってことは、理詰めで導いたのは正しくて、私のゴーストのささやき (要はカンね)の方が間違ってたらしい。まさに「自分のゴーストが信じられ ない」状態に。うますぎ(^^; ちなみにすぐ横道にそれたり、どんどんスタック積み上げまくって本筋に戻っ て来れずにオーバーフローするのは私の特技だったりします。しかもスタック の上限が異常に浅いです。はい。
おっとっと、いかんいかん!目的をすぐ見失いそうになります。数値を文字列 に変換したり、文字列を数値に変換するんだった。
えーっとー。・・・結構探したけどそれっぽいのが見当たらないっす。まぁい いです。何事も勉強です。 digitToInt とか intToDigit ってのが ここにあっ て、型からして、使えそうなんで試してみましょう。
Main> digitToInt '7' 7 Main> digitToInt '9' 9
はい、まずはこれが文字列から数値に変換する時に使えそうな気がします。
Main> intToDigit 7 '7' Main> intToDigit 9 '9'
これが、数値から文字列を作るのに使えそうな感じなんですけど、あまりにも 遠そうですんで、もしかしたら作ってるうちに使えそうな関数に出会えるのを 期待することにします。まぁ、こいつはキープってことで。
無謀ですけど、まず型宣言を書いてみましょう。実際に Haskeller な方がど ういう感じでコーディングするのかは、リアルタイムで見たことが無いのでど ういう順かは知りませんが、多分、型から入るんじゃないかなぁと思ったって ことでチャレンジ!
まず、main です。とりあえず、印字だけさせようと思います。 return も書 かないつもり。
main :: IO ()
main 内では putStrLn とかを使って書こうと思います。したがって、 putStrLn に渡すたらいまわし関数の返り値を文字列に変換する必要があるん で、その関数は整数から文字列への関数になると。
numberToStr :: Int -> String
あ、その前に getArgs から貰った引数の文字列を整数に変換する関数が要りますな。
strToNumber :: String -> Int
んで、このコマンドのまさに核部分です。言わずと知れた超有名な関数です。 ちなみに tak 関数とかたらいまわし関数とか色々言われるみたいなんですけ ど、正式にはどうなんですかね?まぁ、引数に整数を三つ貰って、んでから返 り値も整数です。
tak :: Int -> Int -> Int -> Int
こんなとこでしょうか?あと必要になったら、気付いた時点で追加することにします。
んじゃ、まず文字列を整数に変換してみます。
Prelude> map digitToInt "123" [1,2,3]
map に関しては実は mapM だの mapM_ だのを見てたので、どーしよーかと思っ たんですが、小細工なしで map でいったれ!ってやってみたら、すんなり動 いてくれちゃいました。
んじゃ、[1,2,3] とかってなったリストを各桁と見なした複数桁の数値にします。
Prelude> foldl1 (\x y -> 10*x+y) [1,2,3] 123
わーい、意外とすんなりいきました。ちょっと飲み会の後で少しアルデヒドが 脳細胞に刺激を与えている位が丁度いいのかもしれません。このすんなり具合 は中毒症状では無いので大丈夫(^^)v
んじゃ、strToNumber を作ります。
#!/usr/pkg/bin/runhugs \begin{code} main :: IO () numberToStr :: Int -> String strToNumber :: String -> Int strToNumber str = foldl1 (\x y->10*x+y) $ map digitToInt str tak :: Int -> Int -> Int -> Int \end{code}
これでロードしてみます。
Reading file "/home/cut-sea/script/haskell/tak.lhs": Parsing........................................................................ ERROR "/home/cut-sea/script/haskell/tak.lhs":5 - Missing binding for variable "main" in type signature
ありゃ?えーっと、なになに? main 変数に束縛がねーぞって叱ってる。あー、 本体の定義後まわしにしようと思ったんだけど、ダメなんだぁ。残念。
んじゃ、
#!/usr/pkg/bin/runhugs main :: IO () numberToStr :: Int -> String \begin{code} strToNumber :: String -> Int strToNumber str = foldl1 (\x y->10*x+y) $ map digitToInt str \end{code} tak :: Int -> Int -> Int -> Int
こんな感じになるかな?んで、走れ!
Main> strToNumber "123" 123
いぇーい!!d(^-^)b いや、感心してないで次いきましょう。 numberToStr です。
numberToStr :: Int -> String numberToStr num = do (q,r) <- (quotRem num 10) (numberToStr q) ++ [intToDigit r]
パターンなんぞをタプルに適用してみました。いや、使えるのかどうか知らん けどね。これでいけんじゃね?えいやー!
Type checking ERROR "/home/cut-sea/script/haskell/tak.lhs":13 - Type error in generator *** Term : (q,r) *** Type : (a,b) *** Does not match : Int
うがっ?なんだこれ?
quotRem :: Integral a => a -> a -> (a, a)
ってある。なんか Integral が意味分からんが、 (a, a) を返している。問題 は上のエラーメッセージ中で (a, b) って出ているところ。んーと、んじゃ、 これはどうよ?
numberToStr :: Int -> String numberToStr num = do q <- quot num 10 r <- rem num 10 if (q == 0) then [intToDigit r] else (numberToStr q) ++ [intToDigit r]
タプルをパターンにするのがダメだったのかもしれんと思ったわけだ。ほれ!
Type checking ERROR "/home/cut-sea/script/haskell/tak.lhs":13 - Type error in generator *** Term : num `quot` 10 *** Type : Int *** Does not match : a b
あ?だめだこりゃ。 これ見てもどうも何が悪いのかよくわかんねぇっす。はい。
doはモナド専用なので、 let,whereを使いましょう
・・・・・・・・・・・・・・・・・・・・・・・・おっと、ついのめり込ん でしまっちゃって実況中継忘れた。もう意地で do にこだわったんだけど、全 然型が通らない。押してダメなら引いてみなってことで、もちっと簡単にして みる。
numberToStr :: Int -> String numberToStr num = if (num < 10) then [intToDigit num] else (numberToStr $ quot num 10) ++ [intToDigit $ rem num 10]
こいつでどーだっ!!
Main> numberToStr 123 "123" Main> numberToStr 987654321 "987654321"
ほっ。よっしゃ!何とかいけたね。
んじゃ、 tak 関数を作ってみます。えーっと、私のずたぼろの「はじめての 人のためのLISP」を見ます。私を S式の世界にいざなってくれた真っ赤なハレ ンチ本です。
tak :: Int -> Int -> Int -> Int tak x y z = if (x > y) then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else y んで、ロード! Main> tak 6 3 0 6 Main> tak 8 4 0 8 Main> tak 10 5 0 10 Main> tak 12 6 0 12 Main> tak 100 50 0 100 Main> tak 200 100 0 200
うーっむ、定義を間違ってねーか?って思うくらい異常に早いです。信じらん 無いくらい。ボーゼンとしてしまいます。
えっと、残るは main だけ?よしゃ。その前にだいたいの流れを確認。
Main> map (strToNumber) ["200","100","0"] [200,100,0]
こいつに tak を適用すりゃいいだけ!うわ (^^) 取ったもドーゼン!!へへへ〜 ・・・あら?リストの各要素に適用しないとダメじゃん。しまった、どしよ。
とりあえず、 Scheme? とかだと apply とかってのを使う方向に考えるんだけど、 :find apply とかしても出て来ない。しょーがないから、急遽関数を作ることにしましょう。
apply f [x,y,z] = f x y z
こんなんアリ?だいたい型はどーなるんだよ?
Main> map (strToNumber) ["200","100","0"] [200,100,0] Main> apply tak [200,100,0] 200
おぉっ!動くじゃん。すげえすげえ。じゃあ例の常套手段を使って〜って、今回は違うぞ!一応型を自分で作ってみよう。
apply :: (tak の型) -> [a] -> a
かな?ってーことは、
apply :: (Int -> Int -> Int -> Int) -> [a] -> a
え、違うわ、 全部 Int かな。
apply :: (Int -> Int -> Int -> Int) -> [Int] -> Int
んでロードだ。
Main> apply (tak) [100,50,0] 100
おっけー!もー得意気になっちゃうよ!天狗天狗!!
一応 Haskell の型推論を確認すっと、
Main> apply ERROR - Cannot find "show" function for: *** Expression : apply *** Of type : (a -> a -> a -> b) -> [a] -> b
あらら。めちゃめちゃ応用範囲広いじゃん。オレってばすんげぇ apply の活 用を絞りすぎた。やっぱ難しいなぁ。型宣言するのが良いのって、ある程度プ ログラマの方も型推論がしっかり出来なきゃあまり意味無いって思うのオイラ だけっすか? どーも、具体例が頭にあるだけについ絞っちゃうんだよね。型宣言するときに、 具体的に考えている使い方以上に抽象化して考えないと、実際には Haskell の型推論レベルのものが得られない気がする。
import System main :: IO () main = do args <- getArgs putStr $ numberToStr $ apply (tak) $ map (strToNumber) args
こんなんですね。えっと、ロードがうまくいくんでファイルに実行権限つけて コマンドラインから実行!
[email protected]> ./tak.lhs 6 3 0 [email protected]>
あ、そっか。 putStr じゃなくて putStrLn の方がいいか。 んで、書き換えたんでコード全体はこんな感じになります。
#!/usr/pkg/bin/runhugs \begin{code} import System main :: IO () main = do args <- getArgs putStrLn $ numberToStr $ apply (tak) $ map (strToNumber) args apply :: (a -> a -> a -> b) -> [a] -> b apply f [x,y,z] = f x y z numberToStr :: Int -> String numberToStr num = if (num < 10) then [intToDigit num] else (numberToStr $ quot num 10) ++ [intToDigit $ rem num 10] strToNumber :: String -> Int strToNumber str = foldl1 (\x y->10*x+y) $ map digitToInt str tak :: Int -> Int -> Int -> Int tak x y z = if (x > y) then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else y \end{code}
実行例じゃー。
[email protected]> ./tak.lhs 6 3 0 6 [email protected]> ./tak.lhs 100 50 0 100 [email protected]> ./tak.lhs 200 100 0 200 [email protected]> ./tak.lhs 300 150 0 300 [email protected]> ./tak.lhs 400 200 0 400
よしゃ!すばらしい!!オレってすげぇ!! んじゃ、引続きこのコードを他にも書き換えられねぇか少し遊んでみよう。
しかし、やっぱはえぇっ!! 400 200 0 って与えた時はさすがに回答まで一 呼吸置くんだけど全然待たされてる気がしない。同一マシンで Emacs Lisp で の 12 6 0 を評価した時は、カップラーメンが作れちゃったくらいでしたよ? しかもかなりふやけてしまってスカスカになってたし。いや、そこまで律義に 待つオイラもどーかと思うけどさ。
#!/usr/local/bin/runhugs \begin{code} module Main where import System import IO main :: IO () main = do args <- getArgs case args of x:y:z:_ -> print $ tarai (read x) (read y) (read z) _ -> hPutStr stderr usage usage :: String usage = "Usage: tarai x y z" tarai :: Int -> Int -> Int -> Int tarai x y z | x <= y = y | otherwise = tarai (tarai (x-1) y z) (tarai (y-1) z x) (tarai (z-1) x y) \end{code}
おぉっ、引数チェックの様なものを入れたいと思ってたとこ。いくつか確認し たいので自分のコードをいじくる前にちょい見てみる。まず、大幅に楽チンに 済ませているのが read と print ですな。
んーっと :find read ってすると、なんだかごちゃごちゃしてるんだよな。芋 づる式でぐちゃぐちゃ readHogeってのが出てくるんすよ。マニュアル類を見 ていると ここに read ってのと show ってのが出てくるなぁ。ふむふむ。ど うやら型だの型クラスだのを少しかじっておいた方が良さげな感じ。
でもいきなり意味不明用語連発で死にたくなってくる。少しもどって、ちまち まやってみよう。 カタにハメてやろうか
さらに戻ってみると、 Color って型を定義してます。 んが、イマイチよう分からん。まぁ、よく理解できない時にはHaskellとお話 をして反応をうかがうんです。
data Color = Red | Blue | Green isColor :: Color->Bool isColor c = case c of Red -> True Blue -> True Green -> True _ -> False
んで、遊んでみましょう。
Main> :e color.hs Main> isColor Red True Main> isColor 123 ERROR - Cannot infer instance *** Instance : Num Color *** Expression : isColor 123 Main> isColor "Red" ERROR - Type error in application *** Expression : isColor "Red" *** Term : "Red" *** Type : String *** Does not match : Color Main> isColor Yellow ERROR - Undefined constructor function "Yellow" Main> Red == Red ERROR - Cannot infer instance *** Instance : Eq Color *** Expression : Red == Red
ふむ。なんとも面白いのは Red だの Blue だの Green だのってリテラルが data Color の右辺、つまりデータ構築子として出現しただけで、存在しちゃ うってこと。 C の enum なんかと同じみたいなんだけど、これってScheme?で 言えばどういうこと?敢えて書くとするとこんな感じでシンボル自身を指すシ ンボルを作るって感じだろうか?
(define Color '(Red Blue Green)) (define Red 'Red) (define Blue 'Blue) (define Green 'Green) (define (isColor c) (if (memq c Color) #t #f))
と思ったら、
Main> Red ERROR - Cannot find "show" function for: *** Expression : Red *** Of type : Color
あー、 show クラスじゃないから印字表現がねぇよって言われます。つまり、
(define Red 'Red) (define Blue 'Blue) (define Green 'Green)
ってトコまではやりすぎかな。まぁ全然内部でやってることは違うくさいけど。 どうも、RedとかBlueとかGreenとかってシンボルをColorってグループの一員 としてインターンしているって感じだろう。とりあえず、そう思ってみる。
んじゃ、お次は show クラスってやつだね。でも、つらつら見ててやっぱりキ ビシー!!いや、一部が分からない分にはいいんです。えぇ、大半が分からな いと気持ちが萎えます。
それでもケナゲに ここの導出されたインスタンスを見て、deriving ってな単 語に遭遇!わおっ。由来するとかって意味?しかもクラスっぽいのを継承して ますよ〜って電波をバシバシ感じます。
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving Eq
こーゆーとこに(^^) ちゅーわけで、
data Color = Red | Blue | Green deriving (Eq, Show)
これならどーよ!じゃじゃじゃーん!!
Main> Red Red Main> Green Green Main> Blue Blue
やたっ。正解じゃん。なんかそれっぽいのを継承したみたいっす。 でまぁ、 Eq ってクラスも継承してるつもりなんで、
Main> Red == Red True Main> Red == Blue False Main> Red == 123 ERROR - Cannot infer instance *** Instance : Num Color *** Expression : Red == 123
ってなワケでさっきは叱られた == なんかも適用可能と。クラスってのは他の 言語でいうクラスと同じようなもんなんでしょう。適用可能なメソッドとかも そのレベルで持っていると思われる。
・・・えーっ?そーいや、ずっと型がクラスみたいなもんかと思ってたぞ。(^^; そうか、違うのか。んー(-"-)
Haskell でのクラスとほかの言語でのクラスの意味が違うだけでしょう。 Java と比較するとこんなかんじ。
Haskell Java クラス インターフェイス インスタンス (型) クラス 値 インスタンス(オブジェクト) まーいーや、show クラスってのがなんか見えてきた。多分関数なんかも show クラスを継承させられると、Scheme?みたく関数だけを入力してやるとエラー じゃなくてそれっぽい印字名で見えてくるんだろう。しかし、さっきはどう表 示するのか指定しなかったのによきに計らってくれたな。関数とかだとどう指 定するんだろうね。
じゃあ引き続き、read クラスに行ってみようか。
Main> read ERROR - Unresolved overloading *** Type : Read a => String -> a *** Expression : read
って感じになってますな。 Read a ってのが List a ってのや IO a ってのと 似た様に a をなんかでくるんでるモノってやつです。
Read a は、型 a がクラス Read のインスタンスでなければならない、という 制約です。 ここ参照
Main> read "123" ERROR - Unresolved overloading *** Type : Read a => a *** Expression : read "123" Main> read "Hello" ERROR - Unresolved overloading *** Type : Read a => a *** Expression : read "Hello"
んー、どうもトップレベルは Read a ってやつの印字表現が分からず困ってる んでしょうか。そのままでは使えない感じです。でも、
Main> (read "123") + (read "456") 579
ってのは動作してくれますねぇ。どういうこったろ。
Main> (read "Hello") ++ (read "World") ERROR - Unresolved overloading *** Type : Read a => [a] *** Expression : read "Hello" ++ read "World"
って感じでだめだしさぁ。(–;
ちゅうわけで再度この辺を読んでみます。さっきより若干なりとも触った経験があるので、少しは読めるようになってんじゃない?っていうとっても甘い考えです。
Read クラスには、その値を表現する文字列を構文解析する演算が用意されています。 Show クラスの最も単純な関数は show です。
だそうですね。これってあるデータオブジェクトの印字表現をXとするような データオブジェクトxがあったら、Read X により x を作りますよってことか な。だとすると、read “Hello” ってのはまずくて、 read “\”Hello\"" って しないとだね。
Main> read "\"Hello\"" ERROR - Unresolved overloading *** Type : Read a => a *** Expression : read "\"Hello\""
は?もしかして受取り側がいるのかな。
Main> putStrLn $ read "\"Hello\"" Hello
うむ。どうもそうみたいね。では、 ++ に渡してみましょう。えいや!
Main> (read "\"Hello\"") ++ (read "\"World\"") ERROR - Unresolved overloading *** Type : Read a => [a] *** Expression : read "\"Hello\"" ++ read "\"World\""
あ、そっかすぐ忘れる。これでどーよ?
Main> putStrLn $ (read "\"Hello,\"") ++ (read "\"World\"") Hello,World
おおーっ、ぱちぱちぱち。
Main> (read "123")::Int 123 Main> (read "\"123\"")::String "123" Main> (read "\"Hello, \"" ++ read "\"World!\"")::String "Hello, World!"
お?何やら神のお告げによると、値の型を指定出来ちゃうんだ?んー、 :: っ て一体・・・?(+_+) Cで言えば強制的に値をキャストするようなもんだろか。
型を指定するというよりは、型推論とプログラマの意志の突き合わせができるという感じかなあ。
Main> ((read "123")::Int) + ((read "456")::Int) 579 Main> ((read "\"123\"")::String) ++ ((read "\"456\"")::String) "123456"
うむ。これで所望の型に指定できちゃうわけね。なかなか便利じゃん。これ。
Main> read “\”Hello,\"" ++ " " ++ read “\”World!\"" “Hello, World!” Main> let x = read “123” in x + 0 123 Main> (\ x -> x + 0) (read “123”) 123 Main> read “123” `asTypeOf` length [1..] 123
ほう。
Main> let x = read "123" in x + 0 123
これ見た瞬間に awk の var "" とか var+0 とか思い出してしまった。どうも 周囲の型とつき合わせて、 辻褄が合うような解釈が可能なら、そういう型だ と解釈してくれる って感じかなあ。 でも、そうするってーと、
Main> (read "\"Hello\"") ++ (read "\"World\"") ERROR - Unresolved overloading *** Type : Read a => [a] *** Expression : read "\"Hello\"" ++ read "\"World\""
これと
Main> read "\"Hello,\"" ++ " " ++ read "\"World!\"" "Hello, World!"
これの差がわかんないね。なんで上のはエラーが発生しちゃったんだろ。(?_?) " " ってやつは空白文字1文字を含むリストだな。ん?もしかして read “\”Hello\"" は [a] であって a = Char までは解決してくんないのか。あー はいはい。 ++ はあくまでリスト同士をつなぐだけだからこいつは、中に格納 されている要素が ‘H’ だとか ‘e’ だとか評価せずに [a] ってリスト構造を そのまんま返すんだ。
・・・いやいや、やっぱ納得できてないや。そいつを受け取ったトップレベル は印字できねぇの?印字しようとして値を求めた瞬間に具体的に “Hello,World” って分かるはずだよね。んむむ〜。
とにかく " " ってのが具体的にリスト内の型を特定するのに役立っているん だろうけど、どうにも釈然としないぞ。順を追ってみようか。
Main> (read "\"Hello\"") ++ (read "\"World\"")
ここで “\”Hello\"" ってのは [‘"’,‘H’,‘e’,‘l’,‘l’,‘o’,‘"’] だよね。こい つに read を apply すると read 自身は String->a (但し a は Read a) だ から、 “\”Hello\""自体が String ってのはいいんだ。こいつが Read a って のを返すんだな。あー、こいつに ++ を apply しようとしたってダメなんか? ++ は [b] と [b] とを引数にとるわけだから、 Read a なんてリストじゃな いものを渡したらそりゃ怒るわ。カンカンですわ。そっかトップレベルが印字 できねぇって怒ってるんじゃなかったのか?もしかして。
んじゃ、オッケーな方を検証してみようでないの。
Main> read "\"Hello,\"" ++ " " ++ read "\"World!\""
これは read “\”Hello,\"" ってした結果 Read a な a が得られるよ、と。 " " については [Char] だね。そうすっと ++ は [b]->[b]->[b] だから [b]->[Char]->[b] で b = Char だと。んで、 Read a な a は [b] なわけだ から a = [Char] だとみなされる?あーやっぱ awk の var "" を連想します ね。私は。
もっかいダメダメな方を考えると (read “\”Hello\"“) ++ (read”\“World\”") は a ++ a (但し a は Read a)なんだけど、 ++ 自体は確か に [b]->[b]->[b] だから a はリストだとはみなしてくれるのかもしれない なぁ。ありゃ?(^^; だったら、やっぱリストとして ++ をしてくれて、トップレベルが印字しよう と評価した瞬間に String だと分かるんで無いか?やっぱダメじゃん。理解で きてねーじゃん。うぁぁああ。(+,+;;
- read “Hello” てしたら多分 Hello ってのを内部にもった Read オブジェクトを作るんだよね?
- read “\”Hello\"" ってしたら “Hello” ってのを内部にもった Read オブジェクトを作るんだろう。
- read “123” ってしたら 123 を内部にもつ Read オブジェクトを作るだろうし、
- read “\”123\"" なら “123” ってのを内部にもった Read オブジェクトを作るはず。
仮に read が Scheme? で言うところの read-from-string 的なものだとすれ ば、 1番目のやつはシンボルHelloなオブジェクトを作り出すだろう。 2番目 と4番目は文字列オブジェクトを作るし、 3番目のだと数値データオブジェク トを作るはず。
ただ、Haskellだとreadが返す時点では、まだシンボルだとか数値だとか文字 列だとかってのは分かってないんじゃないかと思うのだ。別に根拠ねーんだけ どさ。
shiro: たぶん、「ここで帰って来る値はこういうオブジェクト」という考え方をしない方がいいんじゃないかと思います。Schemeなんかだと、実際にメモリ上にある型タグ付きのオブジェクトに触っている感触がありますが、 Haskellの場合、実行前に型推論が解決してるので、実行時にオブジェクトの型を見る必要は無い。「何かを中に持つReadオブジェクト」なんて存在しないんですよ。実行時にはreadが何の型を返すかは既に決まっていて、それを実行時に判断する必要はない。ただ、そのためには、readが何の型を返すかがコンパイル時にわかってないとならないってわけです。
へぇっ。最初よくわからなかったんだけど、何度か反芻してるうちにようやく ウンウン!て激しくヘッドバンキング! くくぅ!年も考えずに変なコトしたから首の筋痛めたくさい・・・。osz
そっか。型推論と評価は完全に分離したフェーズなんだ。だから型推論してい るフェーズ(段階)では、値を求めた結果として分かる様な具体的な型っての は分からないよと。型推論の段階で決め切れなかった結果、 showメソッドが ないねぇって思われたらジエンドっすね?
ってことはだ、
Main> read "\"Hello,\"" ++ " " ++ read "\"World!\""
ここで " " をはさむって行為は ::[Char] って感じで型を指定(プログラマ の意図とすり合わせる?)のと同じ意味を持つんだ。もちろん、ここでは実際 に空白文字を一文字挟んじゃうわけだけど。
とか何とか思ってたら、Lazyってページに遅延評価についても、いろいろ解説 されてるようだよ。実にタイムリー!ってかオイラの理解が一向に好転しない んで見かねて解説はじめてくれちゃったか?
ふむふむと分かった気になるかと思ったが、また新たな敵(味方?)が・・・ WHNF? ってなんや〜!!
ってわけで google に教えてもらったら、haskell-jp の豊福さんのスレッド に遭遇。ふむふむ。やっぱ同じようなとこで同じような疑問を持つんだよなぁ と感心。しかしやはり WHNFってほとんど断りなく使ってるな。 Weak Head Normal Formってのか?なんのこったか分からんが[Lazy]には、
WHNF というのは、乱暴にいうと一番外側(の一番左)に データコンストラクタが出てきた形、または、 λが出てきた形です。
だそうだ。この一番外の一番左ってのは、後続の具体例を読まないとイマイチ 釈然としなかったんだが、引数ってわけでもなさそうね。 show (1+1) だと show ってのがそうらしい。そいつが WHNF? になるまでは評価(簡約?)され るんだと。
うーむ。いろいろ出てきて自分が立っているところが分からなくなってきたよ。ガク。
ただ、haskell-jp のスレッドのNothingとputCharのあたりをざーっと読ん で、Lazyもふんがーって読むと、
Prelude> 1 + 1
を入力すると、show (1 + 1)を「評価をする」というのが 出発点と考えられるわけです。
ってのが、知りたかったとこかもしれん。実質 show は print から必要とさ れているんで、段階を踏むんだけどさ。この show ってやつが、ここずーーーっ と引っ掛かってる部分なわけだったんだ。
とりあえず、確認のため :find print ってしてみると、
print :: Show a => a -> IO () print = putStrLn . show
ふむ。show して文字列にしてから putStrLn に渡すと。
あ、あとさっきの haskell-jp の話題でおぉっってのは「トップレベルのロジッ クはフツーに read-eval-print-loop だよ〜」ってとこ。但し eval はクセモ ノだけど。
その辺を気にかけながら、read とか show を使ってもっかい Haskell と対話 してみようか。
Main> show $ putStr "Hello" "<<IO action>>" Main> show [putStr "Hello",putStr "World"] "[<<IO action>>,<<IO action>>]"
まずは噂の <<IO action>> を拝んでおこう。 putStr “Hello” は印字までは 実行せずとも WHNF? ってやつになるらしく、 “Hello” とは印字せず、そうす る IO 型のオブジェクトになる。そいつを show に渡したら一応 show クラス らしくて印字表現があるため、 (っつっても <<IO action>> だけだけど)って のを印字すると。二番目の例だと show が適用される段階では [IO a] だって ことなんだろうな。
Main> "Hello" "Hello" Main> show "Hello" "\"Hello\"" Main> show $ show "Hello" "\"\\\"Hello\\\"\"" Main> show $ show $ show "Hello" "\"\\\"\\\\\\\"Hello\\\\\\\"\\\"\"" Main> show $ show $ show $ show "Hello" "\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"Hello\\\\\\\\\\\\\\\"\\\\\\\"\\\"\""
・・・爆笑。いや、オレってば何がしたいんだ?って。 えっと、何したかったんだか忘れちゃった。えーっと、これは “Hello” 自体 は印字形式は文字列 “Hello” になる。つまり、こいつは ‘H’,‘l’,‘l’,‘o’ になるね。こいつに show を適用すると、
show :: show a => a -> String
なので、この場合、 a 自体も String になっている。たまたまね。そのため に左右のダブルクォーテーションもリストのメンバに取り込まれるんだね。 ‘"’,‘H’,‘l’,‘l’,‘o’,‘"’ってな感じにさ。さらにそいつがエ スケープされると。まぁ print も同じなんだろうな。
Main> print $ show "Hello" "\"Hello\"" あれ? print も一発 show を含んでいるから show $ show "Hello" ってしたのと同じようになると思ったんだけど、なんでだろ? Main> print "Hello" "Hello" Main> putStrLn "Hello" Hello
あー、 putStrLn がこうなるんだっけ。これは返り値が文字列ってワケじゃな くて、イタコのチャネル(この場合には標準出力)に Hello ってディスプレ イするっていうアクションの結果として見えているんだな。出力先がファイル ディスクリプタだったら書きこまれるだけで、モニタ上にはなんも出ないと。
続たらいまわし
だいぶスタックの深いトコから一気に戻るけど、窒素酔いを起こさないよーに。 たらいまわしコマンドをHaskellで実装したわけだが、こいつを色々いじくり まわして遊んでみようじゃないかってことです。
っつーわけで再掲。
#!/usr/pkg/bin/runhugs \begin{code} import System main :: IO () main = do args <- getArgs putStrLn $ numberToStr $ apply (tak) $ map (strToNumber) args apply :: (a -> a -> a -> b) -> [a] -> b apply f [x,y,z] = f x y z numberToStr :: Int -> String numberToStr num = if (num < 10) then [intToDigit num] else (numberToStr $ quot num 10) ++ [intToDigit $ rem num 10] strToNumber :: String -> Int strToNumber str = foldl1 (\x y->10*x+y) $ map digitToInt str tak :: Int -> Int -> Int -> Int tak x y z = if (x > y) then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else y \end{code}
まずは一度挫折している numberToStr の一時的な束縛を使ったコード。あの 後、神のささやきにて let,where を使うよろし!だって。(なぜに中国人風?)
numberToStr :: Int -> String numberToStr num = let (q,r) = (quotRem num 10) in (numberToStr q) ++ [intToDigit r]
こんな感じになるはず。多分。 let decl in body ってな具合です。んで emacs 上で試してみる。
Main> numberToStr 123 " Process hugs illegal instruction (core dumped)
あ、がぁん!!Hugs殺しちゃったよ〜。そんなに悪いことした?オレ(++;アタフタ えっとーどういうこったろ。
Main> numberToStr 1 " Process hugs illegal instruction (core dumped)
ダメだ。でもよーく見るとコアダンプの直前に " ってダブルクォーテーショ ン一個だけの断末魔の叫びが。
どうやら処理しようとはしてくれている。しばし考えててようやく分かりまし た。いやぁ、何とも間抜けなんだけど再帰で書いてて基底条件が無いじゃん。
numberToStr :: Int -> String numberToStr num = let (q,r) = (quotRem num 10) in if q == 0 then [intToDigit r] else (numberToStr q) ++ [intToDigit r]
こんな風に in の中に書いてやる。そんでからプロンプトにぃ〜食らえ!!
Main> numberToStr 123 "123" Main> numberToStr 987654321 "987654321" Main>
やたっ!ちなみに where 節ってのも使えるらしいから、試しておこう。
numberToStr :: Int -> String numberToStr num | q == 0 = [intToDigit r] | otherwise = (numberToStr q) ++ [intToDigit r] where (q,r) = (quotRem num 10)
otherwise とかいけんのかな?って思ってたら大丈夫でした。えーっともしか して _ とか使えないかな?
| _ = (numberToStr q) ++ [intToDigit r]
こうやって、ロードしてやると・・・
Type checking.................................................................. Compiling...................................................................... Reading file "/home/cut-sea/script/haskell/tak.lhs": Parsing........................................................................ Dependency analysis ERROR "/home/cut-sea/script/haskell/tak.lhs":27 - Illegal `_' in expression System>
あーだめか。 case っぽいのとはまた違うのかな?うーん。どうも整理できて ないんだよな。まーいーんだけどね。
‘_’はパターン部(’|‘の前)でしか使えません。ガード部(’|‘の後)には真偽値しか来ません。 ちなみに、’otherwise’ が使えるのは、Prelude で True と 定義 されているからです。
とにかく time とかで計測してみたけど、この三種類ではあんまし大差ない感 じ。ところが、意外だったのは神の提示された read を使っているコードが若 干遅めだったこと。これって凄い不思議な感じ。なんでかって?だって read って個人的には言語核の read-eval-print における read だと思ってたんだ よね。これは高速だろうなと、はい。
一方 numberToStr はっつーと別の関数として実装してて、一文字ずつ読み込 んでは変換してって作業をしているわけだから明らかに遅そうじゃん?一体全 体なんなんでしょう。もしかして read ってのは read-eval-print の read とは別モンなんだろうか。(–; それとも read 自体の問題じゃなくて他になんか遅くなる要因があるんだろう か。もしくは私のread は速えぇって感覚の方がおかしいんだろうか。
プロファイラとかあればいいんだろうけどなぁ。そういやデバッグの標準的な やり方とかも知らんな。なんとなくエラーメッセージからアタリつけたり、あ と神のツッコミだよりって感じ。あ、あと遅延評価(要は問題先送り精神のこ とね)。
http://cvs.haskell.org/Hugs/pages/users_guide/observe.html[ Hugs のマ ニュアル]によると、デバッグには Observe (もしくは Hugs.Observe)モジュー ルが使えるようです。このモジュールをインポートして、監視したい値の前に ‘observe “name”’ と付ければ、実行後に値を表示してくれます。
Observe> let f x = if x == 0 then 1 else x * observe "f" f (x-1) in f 10 3628800 >>>>>>> Observations <<<<<< f { \ 9 -> 362880 , \ 8 -> 40320 , \ 7 -> 5040 , \ 6 -> 720 , \ 5 -> 120 , \ 4 -> 24 , \ 3 -> 6 , \ 2 -> 2 , \ 1 -> 1 , \ 0 -> 1 } strToNumber = foldl1 (\x y -> 10 * x + y) . map digitToInt
に対して hugs +s で
Main> strToNumber "123" 123 (124 reductions, 212 cells) Main> read "123" :: Int 123 (1048 reductions, 1537 cells)
でした。
readはただの関数でRead Intも /usr/local/lib/hugs/libraries/Hugs/Prelude.hs で定義されています。御参 考まで。
余計なお世話かもしれませんが、型推論の時のエラーと実行時のエラーを混同 されているように見受けられます。 (特に read の辺り) Hugs では、実行時のエラーは ‘Program error:’ と表示されます。
Prelude> [1] !! (-1) Program error: Prelude.!!: negative index
ここまでの WayToHaskeller に載っているエラーメッセージで、 ‘Program error:’ と表示されているのは2箇所だけです。つまり、今までのエラーは、 ほとんど型推論の段階で起きていて、コードはいっさい実行されていない、と いう事になります。
ふむふむ。ちょっとしたデバッグプリントやオプションが使えるんだぁ。言わ れてみれば確かに read って色々分岐しまくってたな。 reduction ってのは 何回簡約されたかですな。cells ってのは消費したメモリセル? Lispのご本 尊みたいなものの消費量かなぁとか予想してみる。とりあえず簡約回数だけみ ても、あー read って何回も色々やることがあるんだぁってのが伺えちゃいま す。
デバッグの手段を知ろう
この辺りを知って、しかも話の流れからすると・・・たらいまわし関数の値の デバッグプリントとか見てみたくないですかい?わしゃ見たい!っちゅーわけ で見てみることにしてみましょう。はい。
まずは、Observe をインポートすんだね。import System の下あたりに追加します。
import System import Observe <= コレ
でもって emacs 上で Ctrl-c Ctrl-l でロードしてから、
Main> observe "tak" tak 6 3 0 6 >>>>>>> Observations <<<<<< tak { \ 6 3 0 -> 6 }
あれぇ? 6 3 0 じゃ小さいのか?そんな問題じゃねーよな。でも、一応確認 してみよう。
Main> observe "tak" tak 200 100 0 200 >>>>>>> Observations <<<<<< tak { \ 200 100 0 -> 200 }
うーん。やっぱ関係ないね。もっかい神の啓示を振り返ってみると、
Observe> let f x = if x == 0 then 1 else x * observe "f" f (x-1) in f 10
・・・あーっそっか!ハイハイ。デバッグプリントだった。評価するステップ で印字するんだ。
Main> let tak x y z = if (x > y) then tak (observe "tak" tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else y in tak 10 5 0 10 >>>>>>> Observations <<<<<< tak { \ 9 5 0 -> 9 , \ 8 5 0 -> 8 , \ 7 5 0 -> 7 , \ 6 5 0 -> 6 , \ 5 5 0 -> 5 , \ 3 0 6 -> 6 , \ 2 0 6 -> 6 , \ 1 0 6 -> 6 , \ 0 0 6 -> 0 , \ 3 0 7 -> 7 , \ 2 0 7 -> 7 , \ 1 0 7 -> 7 , \ 0 0 7 -> 0 , \ 3 0 8 -> 8 , \ 2 0 8 -> 8 , \ 1 0 8 -> 8 , \ 0 0 8 -> 0 , \ 3 0 9 -> 9 , \ 2 0 9 -> 9 , \ 1 0 9 -> 9 , \ 0 0 9 -> 0 , \ 3 0 10 -> 10 , \ 2 0 10 -> 10 , \ 1 0 10 -> 10 , \ 0 0 10 -> 0 }
おおぉっ!でけたでけた。ぱちぱちぱち。 一方Schemeインタプリタだと、どうなるかってーと、
gosh> (define (tak x y z) (if (> x y) (tak #?=(tak (- x 1) y z) (tak (- y 1) z x) (tak (- z 1) x y)) y)) tak
さぁ、試してみましょう。
gosh> (tak 10 5 0) : :
・・・(+_+)えぇ、とても書き込めません。そんな必要無いですし。 しかし、最初の引数を observe しただけでもアレですから大変なもんです。 全部を observe したらどうなるでしょうかね。
Main> let tak x y z = if (x > y) then tak (observe "tak" tak (x-1) y z) (observe "tak" tak (y-1) z x) (observe "tak" tak (z-1) x y) else y in tak 10 5 0 10 >>>>>>> Observations <<<<<< tak { \ 9 5 0 -> 9 , \ 8 5 0 -> 8 , \ 7 5 0 -> 7 , \ 6 5 0 -> 6 , \ 5 5 0 -> 5 , \ 4 0 6 -> 6 , \ 3 0 6 -> 6 , \ 2 0 6 -> 6 , \ 1 0 6 -> 6 , \ 0 0 6 -> 0 , \ (-1) 6 1 -> 6 , \ (-1) 6 2 -> 6 , \ (-1) 6 3 -> 6 , \ (-1) 6 4 -> 6 , \ 4 0 7 -> 7 , \ 3 0 7 -> 7 , \ 2 0 7 -> 7 , \ 1 0 7 -> 7 , \ 0 0 7 -> 0 , \ (-1) 7 1 -> 7 , \ (-1) 7 2 -> 7 , \ (-1) 7 3 -> 7 , \ (-1) 7 4 -> 7 , \ 4 0 8 -> 8 , \ 3 0 8 -> 8 , \ 2 0 8 -> 8 , \ 1 0 8 -> 8 , \ 0 0 8 -> 0 , \ (-1) 8 1 -> 8 , \ (-1) 8 2 -> 8 , \ (-1) 8 3 -> 8 , \ (-1) 8 4 -> 8 , \ 4 0 9 -> 9 , \ 3 0 9 -> 9 , \ 2 0 9 -> 9 , \ 1 0 9 -> 9 , \ 0 0 9 -> 0 , \ (-1) 9 1 -> 9 , \ (-1) 9 2 -> 9 , \ (-1) 9 3 -> 9 , \ (-1) 9 4 -> 9 , \ 4 0 10 -> 10 , \ 3 0 10 -> 10 , \ 2 0 10 -> 10 , \ 1 0 10 -> 10 , \ 0 0 10 -> 0 , \ (-1) 10 1 -> 10 , \ (-1) 10 2 -> 10 , \ (-1) 10 3 -> 10 , \ (-1) 10 4 -> 10 } Main>
スゴイデスネ〜ワタクシビックリギョウテンデゴザイマス〜。 こりゃ速いハズだわ。うん。ちなみに observe したやつはコマンドラインか ら ./tak.lhs 10 5 0 とかってしても印字はされません。投げてるポートか違 うんでしょうね。
でもってよーく考えてみると、事実上たらいまわし関数の値を求めるのに必要 な評価ってのはこれだけで済む訳で、考えようによっては、これって一種のコ ンパイラの最適化じゃない?
さらに trace なんかもあるらしい。こいつも試してみよう。 Everyday:2004-10-31を参考にしてみる。
apply :: (a -> a -> a -> b) -> [a] -> b apply f [x,y,z] = trace ("trace:"++show x++","++show y++","++show z++"=") $ f x y z
さあ、これでどーよ?
Type checking ERROR "/home/cut-sea/script/haskell/tak.lhs":31 - Cannot justify constraints in explicitly typed binding *** Expression : apply *** Type : (a -> a -> a -> b) -> [a] -> b *** Given context : () *** Constraints : Show a Trace>
?! Cannot justify constraints in explicitly typed binding って型があ わねぇっておっしゃってるんだろうか。 show a がまずいのかな?ってゆーか、 もしかしてこうかい?
-- apply :: (a -> a -> a -> b) -> [a] -> b apply :: (Int -> Int -> Int -> Int) -> [Int] -> Int apply f [x,y,z] = trace ("trace:"++show x++","++show y++","++show z++"=") $ f x y z
apply :: Show a => (a -> a -> a -> b) -> [a] -> b
でもうまくいきます。
どうかな?
Main> apply tak [6,3,0] trace:6,3,0=6 Main>
あ、おっけーですね。でもまた同じ間違いをしちゃいました。こんなトコに書 いたってしょーがねーじゃんね。
tak :: Int -> Int -> Int -> Int tak x y z = if (x > y) then trace ("trace:"++show x++","++show y++","++show z++"\n") $ tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else y
ここでした。これで一応それっぽい動きが確認できますな。
Main> tak 6 3 0 trace:6,3,0 trace:5,3,0 trace:4,3,0 trace:2,0,4 trace:1,0,4 trace:2,0,5 trace:1,0,5 trace:2,0,6 trace:1,0,6 6
ってね。ぱちぱちぱち〜。 でも正直 observe の方が便利な気がするんですけどね。それって gosh の #?= に慣れちゃってるからかなぁ。
いずれにせよデバッグの秘宝も手にしたし、ページもでかくなって来てそろそ ろ編集するのも辛いので (すでに理由をナメてんな)よちよち歩きへGO!!
【ちょっとひとやすみ】メイビー?
やっぱりまだまだ先。。。
よちよち歩き
(「よちよち歩き」を編集する)|(Haskellerへの道)
他の方々がさくさくHaskellのコードを紡いでいるのを見ていると、まだまだ 道のりは長いらしい。 実は入門の目次見てても型関係とモナド関係でおしまい?って気がしないでも なかったんだが、かなり勘違い?なのが見て取れる。 とにかく二足歩行にちゃれんじだぜ!べいべっ!!
自分で型を
かのやさしいHaskell入門が実は入門者に難しいという定説があるんだけど、 思うにココが
しっしっ!けーれ!こちとら一見さんはお断りでいっ!っておっしゃってる様 に思える。 でもさ、自分の問題に合わせて型とかを作るのって、言語によらず基本中の基 本じゃないすか? なんもかもリストとかなんもかも配列とか、そういう言語上でだって、見掛け 上だけでもそれっぽい型に見えるようなナニモノかをでっち上げるもんね?
っちゅーわけで、以前から Tree とか気になってたりするわけさ。
data Tree a = Leaf a | Branch (Tree a) (Tree a)
この直前に
・・・ような型構築子とPtのようなデータ構築子は別の名前空間にあります。つまり、つぎのように、型構築子とデータ構築子に同じ名前を使うことが できるということです。
data Point a = Point a a
こんな説明があるのだが、なんか足りない気がする。 うーん。Point ってデータ構築子はいつどこでだれが作るんだろう?
というワケで例によってHugsの反応をうかがいつつ考えてみましょー。
#! /usr/pkg/bin/runhugs
\begin{code}
data Tree a = Leaf a | Branch (Tree a) (Tree a)
\end{code}
まずは、これだけのコードを C-c C-l でロードしてみる。
/usr/pkg/share/hugs/lib/Prelude.hs
/home/cut-sea/script/haskell/tree.lhs
Main>
お?予想外に通りました。何を予想してたかってーと、左辺のやつは型構築子 てやつで、右辺にあるのがデータ構築子だってことらしいけど、どっこにも定 義してないんだよね?このデータ構築子ってのがさー。にも関わらずエラーも 何も出ないと考える方が異常かなぁっと。 もしかしてたまたま Branch とか含まれてるのか?と思って :find Branch っ てすると、なんと自分で書いた上のコードがそのまま表示されやがった。 :find Tree しても同じ。
・・・ってーことはデータ構築子ってのは勝手に作られるんだね。そういや、 以前ここ で Color をいじった時にも、何もしなくたって良かったな。 Red とか Blue とかどうやって作るか?なんて考えもしてないわ。どうやら具体的 にどのように実装するものかってのを定義しなくてもいいわけか。以前は列挙 型ってことだったんで、 Red や Blue ってまるでリテラルの定義の様に錯覚 してたけど、改めて説明を読むと、これって(無引数)データ構築子だから Red とか Blue のインスタンスを生成する関数(?)のようなもんなわけか。
しかもユーザ定義のデータ型とかって、例えば C ならどんな構造体だとか、 この要素は int だとか char * だとか指定しないといけないけど、 Haskell の場合には不必要と?んーっむ、すんげー不思議(–; ってゆーかペテンにかけられてる気がする。どんな構造(気持ち的には実装) か指定してもいないのになんとなく作れちゃうのか?単に型同士の関係さえ掴 めればいいのだろうか? でもさぁ、そうだとしても、さらなる疑問が出て来るぞ。型同士の関係って意 味でいうと次のところとか気になるんだよなぁ。個人的に。
型構築子とデータ構築子は別の名前空間にあるんなら、
data Tree a = Leaf a | Branch (Tree a) (Tree a)
この型宣言の左辺にある Tree ってやつと 右辺の Tree ってやつの関係は? 右辺の Tree ってのは Tree って型のデータを作るためのデータ構築子だと思っ てたけど違うのか? emacs 上でカーソルを合わせるけど案の定なんも表示されないね。ミニバッ ファ。 んじゃ、 Main> プロンプトに Leaf 1 とか入れてみようか?
Main> Leaf 2
ERROR - Cannot find "show" function for:
*** Expression : Leaf 2
*** Of type : Tree Integer
ありゃ、ダメだ。そっか show クラス継承せんといかんな。
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving Show
改めてロード!
Main> Leaf 1
Leaf 1
Main> Leaf 2
Leaf 2
Main> Tree 1
ERROR - Undefined constructor function "Tree"
ふむ、Tree はデータ構築子じゃねーよ!って叱られちった。やっぱそうなんか。 んだらば、コレは?
Main> Branch 1 2
ERROR - Unresolved overloading
*** Type : Num (Tree a) => Tree a
*** Expression : Branch 1 2
あ、違った。(^^;
Main> Branch (Leaf 1) (Leaf 2)
Branch (Leaf 1) (Leaf 2)
Main>
うむ。ってーことは、右辺に現れる大文字英字から始まるシンボル名の内、ど れがデータ構築子で、どれが型構築子なんじゃろ(?_?)
data Point a = Point a a deriving Show
これはアリなんだよねぇ?うーん。 こういう時は、大っ嫌いなんだけど仕様書みるっきゃねーか。 ユーザ定義のデータ型を見てみよう。
topdecl -> data [context =>] simpletype = constrs [deriving]
simpletype -> tycon tyvar1 ... tyvark (k>=0)
constrs -> constr1 | ... | constrn (n>=1)
constr -> con [!] atype1 ... [!] atypek (arity con = k, k>=0)
| (btype | ! atype) conop (btype | ! atype) (infix conop)
| con { fielddecl1 , ... , fielddecln } (n>=0)
fielddecl -> vars :: (type | ! atype)
deriving -> deriving (dclass | (dclass1, ... , dclassn)) (n>=0)
dclass -> qtycls
・・・うわっ。(^o^; 基本的には topdecl は data simpletype = constrs だよってーとこから下っ ていって、 constr1 | constr2 … | constrn って感じの列挙になるよと。 でもって constr ってのがどうも難し過ぎて閉口しちゃいました。ただ、なん となくなんだけど con ほげほげ ってな感じになってそうなんで、 con って のが constructor の略だとすれば、最初の一個目のシンボルだけがデータ構 築子ってことの様に思える。残りの atype ってのは多分、なんかの型だと思 うんだけど自信ねぇや。 [!]って感じで角括弧で囲まれたやつは省略可ってこ とだと思うんだけど、これも自信無し。まぁ、あまり突っ込んでも、まだ分か らないと思うんで潔くあきらめる。 とりあえず、| で区切って、各部分の最初のシンボルがデータ構築子の名前に なるんでしょーって思っておこう。 (ただし、途中に infix 云々があるんで、 こいつはちょい別っぽいけど)
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving Show
つまり、ここで Branch ほげ ふがってのは、この時点で Branch がデータ構 築子、ほげとふがが型を表してんだね。ってーことは、
data Tree a = Leaf a | Tree (Tree a) (Tree a) deriving Show
~~~~~~
こんな風に書いた場合、ここに出てくる Tree のうち、最初の波下線つけた Tree だけがデータ構築子で、続く (Tree a) (Tree a) が型であり、データ構 築子 Tree の引数(って言い方が妥当かどうか知らんが)にもなるシロモノだね。
Main> Tree (Leaf 1) (Leaf 2)
Tree (Leaf 1) (Leaf 2)
Main> Tree (Tree (Leaf 1) (Leaf 2)) (Tree (Leaf 3) (Leaf 4))
Tree (Tree (Leaf 1) (Leaf 2)) (Tree (Leaf 3) (Leaf 4))
Main> Tree (Tree (Leaf 'a') (Leaf 'b')) (Tree (Leaf 'c') (Leaf 'd'))
Tree (Tree (Leaf 'a') (Leaf 'b')) (Tree (Leaf 'c') (Leaf 'd'))
Main>
うん。なんとなく正解くさくないっすか?
さて、ここで重要なんじゃないかと思うのは、まさに Tree がどんな構造(こ こも気持ち的には実装ね)か?なんて一切指定というか記述されてないこと! ではなかろーか。 上の Tree 型の定義をみたら、規格外の偏屈者でもない限り、
Tree (Leaf 1) (Leaf 2)
+---+---+
| + | + |
+/--+--\+
/ \
+--/+ +\--+
| + | | + |
+---+ +---+
Leaf 1 Leaf 2
こーんな感じの木構造を思い浮かべるハズ。(だよね?だよね?DAYONE?オレ が規格外の偏屈者ってこたないと・・・) でも、多分 Haskell は 「想像すんのは勝手だけど、どんな風に実現してるか はオレの勝手じゃ!」とおっしゃるかもしれんワケだ。
例えば、Scheme? とかの Lisp? 系言語ではフツーはリストって言えば、おそ らく上の Tree みたいになっているハズだけど、全く違う実装も考えられる。
(define (cons x y)
(lambda (z) (z x y)))
(define (car z)
(z (lambda (x y) x)))
(define (cdr z)
(z (lambda (x y) y)))
なんて感じで実装されてたって良いわけやね。要は cons/car/cdr が互いに、 インターフェースがピシッと合わせてあって、ソトヅラさえ、正しく動けば、 どんなんでもそんなん自由なわけか。
ただ、上記で定義した cons/car/cdr は、こいつらの中だけで仲間意識があって、
native に実装されている cons/car/cdr とは協定組んでないんで、いっしょ くたにして書いても動かないのは当ったり前。つまり、こいつらはこいつらの 世界だけで構成される計算ロジックの中でだけうまく動くので、ちゃんと閉じ 込めてやらないといけないんだよね。 (実はモナドってやつの世界観もコレっぽい気がせんでもないんだが)
data Tree a = Leaf a | Tree (Tree a) (Tree a) deriving (Show,Eq)
こんな風に Eq も継承させてみる。そうすっと、
Main> Tree (Leaf 1) (Leaf 2) == Tree (Leaf 1) (Leaf 2)
True
Main> Tree (Leaf 1) (Leaf 2) == Tree (Leaf 1) (Leaf 3)
False
比較判定もできるようになるんですな。さて、ここでこんなん試してみましょう。
ちょっと割り込み。Quiz です(まず、脳内で考えましょう。それから、処理系に訊きましょう)。
data Tree a = Leaf a | Branch (Tree a) (Tree a)
- Leaf の型は?
- Branch の型は?
–nobsun(2004/12/08 10:23:37 JST)
おっと、割り込み要求を受信してしまった。しかも、処理系に聞かないように とクギさされました。なんか行動見抜かれてます。ぎく。(-_-;
でも忘年会帰りでアルデヒドが脳内で暴れ狂ってるんで何か分かりそうな気がする。
えーと、 Leaf とか Branch ってのはデータ構築子ですねぇ。データ構築子に 型なんてあんの?と一瞬固まったんだけど、こいつは確かに一引数とか二引数 の関数とみなせそうですな。 Leaf は 任意の型 a を引数にとり、 Tree a な 型のデータを生成する関数なんじゃない? 生成するなんて言い方が妥当かどうか怪しいんだけど。同様に、 Branch は Tree a を二つ引数に取って、 Tree a な型のデータを返す関数ですよね? これって妙に身構えちゃってたけど、良く考えたら構築子が関数ってのは別に 驚くようなこっちゃねぇな。(^^; ま、ともあれ口頭で話すよか書いた方が綺麗かなぁ。
Leaf :: a -> Tree a
Branch :: Tree a -> Tree a -> Tree a
こんな感じじゃなかろかしらん。しかも今回は結構自信マンマン。(^^)v なんかすげぇつじつま合ってそうだし。同様に推測すると Red や Blue なんかも。
Red :: Color
Blue :: Color
ですね。無引数の関数って実質 thunk で、それは Haskell では値だ。これも 自分的には納得できる。
んじゃ、自信もあるんでファイナルアンサー!
Main> Leaf
ERROR - Cannot find "show" function for:
*** Expression : Leaf
*** Of type : a -> Tree a
Main> Branch
ERROR - Cannot find "show" function for:
*** Expression : Branch
*** Of type : Tree a -> Tree a -> Tree a
Main>
ビンゴ!!やったね!!(^^)v しかしアルコール入るといー感じなのは喜ぶべきなんか・・・?ってな自己ツッ コミはおいといて、素晴らしい!!ダテに二足歩行しちゃいねぇ! ・・・あれ?ってことはデータ構築子って関数?オイラそんな関数なんて定義 してねぇぞ?(@[email protected];えぇっ?? 関数って定義してないのに関数が出来てるくさいんですけど、コレ・・・。酔っ 払いすぎてマボロシでも見てますか?ワタシ??
気になるので逆にこんなんしてみる。これで逆に Tree a 型ってのができちゃっ たりして。ゴクリ。
#! /usr/pkg/bin/runhugs
\begin{code}
{-
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show,Eq)
data Tree a = Leaf a
| Tree (Tree a) (Tree a) deriving (Show,Eq)
data Point a = Point a a deriving Show
-}
Leaf :: a -> Tree a
Branch :: Tree a -> Tree a -> Tree a
\end{code}
つまり、今度は data Tree a を宣言するんじゃなくて、 Leaf や Branch 関 数の型宣言をすることで、あたかも Tree a 型が宣言できちゃうんじゃないか なぁ?って企てだ。いけーっ!!
Prelude> :load /home/cut-sea/script/haskell/tree.lhs
Reading file "/home/cut-sea/script/haskell/tree.lhs":
Parsing
ERROR "/home/cut-sea/script/haskell/tree.lhs":13 - Syntax error in declaration (unexpected `::')
Prelude>
あーぁ、これはハズレだ。
「えっ? 型? あ、そうか。そんなものもあったな。いやあ、すっかり忘れておったわ」
ガーーーッン (‾□‾;)
タイプ量は増えるけど、処理系に礼儀正しく型を聞く方法。
Hugs.Base> :t id id :: a -> a
よくよく考えたら分かったかも。
あ、いや、上で Leaf や Branch を定義することで Tree a が出来るか?って ムボーなチャレンジがダメな理由がね。
やっぱ引用はこっからだ。
すべての値は型 (type)をもちます。 (直観的には型は値の集合と考えることができます。)
そうよ、型は値の集合なんよね。(あくまで直観的にってお断りあるけど) 例えば、上の例で言えば Leaf か Branch で構築されるものだけが Tree なん だ。 Leaf だけでもないし、Branch だけでもない。また、それ以外のものも 含まれちゃいない。だけど、ムボーなチャレンジの方で、もし私が Leaf だけ 定義してたとしたら、 Tree を完全に表現できてないんだね。(でも完全なの か不完全なのかすら分からん) 同じく Leaf と Branch だけで Tree の全てかどうかは Tree が明かされてな い以上は保証できん。
逆に型 Tree を記述すれば、要素は全部明確になっているから、どの要素だっ て生成するための関数の存在を仮定する(?)ことは可能なような気がする。 なんとなくだけどさ。私の信用ゼロのゴーストがそうささやいている。
nobsun (2004/12/12 23:35:47 JST): データ構築子は関数のような型をもっていますし、関数のように使えますが、関数ではありません。
data Tree a = Leaf a | Branch (Tree a) (Tree a) tree,tree' :: Tree a -> Tree a -> Tree a tree = Branch tree' = Branch
この tree および tree’ は関数です。では、tree、tree’ と Branch ではなにが違いますか?
「ただの」関数ではないけれど、関数であるべき用件は満たしているのでは。 – yts
Paul Hudak:
> the Haskell designers might have used the following syntax: > > data Shape where > Circle :: Float -> Shape > Square :: Float -> Shape > > which conveys exactly the same information, and makes it quite clear > that Circle and Square are functions.
http://www.haskell.org//pipermail/haskell-cafe/2004-November/007357.html
データ構築子は関数では無いのか?
うーん。分かりません。(+_+; とりあえず、関数は関数の本体が無いとダメだけど、データ構築子は無くても おっけー!ってか無い。
nobsun(2004/12/15 15:31:31 JST)
データ構築子はタグのようなもので、強いて関数として書くなら、
(define (Leaf x) (list ’Leaf x)) (define (Branch t1 t2) (list ’Branch t1 t2))
と同じようなものです。これなら、どれも同じパターンなので、強いて本体を書く必要はないでしょう(Haskell では書きたくても書けませんが。。。)。
前に上げた、tree、tree’ と Branch の重要な違いは、たとえば、
Branch (Leaf 5) (Leaf 1)
というデータがあったとき、このデータは、tree によって作られたものなの か tree’ によって作られたものかは解らないですよね。でも、Branch で作ら れたものだということは、解ります。
他になんかあるかなぁって思っていくつか式入力してみたけど、特に差らしき とこが分かんないです。違いの分からない男です。えぇ・・・。
・・・ってワケで、googleさん使ってカンニングしちゃいます。 関数型言語の比較/型の定義なんぞをみると、関数とデータ構築子は名前空 間は別って書いてあるな。名前の先頭文字が大文字・小文字の差はある。他に なんか美味そうなネタないかしらん。
あとは、(今神が導こうとしているとこと違うかも知れないけど) このフィールドラベルか。
data T = C1 {f :: Int, g :: Float}
| C2 {f :: Int, h :: Bool}
フィールド名 f は型 T の両方の構築子に適用されています。
つまり、もし、x の型が T ならば、x {f=5} は T 内の
どちらの構築子が生成した値に対しても機能します。
ふむ。これはちょっと、いやかなり面白いかも。 ここで C1 とか C2 とかってのが、 Leaf や Branch にあたるわけだけど、こ の例で x ってやつは葉っぱか枝かはっきりしてない状態でデータが作れちゃ うんだって。
とかなんとか言ってるうちにお告げがありました。ほー。ってことは、こうい うのはダメなのかな。
\begin{code}
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show,Eq)
data Three a = Leaf a | Branch a deriving Show
\end{code}
つまり、 Leaf で作られたり Branch で作られたりしたデータが、自分でなん の型か分からないとか唯一に決まんないのはNG食らいそう。 例によって C-c C-l してやると。
Prelude> :load /home/cut-sea/script/haskell/tree.lhs
Reading file "/home/cut-sea/script/haskell/tree.lhs":
ERROR "/home/cut-sea/script/haskell/tree.lhs":5 - Repeated definition for constructor function "Leaf"
Prelude>
って感じで、 Repeated definition ってのが Leaf に対して出される。 ちなみに Branch で出ないのはなんでか?今の時点で考えられるのは、
- Leaf でエラー出るから、そっから先はまだ目が行ってない。
- Branch の引数の取り方が違うんでちゃんと見分けてくれてる。
- それ以外の何か。(思い付かん)
さぁどれかな。
data Three a = Branch a deriving Show
こうしてみる。 Three から Leaf を除くの。
Prelude> :load /home/cut-sea/script/haskell/tree.lhs
Reading file "/home/cut-sea/script/haskell/tree.lhs":
ERROR "/home/cut-sea/script/haskell/tree.lhs":5 - Repeated definition for constructor function "Branch"
Prelude>
って訳でいっちゃん上のやつでした。 んー、やっぱS式で表現されると実にスンナリ受け入れられマス。
さて、じゃあ Tree みたいなありがちな構造だけじゃなく、もちっと複雑な型 を見てみよう。でも自分じゃ考えらんない。そもそもどういった構造を考えて も所詮既成概念からくるようなべったべたの工夫のカケラもないものになるの が目に見えておる。
というわけでネタ探しだ。っつーか、心当たりはあるのだが。えーっと。あ、 これこれここにある型構築子の不動点だのバランス木だの、これでんがな。
型構築子の不動点を表す、
newtype Fix f = In (f (Fix f))
くらいは私も見たことがあったのですが、
完全バランス木を表す
data Fork a = Fork a a
data Perfect a = ZeroP a | SuccP (Perfect (Fork a))
しかし、 newtype ってまた。(^^;ガン○ム世代をくすぐるねぇ ちなみに Newtype 宣言とかいうやつで、やさしい Haskell 入門にはこうあります。
プログラミングの練習に共通していることは、表現が既存の型と同一であるが、 型システムのなかでは別ものであるような型を定義することです。 Haskell では newtype 宣言が既存の型から新しい型をつくりだします。
うーん。C の typedef みたいなもんかと思ったんだけど、ちょっと違う気も する。型システムの中では別物って書いてあるけど、それの意味するところが 理解できてないのが致命的なんだろうね。
まぁ、こいつを肴にひさかたぶりにHaskellをいぢいぢしてみるかい。のんび りね。のんびり。
まず、型構築子の不動点を表すとかいうやつです。
newtype Fix f = In (f (Fix f))
・・・(-"-) えーっと、久々なんでかなり忘れてる。やべぇ。復習、復習。あ、 そうだ。In がデータ構築子だね。 f がなんだかよう分からんが、 Fix は型 構築子になるんじゃないかな。あれ? もしかして Fix f っていう型かな。 IO () とか IO String みたいに f は a とか b なんかと同じ型変数だろうか。 だとすると、IO a みたいなもんだな。 Fix f ってのが型変数 f を内部に持 つ Fix 型と。んー、とりあえずそう考えてみるか。あんまり抽象的だとつい てけないから、具体的な型にしてみるか。
newtype Fix Int = In (Int (Fix Int))
これならどーよ。念のためにロードしてみよう。うりゃ!久々の一発!
Hugs session for:
/usr/pkg/share/hugs/lib/Prelude.hs
Type :? for help
Prelude> Reading file "/home/cut-sea/script/haskell/fix.lhs":
ERROR "/home/cut-sea/script/haskell/fix.lhs":12 - Illegal left hand side in datatype definition
Prelude>
うがぁっ!えーと、datetype 定義の左辺がだめだってか? やっぱ Int とかに したらいけないのか。
例えば、Scheme で
(define (g x) x)
を具体的な値にしたくて
(define (g 0) 0)
としたら、エラーになりますよね。要するに、Fix の仮引数 f まで Int にし てしまったのがエラーの原因です。
やるのであれば、
newtype Fix = In (Int Fix)
でしょうか。(ほかのエラーが出ると思いますが)
newtype Fix f = In (f (Fix f))
んじゃ、元に戻してみよう。C-cC-lだっ。
/usr/pkg/share/hugs/lib/Prelude.hs
/home/cut-sea/script/haskell/fix.lhs
Main>
うむ。しかし、どうやって使えばいいのかが分からん。(-_-; いやいや、落ち着け、オレ。例えば、
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show,Eq)
この Tree の例で言えば、Tree って型はデータ構築子の Leaf か Branch で 作られるんだったよね。これってLeafやBranchってタグが付けられるようなも んだとかって話があったな。それからすると、例えば Leaf 10 とかってして Tree 型のデータを作るみたく In (10 (Fix 10)) とかって感じか? でも (10 (Fix 10)) は何か変。型変数の名前の付け方が f ってところからして俺のこ と関数だと思ってよ〜 って電波をビシビシ感じます。ええ。そうすっと、例 えば・・・
Main> In ...
ん?んんんん〜?あれれ?なんか書けないぞ、コレ。なんでだ?(@[email protected];; えーっと。Tree なんかだとこうだよな。
Main> Leaf 10
Leaf 10
Main> :t (Leaf 10)
Leaf 10 :: Num a => Tree a
Main> Branch (Leaf 1) (Leaf 2)
Branch (Leaf 1) (Leaf 2)
Main> :t Branch (Leaf 1) (Leaf 2)
Branch (Leaf 1) (Leaf 2) :: Num a => Tree a
あー、そうか再帰的な型なのはいいんだけど、基底っていって良いのかなぁ、 Leaf みたいな、そういうデータ構築子がFixに無いからだ。こりゃ。
まあ、
Main> :t In Nothing In Nothing :: Fix Maybe
とか書けない事も無いですが、素直にここから例を引っ張ってきた方がいい と思います。
それでも不動点についてなんか無いかしらんと google に頼ってみると結構説 明してるのが多いな、うん。個人的には この説明 が結構一歩一歩説明してあっ て分かりやすいかも。 …いや、分かったような気にしてもらえるかも、って んで推奨。
Haskell に限らないみたいだが Schemeなんかでもふとしたキッカケでぶわーっ て不動点の話題が燃え上がることがあるようで。
なんだか fix って操作というか簡約の方向を、自分を呼び出すような仕掛け を作りこむのに使ってる感じがするわいな。不動点て聞くとどうも勘違いしや すいのよね。(私だけかい?) 検索中にもどっかで見かけたが、関数を与えたら 勝手に不動点を計算してくれる〜みたいなさ。 (^^;いやまさに私もそう思っ てましたって感じで、ははは。
さて、神の助言にしたがって入力してみる。 (実はその前に見てて fold/unfold の定義を見落としてた。はずい〜)
\begin{code}
newtype Fix f = In (f (Fix f))
out (In x) = x
fold f = f . fmap (fold f) . out
unfold f = In . fmap (unfold f) . f
data ListF a x = Nil | Cons a x
instance Functor (ListF a) where
fmap f Nil = Nil
fmap f (Cons a x) = Cons a (f x)
type List a = Fix (ListF a)
nil = In Nil
cons x xs = In (Cons x xs)
lengthFix = fold f where
f Nil = 0
f (Cons _ n) = n + 1
\end{code}
length はそのまま使うとmultiple define だって叱られるんで、とりあえず、 lengthFix としてみる。
…うぅ、あまりにも初めて見る方々が多いので、ちょっとキビシイ。(–; instanceだのFunctorだのぞくぞく新キャラ登場。
まぁ、いろいろ考えた末に一番最後の式から見ていくことにするわけさ。
lengthFix = fold f where
f Nil = 0
f (Cons _ n) = n + 1
これはいいねぇ。すっごい抽象的にだけど、イメージは分かる。「lengthFix リストっぽいもの」が「fold f リストっぽいもの」って感じで使われるんじゃ ないかしらん。で、whereってのは、まぁこの文脈でfはこれこれこういうモノ だよってことだね。つまり、fはNilなら0を返して、(Cons _ n)ならnに1を足 すという関数だと。 あっしゃ大域的に名乗るほどの者じゃございやせんとかっ てfを局所的に定義しているようなもんだ。
んん?「リストっぽいもの」を普通のリストだと考えると、ちょっと引っかか るぞ。 Consってデータ構築子だよね。
data ListF a x = Nil | Cons a x
うん、そうだよね。 ListF って型はNilか、もしくは型変数 a x に Cons タ グ付けたものだとするんだけど、もしかしたら a と x は別の型なのかもしれ ない。んー、ってかそうだって書いてあるじゃん。これは変数じゃなくて型変 数なんだから Cons a a じゃなくて Cons a x って書いてあるってことはやっ ぱり別の型なんだよっ。(^^; いや、まぁScheme?のリストでも確かに違うっちゃ違うけどさ。なんか驚いちゃった。
こういう使われ方をするってことは、(Cons _ n)の n には整数が来て、 _ な ワイルドカードにはリストを構成する要素みたいなもんが来るということです な。 Scheme?っぽいリスト表現で書くと、
'(a b c d e f g) => (a b c d e f g ())
=> (a b c d e f g 0)
=> (a b c d e f 1)
=> (a b c d e 2)
=> (a b c d 3)
=> (a b c 4)
=> (a b 5)
=> (a 6)
=> (7)
みたいな感じの計算になるんじゃないか?多分。あくまでだいたいね。そうす るとなんか fold って感じしません? 次にぽーーんっと飛ぶけど、
out (In x) = x
fold f = f . fmap (fold f) . out
これと、lengthFixの例を見ると、
lengthFix == fold f
lengthFix リストっぽいもの == fold f リストっぽいもの
== f . fmap (fold f) . out リストっぽいもの
~~~~~~~~~~~~~~~~~~~~~~~~
ってなるから、なんとなーくだけど リストっぽいもの == In x で表現される ものって感じじゃないかな。なんて軽く言っちゃったけどfoldの定義が再帰構 造になってるわ、 fmapもあるわで、ちとツライかも。(TヮT)アハハ こーゆー自分の頭を越えたかな〜って時は素直に参りましたm(__)m ってワビ入れてから、具体的な値で計算を追ってみる作戦を採用する。一番簡 単なのはNilとかだよね。こいつはListF型だな。こいつとInていうデータ構築 子とがどう絡んでくるのがはっきりしないんだけど、
instance Functor (ListF a) where
fmap f Nil = Nil
fmap f (Cons a x) = Cons a (f x)
このfmap f Nilって使い方が取っ掛かりになるかな〜? lengthFix は fold f で、 fold f は f. fmap (fold f) . out だよね。ってことは、In xって感じ で作られた「リストっぽいもの」にoutを適用して得られた xがfmap (fold f) x って感じに適用される訳だな。ってことはxがListF型らしい気がする。そー 思いません?
じゃ、そうだと仮定してNilをInなデータ構築子に渡してみよう。タグ付けさ れるイメージで考えると、In Nil なデータになるね。 そーすっとー、こうだっ!
lengthFix (In Nil) => fold f (In Nil) -- lengthFix の定義
=> (f . fmap (fold f) . out) (In Nil) -- fold の定義
=> (f . fmap (fold f)) Nil -- 適用
=> f (fmap (fold f) Nil) -- 式変形
=> f Nil -- ListF a に対するFunctorのメソッド?fmap の定義から
=> 0 -- lengthFixのwhere部から
おぉおぉぉぉっ!正解くせぇぞ。 (ちなみに上のfが“大域的に名乗るほどのものじゃございやせん”なfなんで一 応確認ね) んじゃ調子に乗って、も少しリストっぽいものを作って動作を追っ てみようか。
………………………………………………………………あ。(‾□‾;)
とんでもないことに気付いちゃった。 Fixにちっとも触れてないよ。(–; さらにこの後でCons使った例をやっても、なんだか触ること無しに終わりそう な予感がするんだけど気のせいか?
えーと、気づかなかったことにして、とりあえず追うよ。
Cons "Hello" Nil
とかしてもいいのかな?えーっと、
instance Functor (ListF a) where
fmap f Nil = Nil
fmap f (Cons a x) = Cons a (f x)
これからすると、Cons a x の x は f x って感じで f に適用されるんだから、 そういう型じゃなきゃダメだね。
lengthFix (In Nil) => fold f (In (Cons ? ??))
=> (f . fmap (fold f) . out) (In (Cons ? ??))
=> (f . fmap (fold f)) (Cons ? ??)
=> f . fmap (fold f) (Cons ? ??)
=> f . Cons ? (fold f ??)
なんだから ?? つまり Cons a x の x は、fold f に適用可能なものじゃなきゃ ダメだ。
fold f = f . fmap (fold f) . out
だから、 ?? は In x のように In データ構築子で生成されるものなんだ。
Cons ? (In Nil)
とかが Nil の次に簡単な「リストっぽいもの」候補かな。ちなみに ? は結局 値を必要とされないので、多分なんだっていけそうな気がする。 Cons データ 構築子で作られたものはListF型なので、こいつを In で包めば、また別の Cons の cdr (か?)にすることができるね。
In (Cons [1..] (In (Cons [0..] (In Nil))))
こんなんでどうでしょ。なんかとりあえずリストっぽい感じがせんでもないん じゃない?こいつをlengthFixに適用させてやるとどうなるかシミュレートし てみる。
lengthFix (In (Cons [1..] (In (Cons [0..] (In Nil)))))
=> fold f (In (Cons [1..] (In (Cons [0..] (In Nil)))))
=> (f . fmap (fold f) . out) (In (Cons [1..] (In (Cons [0..] (In Nil)))))
=> (f . fmap (fold f)) (Cons [1..] (In (Cons [0..] (In Nil))))
=> f . fmap (fold f) (Cons [1..] (In (Cons [0..] (In Nil))))
=> f . (Cons [1..] (fold f (In (Cons [0..] (In Nil)))))
=> f . (Cons [1..] ((f . fmap (fold f) . out) (In (Cons [0..] (In Nil)))))
=> f . (Cons [1..] ((f . fmap (fold f)) (Cons [0..] (In Nil))))
=> f . (Cons [1..] (f . fmap (fold f) (Cons [0..] (In Nil))))
=> f . (Cons [1..] (f . (Cons [0..] (fold f (In Nil)))))
ふぅ。 あ、くどいけど、このfは例の大域的に名乗るほどの者じゃありやせんのfです よん。 (最初にミスってるなぁ。fold f の f を g とかにしとけばよかった んじゃん) だから、(fold f (In Nil))はさっきやったばっかりのやつね。結 果は 0。
=> f . (Cons [1..] (f . (Cons [0..] 0)))
ようやくlengthFixのfの一般条件が使える。
lengthFix = fold f where
f Nil = 0
f (Cons _ n) = n + 1 -- こいつね。
っちゅーわけで、ゴールまで一直線。
=> f . (Cons [1..] (f . (Cons [0..] 0)))
=> f . (Cons [1..] (0 + 1))
=> ((0 + 1) + 1)
=> 2
ほっ。なんかよさそうだけど釈然としねぇ。なんで In out を使ったのか、な んで Fix が必要だったのか。再帰の原動力はもともと不動点型にあると予想 してたんだけど、今は fold f の定義がそうじゃん、とか思い始めてるし。
再掲。
\begin{code}
newtype Fix f = In (f (Fix f))
out (In x) = x
fold f = f . fmap (fold f) . out
unfold f = In . fmap (unfold f) . f
data ListF a x = Nil | Cons a x
instance Functor (ListF a) where
fmap f Nil = Nil
fmap f (Cons a x) = Cons a (f x)
type List a = Fix (ListF a)
nil = In Nil
cons x xs = In (Cons x xs)
lengthFix = fold f where
f Nil = 0
f (Cons _ n) = n + 1
\end{code}
見返すと、他にも使ってないものがある。 nilとconsだ。もしかしたら、直接 NilやConsやInを使うのが間違ってんのか?
今ごろ気付くとは、不覚!
In (Cons [1..] (In (Cons [0..] (In Nil)))) == (cons [1..] (cons [0..] nil))
おわっ!すげぇ!たったこんだけの事だけど、全然違って見えるわ。こりゃ。 ふーん。なるほどなるほど。
いや、こんなん感心しててもしょうがない。本丸はこっちなんだよな。
newtype Fix f = In (f (Fix f))
うーっ、なんか分かるような分からんような手が届かない感じでキモチ悪い。 fold fの定義の再帰性はlengthを求めるアルゴリズムの再帰性の元で、 Fix f の再帰性は、なんだか「リストに何かをconsしたものもまたリストになる」っ ていうそういう再帰構造の源なんだろうか。再びゴーストがささやいてるんだ けど。(なにせ信用ないからなぁ(–;;)
すっきりしないのは多分 newtype Foo = … な説明は入門にもあるんだけど、 newtype Fix f = となった時のことが分かってないからか。ふむ。
えーっと、どっか別の場所にないかいな。やっぱり、構文リファレンスかなぁ。
topdecls -> topdecl1 ; ... ; topdecln (n>=0)
topdecl -> type simpletype = type
| data [context =>] simpletype = constrs [deriving]
| newtype [context =>] simpletype = newconstr [deriving] -- これね
| class [scontext =>] tycls tyvar [where cdecls]
| instance [scontext =>] qtycls inst [where idecls]
| default (type1 , ... , typen) (n>=0)
| decl
このnewtype宣言から始まって、今のケースではcontextは無いから、 simpletypeがFix fだ。
simpletype -> tycon tyvar1 ... tyvark (k>=0)
というわけでFixが型構築子でfが型変数。うーん。合ってるなぁ。
tyvar -> varid (type variables)
tycon -> conid (type constructors)
ほれ、この通り。 (f (Fix f))みたく使われているんだからやっぱり型変数f は関数型のデータなんだよね。なんも新しい発見が無かった。orz
newtype Fix f = In (f (Fix f))
以前確認した通り、左辺のFixは型構築子で右辺のInはデータ構築子で、右辺 のFixは型構築子だ。ここで、ちょっと視点を変えてっていうか戻して、
(cons [1..] (cons [0..] nil))
こいつと見比べてみよう。 fって型変数によって代表されるのはnilとか(cons [1..])のような関数になる のかな。実際nilは無引数の関数で(cons [1..])は一引数の関数だ。 nilを (nil (Fix f))って感じで適用されることはないけど、 (Fix nil)ってのはア リアリなんだよね。(あ、もちろん意味的にってことであって実際の式じゃな いです、キモチの話ね) つまり、Fix fは nil とか Fix f にcons ??な関数を適用した結果できるもの だと。(なお、cons とか nil とかの場合にはInは含まれているのに注意) おおぉ、全てがかみ合ってきたぞい!?もしかしてようやくたどり着いたか? (どこに!?)
そうすると、なんか新しい視点が開けた感じだな。
(cons 1 (cons 2 (cons 3 ())))
~~
↑
()は値→値はthunk→thunkは無引数の関数→()は関数の仲間だよね
とみなすことから始まって、
(cons 1 (cons 2 (cons 3 ())))
~~~~~~~
↑
cons 3 をcurry化して関数とみなす
Scheme なら (cut cons 3 <>) なキモチ
てな風にする。()がfでcons ??もfの仲間。生成されるリストってやつがFix f で表現されるもの。つまり、リストにfを適用したものがリストになる。くど いけど、()またはリストにcons ??を適用した結果できるのはリスト。そう見 るとリストって不動点型ってわけだね。
ではでは、実機確認!!
Main> lengthFix nil
0
Main> lengthFix (cons 1 (cons 2 (cons 3 nil)))
3
Main> lengthFix (cons [1..] (cons [0..] nil))
2
へっへっへー!やったネ!!(^^)v
そうすると配列も同じように不動点型として定義できそうな気がするぞ。配列 に (++) すると配列になる、とかね。この場合は f は ((++) [1..]) みたい な感じでさ。 nilに相当するのは当然[]だろう。おおっ、全体を書く根性も能 力もまだまだないけどなんかできそうじゃん。
ありゃ?そうすっと整数も整数に足し算(や引き算)をすることで整数になる わけだから、これも不動点型か?おいおい(^^;)
えーっと、ちょびっとだけチャレンジ。
lengthFix = fold f where
f Nil = 0
f (Cons _ n) = n + 1
consFix = fold f where
f Nil = []
f (Cons x xs) = x:xs
strAppendFix = fold f where
f Nil = ""
f (Cons x xs) = x ++ xs
intFix = fold f where
f Nil = 0
f (Cons x y) = x + y
なんだかconsFixってのは変な名前だけど、
Main> consFix (cons 1 (cons 2 (cons 3 nil)))
[1,2,3]
Main> strAppendFix (cons "Hello" (cons "," (cons "World" (cons "!" nil))))
"Hello,World!"
Main> intFix (cons 10 (cons 20 nil))
30
残念ながらconsFixの場合には[]がshow出来ないのでエラーでちゃうけどね。(^^; ま、あとは全く同じConsの定義をplusとかstring-appendみたく、いい名前に 付け直してやれば、それっぽくなるんじゃない?
「fって型変数によって代表されるのはnilとか(cons [1..])のような関数」で はありません。
type List a = Fix (ListF a)
この定義を見れば分かりますが、f はこの場合 (ListF a) です。また、以前出した例
Main> :t In Nothing In Nothing :: Fix Maybe
では、f は Maybe です。
つまり、f には、(ListF a) や Maybe のような「型を受け取って新たな型を 返す関数(のようなもの)」が入ります。正確に言うと、f には、類 ‘*→*’ を 持つ型が入る事になります。 (入門の「類」の説明を参照)
あと、lengthFix, strAppendFix, intFix は、length, concat, sum の [a]を List aに置き換えたものですし、consFix は List a->[a] のコンバータなの で、不動点型を定義しているわけではありません。
そもそも、型の不動点のキモは、
data List a = Nil | Cons a (List a)
のような再帰的な型を、再帰を使わずに
data ListF a x = Nil | Cons a x type List a = Fix (ListF a)
のように定義できる、という所にあります。
る、るる、類(kind)って?
えっと、まず神の示された類っちゅー単語に意識集中して 入門を読みませう。
読みましたか?私は以前も読んでたハズなんだけど、こんな内容だったっけ?っ て位初めて読んだ感じ。(–;; いや、以前は読めてなかったんでしょう。実は先日来CLOS系のお勉強なんぞも やってて、その副作用かもしれないけど、類が出てくるまでの前半部分は結構 すんなり頭に入ってきました。はい。総称関数とメソッドの関係みたいなのが ここにもあるなーとか思っちゃいましたよ。 少なくとも類が出てくるところまではね。
んじゃ、どこで躓いたかっていうと…
ここまで、「第一級の」型を使ってきました。
つまり、型構築子 Tree はこれまで常にひとつの引数と組になっていました。
たとえば、 Tree Integer ( Integer 型の値を含む木)、
あるいは、 Tree a ( a 型の値を含む木の族をあらわす)です。
しかし、Tree それ自身は型構築子であり、ひとつの型を引数としてとり、
ひとつの型を返します。Haskell にはこの種の値はありませんが、
このような「高階の」型はクラス宣言のなかで使用することができます。
型クラスと多重定義からの引用ですが、この中の最初の文ね。
あれぇ?確か型は第一級じゃないよって言ってなかったか?と思ったわけです。
Haskell の値はすべて「第一級の対象」です。
すなわち、関数の引数、関数の返り値、になり得ます。
また、データ構造などのなかに入れることもできます。
一方、Haskell の型は「第一級の対象」ではありません。
あ、コレコレ。 値、型その他の有用な概念 にきっちりうたっとりまんがな。 (-"-)うーっむ。
「値、型その他の有用な概念」に出てくる「第一級の対象」と「型クラスと多重定義」に出てくる「第一級の」とは別物です。
「値、型その他の有用な概念」に出てくる「第一級の対象」は ‘first-class’ の訳で、「型クラスと多重定義」に出てくる「第一級の」は ‘first-order’ の訳です。
個人的には、‘first-order’ は「一階の」と訳すべきだと思います。
- 御指摘の通りこれは明らかな誤訳です.修正しました.– nobsun (2005/03/25 09:05:47 JST)
なるほど、確かにfirst-orderになってました。という訳で、訳文訂正のフィー ドバックをかけておいて先へ進みましょう。
そうすると、型構築子のうち、「型を引数に取って型を返す」という「高階の 型」なんちゅうもんが出てきちゃいました。 高階って聞くと なんだかまたすごいことが記述できちゃうのか? ってワクワ クしませんか?そー思うワタシはビョーキですかね。。。(^^;;; 高階関数なんてもんが使えるようになった時、ちょっとの違いが大違い、そ りゃーもーキノコ食ったヒゲおじさんくらいパワーアップしたの覚えてますよ。
ここでもどうやら
こうした能力は大変便利なものです。
これは総称的「コンテナ」型を記述する能力のあることを示し、
fmap のような、任意の木、リストあるいは他の型の上で
統一的に動作する関数を可能にします。
と、なにやら魅惑的な誘い文句が書かれとります。魅惑的ってのは○○を記述 する能力のあるって文言のことです。もうLLプログラマにとっては垂涎ものの 殺し文句じゃないかい?
しかも驚いたことに、すでにこういう型構築子を扱ってたんだとさ。
[型適用は関数適用とおなじやりかたで書きます。
型 T a b は (T a) b のように構文解析されます。
タプルのような特別な構文を使う型はカリー化可能な別のスタイルで書きます。
関数については、 (->) が型構築子です。
f -> g と (->) f g とは同じです。同様に、[a] と [] a とは同じです。
タプルについては、型構築子(同時にデータ構築子でもありますが)
は (,)、(,,) などとなります。]
ということで -> とか [] がまさにそうなんですって。確かに f -> g を (->) f g とか書くと分かるような気もする。
んで、類ってなんじゃ?というと、
型式は別々の類に分類されます。これは次の 2 つの形式をとります。
+ 記号 * は具体的なデータ対象に結びついた型の類をあらわす。
すなわち、 もし、値 v の型が t であるなら、v の類は * で なければならない。
+ もし、k1 と k2 とが類ならば、k1->k2 は k1 という類の型をとり、
k2 という類の型を返す型の類である。
型構築子 Tree の類は、 *->* であり、Tree Int の類は * です。
Functor クラスのメンバーはすべて、類 *->* でなければならず、
類付けのエラーは、次のような宣言により引き起こされます。
…む、ムツカシイ(-"-; わざとやってねぇか?Haskell!? えっと、なんかの型があるとして、そいつが具体的なデータの型をあらわして るなら、類*で、型を引数に取って型を返すような型の場合には*->*って感じ で高階の型って扱いになると。 Treeの例を見ると分かるような気にさせられ るんだけど。。。通常の(曖昧な言葉だなぁ)型の類は*で高階の型の場合には *->*とか*->*->*とかって思っていいのかな。
もっかい神の助言を読み返すか。
つまり、f には、(ListF a) や Maybe のような
「型を受け取って新たな型を返す関数(のようなもの)」が入ります。
正確に言うと、f には、類 '*→*' を持つ型が入る事になります。
んー、ってことはfはなんらかの関数型が来るってことじゃねーかい?いや、 形からしてさ、*->*ってのがいかにもそんな感じ。つまりfのとこにはただの 型じゃなくてなんか関数型に分類されるものが来ると。
data List a = Nil | Cons a (List a)
のような再帰的な型を、再帰を使わずに
data ListF a x = Nil | Cons a x
type List a = Fix (ListF a)
のように定義できる
ってのが型の不動点のキモだってことなんだけど、この関係だけ確認しておこ う。
MyEqクラスってヤツ
えー、確認しておこう。なーんつってナレーション風に書いておいて放置プレ イすること7ヵ月。 長き沈黙をやぶり、今再びはじめちゃおうかな。
気づけば*.lhsなファイルをEmacsに読み込んでもhaskell-modeにならないわ、 C-cC-lしてもマトモに評価できないわ。 完全に忘れてしまってて足腰がなまってしまってたんで、よちよち歩きがます ますぐずぐずに。そりゃもう狂牛病になった牛みてーな状態。
世の中には1ヵ月でHaskellを学んで、次の1ヵ月でPerl6を実装してしまうっつー 変態もいるっちゅーのにね。(-,-;;
まぁ雑談はほどほどにして早速書いてみる。
\begin{code}
class MyEq a where
(===) :: a->a->Bool
instance MyEq Integer where
a === b = a == b
data Foo = Foo Integer String
instance MyEq Foo where
Foo x _ === Foo y _ = x === y
\end{code}
実はね、クラスとか型とかが難しいYo!って愚痴ったら示してくれたのが上の 様なコード。実際に自分で書き下ろしてちゃんとHugsにロードできる様になる までに四苦八苦したよ。
でもデバッグする際にあらためて ここを読んでたら、まんま書いてあんじゃん。
毎度のことながら読んで分からない文言てのはまるで記憶に残らないもんだねぇ としみじみ。
まぁ、7ヵ月の休養の結果、なぜかちょびっとだけ分かるようになった、こい つを餌に再スタートを切ってみよう。いっくぽーーーーっん!!
同じとは?
例えば仮に彼女がキュートな笑顔で
「りんごとバナナは同じだよね」
なーんて目頭がアツクなるほどイタイことをのたまったとしよう。
いや、イタイと思わずに「そうそう!俺もそー思うYo!」とかって逝ってしまう人もいるかもしれん。でもさ、イタイかどうかは同じって言葉の意味というか定義によるわけよね。
でも物凄く不思議ではあるんだが、それ以前に同じって言葉の定義ではなく、 同じという言葉の出現する文脈ってのは共通したものとして存在している。それは
「○○と××は同じか否か?」 「同じだ」もしくは「違うよ」
という、なんつーの?二つの対象をならべて、真偽を問うという体系のようなもの。
別の例を挙げてみる。
「▲▲君ってジャイアント馬場よりデッカイよね」
という、これまたビミョーな発言があったとしよう。これは身長を比較してい るのか、心の広さを比較しているのか、はたまたナニを比較しているのか分か らんが、それでもデカイか?という言葉が出現する文脈において共通するもの があるわけやね。
「○○と××ではどちらがデカイか」 「そりゃ○○よ」とか「××なんじゃん?」とか「一緒だよ」
などですな。 あるいは、もちっと高度な応用をきかして
「○○と××と△△と□□ではどれが一番でっかい?」 「△△かな」とか「☆☆はでかいぞ」とか「しらね」とか。
まぁ、とりあえず「同じ」の例でコードを組んだので、そっちで考えてみるこ とにしよう。
class MyEq a where
(===) :: a->a->Bool
これはまさに「同じ」の定義ではなく「同じ」という言葉が使われる文脈を決 めているんじゃないかと思うわけよ。実際、
a(○○)とa(××)は同じ?->Bool(同じだ/違うよ)
って感じに見えるよね? 少なくとも
「りんごと同じがバナナ?」
なーんて世界が真っ白になるほどブッ飛んだ発言は同じという概念の枠には無 いんだよね。先程の彼女のイタイ発言もここまではイタクないわけだが、それ はa->a->Boolには当てはまっているからでしょうね。 だから辛抱強い人だったら「その同じってどういう意味でいってんの?」と定 義をもとめるワケですな。 でもって、
instance MyEq Integer where
a === b = a == b
これがIntegerについてMyEqという同じの概念を規定した時の定義だと思う。 多分ね。ここでIntegerについて語る時には同じってのはこういうことだ!っ てー感じの判断基準を与えておるようだ。
つまり「フルーツだから同じ?→YES」なのか「果物の種類として同じ→NO」 なのか、あるいは「このりんごとあのりんごは同じ?」の場合でも「色が違う」 「品種が違う」「産地が違う」「糖度が違う」「存在している位置座標が違う」 などなど、そういう定義がInteger上でされたりNumber上でされたり、まぁそ んな感じなんでしょう。
だから
data Foo = Foo Integer String
instance MyEq Foo where
Foo x _ === Foo y _ = x === y
ってすれば、Fooってタグのついたデータ構造に対しても同じかどうかを議論 することが出来るんですな。 ここではFooには2つのスロット値というかメンバデータとでもいうものがあっ て、例えばこれが年齢と名前だとすると、年齢が同じなら同じとしようよって ことでしょう。 Foo 34 “cut-sea”もFoo 34 “羽生善治”も「同じ」なんですよ、誰がなんとい おうとさ。いや、同じなのです!ソコ!妙なツッコミいれないよーに。
Hugsで評価してみると、こんな感じ。
Main> Foo 1 "hoge" === Foo 1 "foo"
True
うむ。似たようなのをも一個。
data Fluit = Fluit String
instance MyEq Fluit where
Fluit x === Fluit y = True
これはFluitなタグさえついてりゃ同じは同じですね。
Main> Fluit "Apple" === Fluit "Banana"
True
よし、少しずつ調子が出て来た。 どうせxとかyの部分を無視させるんだったら、これはどうよ?
data Fluit = Fluit a <= ここをStringからaにしてみた
instance MyEq Fluit where
Fluit x === Fluit y = True
うりゃ!(C-cC-l)
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":21 - Undefined type variable "a"
Prelude>
あうっ!!(久々に食らってちょっとカンドー)
値は無視できても型は無視できないよ.
instance Eq Fruit where
(==) = const . const True
でどう?
って浸っててもショーガネーってことで、Treeとかの定義を再度見てみる。ははぁ。
data Fluit a = Fluit a
instance MyEq Fluit where
Fluit x === Fluit y = True
えいや!
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":23 - Illegal type in class constraint
むむ?えーっとあてずっぽだけど、23行目ってのは手元では
instance MyEq Fluit where
この行だ。っちゅーワケだから、
data Fluit a = Fluit a
instance MyEq (Fluit a) where
Fluit x === Fluit y = True
こうしてみる。いや全然根拠無いし分かっちゃいないが、これでMainプロンプ トがオメミエ。うーん。なんでじゃろ?(^^; まぁとりあえず使ってみるなり。
Main> Fluit "APPLE" === Fluit "BANANA"
True
Main> Fluit 1 === Fluit 3
True
当然次なる疑問は、
data Foo = Foo Integer
と
data Foo Integer = Foo Integer
とはどう違うんぞなもし?という点なんだが、そもそも下の定義は可能なんだ ろうか?型変数aで出来るのはわかってるんだが。 Hugsにきいてみよう。
data Bar Integer = Bar Integer
んでエバってみると…
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":29 - Illegal left hand side in datatype definition
Prelude>
うーむ。なんか予感は当たったんだが、単にIllegal left hand side in datatype definitionだって。もちっと噛み砕いた説明くれーーー!! まぁSyntax上正しくないってことだろうが、なぜ書けない?
んじゃaを出しておきながら使わないとどうなる?
data Bar a = Bar Integer
…いけますなぁ。んじゃあ全く違ったらどうよ。
data Bar a = Bar b
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":26 - Undefined type variable "b"
ふむ。 規則は分かる。左辺は今から多分定義しようとしている何者かなので、 (型) 変数がくるべき。右辺は定義済み、もしくは今定義しようとしている部分に無 いとダメ。 ありゃ?当り前なのか?
じゃあ単に
data Foo = Foo Integer
と
data Foo a = Foo Integer
の違いはFooを(Foo a)としただけのこと?後者の何が嬉しいかっていわれたら 多分これだと嬉しくない。
data Foo a = Foo a
ってするとさっき試した時みたいにa部分に色々取り得てウレシイってことかな。
左辺の型変数ってやつがどうも何者なのか分かりにくいのでも少しいじってみる。
data Hoge a a = Hoge a a
これはどうさ?
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":26 - Repeated type variable "a" on left hand side
うーんくりかえし出ちゃダメってか。当然これはイけるんだよね?
data Hoge a b = Hoge a a
うん。ちとマニュアルを読んでみるか。
左辺の型変数については、関数定義の仮引数と同じように考えればいいのではないでしょうか。
たとえば、型 x -> y -> x の関数 hoge の定義
hoge a b = a
と、類 * -> * -> * の型 Hoge の定義
data Hoge a b = Hoge a a
を比較すれば、左辺の変数の意味がわかりやすいと思います。
同様に、型定義を関数定義と置き換えて考えれば、なぜエラーが起こるかもはっ きりすると思います。
なんと!?そうかー
data Hoge a b ...
とあったときに、a/bってのは位置が問題なのではなく、Hogeという型では2 種類の型変数を使うよってことだけが重要なのかな?つまり第一引数(?)の型 変数とか第二引数の型変数とかっていう位置を表すシンボル(の型) じゃなく て、単にHogeってのは二種類の型をつかうって定義されるものだよ、と。
そういや、データを構築するのは右辺の定義なんだから、引数の位置ってか何 番目の引数ってのは重要だろうけど、左辺はそもそも関係なさげだわなぁ。
ってことは、
data Hoge = ...
なHogeって型は他の型には一切依存しない、言い方を変えると、他の型の存在 を一切仮定せず定義される型なんだね。
んだらば一応確認しておこう。
data Fuga = Fuga [String]
instance MyEq Fuga where
Fuga s1 === Fuga s2 = length s1 == length s2
あう。訂正…「他の型には一切依存しない」じゃなくて「他の変数な型には一 切依存しない」か。でもそもそも左辺には型変数しかないわけで、Integerみ たいな型が書けるわけじゃないもんねぇ。なんかもって回った書き方になるけ ど、うーん、拘るのはヤメっか。
data FugaS a = FugaS [a]
instance MyEq (FugaS a) where
FugaS s1 === FugaS s2 = length s1 == length s2
でもって評価すると、もうバッチリ。いや、めちゃめちゃ簡単な例なんだけど さ、分かって書いてる感があるってのが進歩なわけよ。はい。
Main> Fuga [] === Fuga []
True
Main> Fuga ["", "", ""] === Fuga ["abc", "xyz", "123"]
True
Main> Fuga ["", "", ""] === Fuga ["abc", "xyz"]
False
Main> FugaS [] === FugaS []
True
Main> FugaS ["", "", ""] === FugaS ["abc", "xyz"]
False
Main> FugaS [1, 2, 3] === FugaS [12, 23, 34]
True
Main> FugaS ['a','b','c'] === FugaS "ABCDEF"
False
やったね!! ちょっとだけ開眼(した気になってる)。
じゃあ引続き、今度はtypeだかnewtypeだかを学びませう。
既に理解されているとは思いますが、念のため追加で説明します。
まず、型定義の左辺に変数が許されない場合にどうなるかを考えます。そうす ると、Integer の木が必要な場合、String の木が必要な場合で、別々の定義 を書かなければなりません。
data IntegerTree = IntegerLeaf Integer | IntegerBranch IntegerTree IntegerTree data StringTree = StringLeaf String | StringBranch StringTree StringTree
別の型の木を作るたびに定義し直すのは面倒なので、好きな型を渡せば、その 型の木を作ってくれるような関数を考えます。
data Tree a = Leaf a | Branch (Tree a) (Tree a)
そうすれば、いちいち使う型ごとに定義し直さなくても、Integer の木が欲し ければ (Tree Integer) を、 String の木が欲しければ (Tree String) を使 えば良くなります。
型定義の左辺の変数は、後々ユーザが好きな型を渡すための受け口の役割を果 たします。これは関数の仮引数の役割と同じですよね。
まずtype!
type String = [Char]
type Person = (Name,Address)
type Name = String
うははっ、もらい。これは簡単じゃね。単に別名として使えるようにしてんだ ろう。つまり、[Char]って書く代わりにStringって使えるだけだ。 Cでいうと ころのtypedefだろう。
ではそそくさとnewtypeへいっちゃおう。例の初心者にやさしくない入門によれば、
表現が既存の型と同一だが,型システムの中では別の型として識別されるよう な型を定義
だそうだから、さっきのtypeとは別の型として識別される点で違うんだろう。
さっきから「だろう」ばっかしでちゃんと確認してないので、少しHugsに聞い て確認してみよう。直観的にはCLOSのclass-ofみたいなのがあればいいんだけ ど、なんかHaskellには無いような気が…。
type Name = String
data Foo = Foo Name deriving Show
んで動作確認はこれ。
Main> Foo "My Name"
Foo "My Name"
おっけー。じゃあ間違ってみよう。(なんだこの表現は)
Main> Foo 123
ERROR - Unresolved overloading
*** Type : Num [Char] => Foo
*** Expression : Foo 123
どーよ?ん?ちゃんと[Char]だと言ってる。つまりあくまで推測だけど、Nameっ て読み込んだ時点で[Char]に置き換わってんじゃないかな。内部的にはもう区 別つかないんだろう。多分。 Stringってのも結局置き換わって[Char]になっ ちゃうわけだしさ。
そいでは次newtypeでやってみる。
newtype Namex = MakeNamex String
data Bar = Bar Namex deriving Show
んでエバって…
Main> :load /home/cut-sea/script/haskell/class.lhs
Reading file "/home/cut-sea/script/haskell/class.lhs":
ERROR "/home/cut-sea/script/haskell/class.lhs":42 - An instance of Show Namex is required to derive Show Bar
あらら。だめじゃん。 deriveがダメとか言ってやがんのかな?
newtype Namex = MakeNamex String
data Bar = Bar Namex
これなら確かにMainプロンプトがでてくれる。ん?んん?…もしかしてこれ自 体が証拠か???? つまりさっきのtypeの時にはNameがShowなStringそのものだから文句も言われ なかったと。
で、Namexは全く新しい型だから、まずBarをShowするには NamexをShowを deriveしてやらんとだめだよと。
newtype Namex = MakeNamex String deriving Show
data Bar = Bar Namex deriving Show
おおっ!!Main出現!!やたっ!!すばらしい。 では動作確認。
Main> Bar "My New Name"
ERROR - Type error in application
*** Expression : Bar "My New Name"
*** Term : "My New Name"
*** Type : String
*** Does not match : Namex
はうっ。。。 えーっとマニュアルマニュアル…。あ、そういうこと。。。
Main> MakeNamex "My New Name"
MakeNamex "My New Name"
よし。
Main> Bar MakeNamex "My New Name"
ERROR - Type error in application
*** Expression : Bar MakeNamex "My New Name"
*** Term : Bar
*** Type : Namex -> Bar
*** Does not match : a -> b -> c
あうっ。もしかしてこう?
Main> Bar (MakeNamex "My New Name")
Bar (MakeNamex "My New Name")
おおーっ。なんだかnewtype使う意味がワカンネー。 ま、いーや。とりあえず、MakeNamexってのがNamexな新しい型のコンストラク タっぽいもんなわけね。いいです。そういうことで、今日のところは。ハイ。
あ、ゴメンゴメン。忘れるとこだった。さっきのエラーの、Typeを見てみると [Char] -> Bar じゃないとこがnewtypeですな。
*** Type : Namex -> Bar
おそらくね〜。
まだ不動点はムリ
えーっと後ろを振り返るとやっぱ型の不動点は心残りなんだが、どうも今見て も理解できん。
んで、何が自分で分かんないのか突き詰めるとこれだ。
newtype Namex = MakeNamex String deriving Show
type Name = String
data Foo = Foo Name deriving Show
instance MyEq Fuga where
Fuga s1 === Fuga s2 = length s1 == length s2
みたいにNamexやNameやFooやMyEqとかってシンボルが出てくると、これらのシ ンボルについて定義してるっつーか宣言してるっつーか、そういうのが分かる んだが、
newtype Fix f = In (f (Fix f))
~~~~~~
type List a = Fix (ListF a)
~~~~~~
data ListF a x = Nil | Cons a x
~~~~~~~~~~
instance Functor (ListF a) where
~~~~~~~~~~~~~~~~~
fmap f Nil = Nil
fmap f (Cons a x) = Cons a (f x)
とかって出てくると、これらは何について定義してるのか良く分かんないんす。 はい。ちょっと探したとこだとclassではそういうの見当たんなかったけどさ。
たとえばSchemeだと
(define (f x y) (* x y))
ってのが出て来ても、
(define f (lambda (x y) (* x y)))
と同じことだって言われると、なーんだーって分かる気がする。 (んじゃない かと思う。今となってはScheme?知らない人の気持ちがわからんけど) ま、まま、それと同じように、
data ListF a x = Nil | Cons a x
これは
data ListF a = ???
と同じだよーとか、
instance Functor (ListF a) where
これは
instance Functor ListF where ????
と同じなんだよってのがあると分かるかもしれんとかナンクセをつけてみたりね。
ListFを直接そのように定義する事は(多分)できないのですが、このような説 明はどうでしょうか。 (ただ、私の場合、「やさしいHaskell入門」をすべて読み終えた後でも、型の 不動点の話を理解するのに苦労したので、どうしても分からないようなら無視 して別の内容を学ぶ事をお勧めします。)
型を値の集合(リスト)と考えます。そうすると、整数型と文字型は以下のよう に表せます。 (要素を全部書くと煩雑なので一部だけにしてあります)
int = [1, 2] char = ['x', 'y']
ここで、この型を扱う関数として、以下の2つのものを考えます。まず、それ ぞれの要素を一つずつとって、組み合わせた新しい型を生成するdprod。
dprod a b = [(x, y) | x <- a, y <- b]
intとcharに適用すると以下のようになります。
*Main> dprod int char [(1,'x'),(1,'y'),(2,'x'),(2,'y')]
もう一つは、それぞれの要素をすべて含んだ新しい型を生成するdsum。ただし、 もともとどちらの要素であったかをはっきりさせるために、片方の要素の前に Left、もう片方の要素の前にRightをつけます。
dsum a b = [Left x | x <- a] ++ [Right y | y <- b]
intとcharに適用すると以下のようになります。
*Main> dsum int char [Left 1,Left 2,Right 'x',Right 'y']
さて、この枠組みでListFがどうなるのかを考えてみましょう。
data ListF a x = Nil | Cons a x
これは、型ListF a xの要素はNilかCons a xのどちらかである、という宣言で す。どちらか、ということは、dsumが使えます。また、Nilは要素が1個の型と みなせるので[()]で表し、Cons a xは型aと型xの要素を一つずつとるので dprodで表せます。
listF a x = [()] `dsum` dprod a x
lambda式の方が理解しやすいのであれば以下をどうぞ。
listF = \a x->[()] `dsum` dprod a x
intとcharに適用すると以下のようになります。
*Main> listF int char [Left (),Right (1,'x'),Right (1,'y'),Right (2,'x'),Right (2,'y')]
あやしい集会
先日あやしい集会に行ってきました。 その怪しさといったら筆舌に尽くし難いわけで、一体どこに隠れていたのか、 明らかにまっとうな社会生活を送れてるとは思えない生命体が20人近く一同に 会するわけです。 以前キャッチーなお姉さんに捕まった時もヤバイとは思いましたが、そんなの 比じゃないです。お姉さんの場合は心は腐ってましたが見た目はキレイだった ので、結婚してくれるなら買ってもいいと言い張り、今すぐ役所に行こう、今 すぐ入籍してくれたら君を連帯保証人にして買いましょうと迫ったわけです。 残念ながら性格の不一致とでも申しましょうか、婚姻にはいたらなかったわけ ですが、これはこれでHaskellに比べれば案外ちょろいもんでした。(意味不明)
えーっと何の話だっけ、あ、そうそうその怪しい集会のおかげでちょっとだけ モナドが怖くなくなりました。まぁ、それもあるし、ここも長くなってきたし、 潜入ルポも兼ねて(兼ねてないです。念のため) Programming_WayToHaskeller_てくてくへ Go! (うーむ、さらにイー加減さに拍車がかかってきた。。。)
(「よちよち歩き」を編集する)|(Haskellerへの道)
てくてく
= と <- と
なんか分かったとは言えないけどモナドって、実はそんなにムツカシイもんで もないのかもとか思っちゃいました。そのキッカケが=と<-です。ハイ。
モナドってのはどうやら何か秘密を隠しもってるヤツで、「秘密のある男性っ てステキ!」って感じのダンディーな方だったわけです。 通常関数型言語ではプログラムは式で構成されてて、そいつを評価すると値っ てやつを返してくれる訳ですね。 (通常って語るほど色々しってるわけじゃな いけどさ) で、実際大抵の場合大抵のプログラマにとって重要なのはこの値だっ たりするんですが、モナドの場合はアクションを取るものを返すようで、これ はこれで重要なんですが、実際に次なる(って言葉が曖昧だけど)処理にとって は大抵は役立たずだったりします。本当に欲しいのは隠し持ってる方の情報だっ たりするわけでんがな。
それを取り出すには x = getArgs とか x = getContents ではダメで、 x <- getArgsとかx <- getContentsなんですね。 当然次のようにして x = getArgsとか出来なくもないんだけど、
test = let x = getArgs
in [x]
エバると、
Main> test
[<<IO action>>]
Main>
全然嬉しくないわけです。えぇ。でも、
test2 x = do args <- head x
return args
さらにこんな風にしてやれば、test2 testでもって内容を取り出せるっちゅー 寸法でんがな。 (あ、しまったreturnってことは…取り出せてねー) ま、まーだいたい意図は分るでしょ?ってことで、モナドに関しては、どうや らその区別がちゃんと分かっちゃうと案外可愛いものなのかもしれません。 つまり値を返してくるものとそいつが隠しもっているもの、そんでもって、そ れらの取り出し方ですな。
ただ、どうやら<-によってどんな風にその秘密を取り出せるかはそのモナドの 種類によって違うって感じでしょうか。つまりリストにおいてcarっていうか headで取りだすのすら、恐らく一つの方法にすぎないのかもしれません。 (つ まり機構そのものとしては)多値でもって全要素を返すような、Scheme?の receiveみたいなものだとしてもおっけー!なのかもしんないワケっすよ。
-- あくまでキモチとしてこんな感じデス
-- haskell
do (x, y) <- [1,2]
...
-- scheme
receive (x y) ((lambda args (apply values args)) '(1 2))
...
こんなイメージですね。じゃあ何でリスト(モナド)では<-ってやった時に今の 仕様になってるのかっていうとワカリマヘンorz なんとなくモナド則とかいう屁理屈と関係あんじゃーねーの?って思ったんで すが、難しい議論は学生時代に足を洗ったのでパスです。
まーいずれにせよ、そう考えるとモナドってのは「なんでもモナド」(どっか で聞いたことあるな…)って言ってしまってもいいくらい、どこにでも出てき ていいんじゃねーの?とか思い上ってしまいそうです。
なんでもいいから何か新しい構造だとか型のようなもんを作ったら、それはリ スト同様に、値はその構造を返すし、その中身は<-とかで取り出すのが必然の ようにすら思えてきます。 (ヤバイ洗脳がすすんでるかも…)
あ、そうそう。ちょっと思ったのは、こんな感じですよ。少し前に別の場所で 書いたScheme?のfoldnってやつです。
(use srfi-1)
(use gauche.collection)
(define (foldn n proc . args)
(let ((nils (take args n))
(cols (map (cut coerce-to <list> <>) (drop args n))))
(if (any null? cols)
(apply values nils)
(receive vals (apply proc (append (map car cols) nils))
(apply foldn n proc (append vals (map cdr cols)))))))
さて、ここで書いた時から気になってたのは、carやcdrが出て来てるトコです。 これはあまりにもリストに寄った作りで、実際<collection>なものに対応する ために coerce-toを使っているのですが、「これはよくないなー、よくない よー」と山本直樹のマンガに出てくるキャラばりに思ったわけですよ。はい。 そこで本当ならイテレータ構築メソッドとやらを使って、nextだかgetだと かいった、一段抽象的なメソッドを使って書くべきなんでしょう。
エーっと、また何を書こうとしてたのか忘れたぞ。なんだっけ? あ、そーだ。つまり、これはcollectionだとかsequenceだとかってものに特化 してるんだけど、さらに一般化すると、中に何かを持っているものからそれな りの方法で中身を取り出すという、ただもうそれだけのようです。
正しくないんだけど、はっきしいって今の私にとっては、ほとんどScheme?の オブジェクトに対するrefとかslot-refとモナドにおける<-ってたいして変わ んねーじゃんって感じなわけです。少なくとも位置付けとしてはそんなもんだ ろうってアバウトに思ってても使えそうな気にはなっちゃってるわけです。思 い上りまくりです。えぇ。
まぁ実際には、おおーそうなのかー!!とか思う程簡単じゃないわけで、モナ ドのバヤイはそれを使うコンテキスト(?)みたいなのとモナド則を満すような >>=とreturnの組合わせってのがナゾを呼ぶわけですな。 そこに入りだすとなんか地獄が手ぐすね引いて待ってそうなので近寄りません がね。まぁ「臆病なくらいでちょうどいいのよね」とかつぶやいたりしましょ う。
さらに<-と>>=の関係が分ってしまえば天下か?
でもってさらに衝撃の事実ですが、
main = do cs <- getArgs
mapM_ putStrLn cs
と
main = getArgs >>= (\cs -> mapM_ putStrLn cs)
とは同じなんだそうです。多分以前もどっかで誰かに教えてもらったのかもし れませんが、はっきりいって頭に入ってないっす。そんなに色々覚えらんねー よーって(無礼にも)思ったに違いありません。が、今なら分りますね。 モナド的にはどうも>>=とreturnってプリミティブっぽい気がするんですが、 個人的にはっきり言って <- のがずっとプリミティブって感じがするので、モ ナドって言わずに <- これを ref とでも名付けて>>=とかは、シンタックスシュ ガーというかなんというか、まぁ欲を言えばうやむやにしてくんないかなーと か思ったりします。 思いませんか?そうですか、私だけですか…
ちなみに、mapM_ ってやつですが、正直mapとかmapMとかmapM_とか、なんでこ んなしちめんどくせーのか知りませんが、まぁ型が違うのでこういうもんなん でしょうね。 emacsのミニバッファに表示される型を見ながらイロイロ試すか、マニュアル を検索するかしてしのぎます。
あらためて見ておきましょう。
main = do cs <- getArgs
mapM_ putStrLn cs
こいつはgetArgsがIO [String]なので、アクションなんですが、[String]なヒ ミツを抱えてるワケですね。 でもって、<-でそのヒミツをなんとか取り出してcsに束縛するんです。そう、 <-はモサドみたいな機関で、モナドからモサドが国家機密を探り出すわけです。 (w (ただ、実際の動作はナゾです。そもそもこの時点で、ヒミツを全部取り出す 必要はないし、どういう順で取り出すのかもそのモナドの勝手じゃーんってこ とです。おそらくこれも必要になった時点で秘密がバクロされるという、ご都 合主義の2時間ドラマみたいな仕掛けになっているんでしょうね。 Preludeと いう名前といい、どうもHaskellを作った人は劇作家じゃないだろうかと勘繰っ たり) あとはmapM_で煮て焼いて食ってるだけっす。
main = getArgs >>= (\cs -> mapM_ putStrLn cs)
こいつのバヤイも同じです。getArgsが抱えてるヒミツを>>=を通して右に送り つけてる感じでしょうか。右に渡ってるのは見えないけど[String]なデータだ と思えばよく、それをlambdaが処理してIO ()な形で始末してるんです。
私が>>=より<-の方がプリミティブっぽいと感じたのは、多分データの受け渡 しが見えるからのような気がします。ハイレベルの言語って結局同じことをや るんだけど、暗黙裡にやってくれることが多いっていうか広いっていうか、まぁ いい感じに処理してくれちゃうわけで、見えない部分で活躍する裏方さんが大 勢いらっしゃるんです。 >>=もある意味そうなんですよね。今はlambdaを書い ているからcsが見えてますが、もし、
printArgs = mapM_ putStrLn
こんな風になってたら、
main = getArgs >>= printArgs
csが消滅しちゃうわけですね。そーすっと、csの存在が見えないのに動作の裏 側で暗躍することになるので、説明された時にイメージできないわけです。 (という言い訳です) 逆に<-を使うとイヤでもcsの存在を書かなきゃならないので、これは表社会の 住人さんだからなんか人に優しい感じがするんですよね。(という言い訳です よ、しつこいけど)
getContentsのバヤイ
ほとんど同じなんだけど、getContentsを見ておきましょう。
main = do cs <- getContents
putStr cs
getContentsはIO Stringってことで、今度はヒミツはStringなものなので、 mapM_とかせずにそのままputStrに渡すことにしました。なんか全然簡単って 気がしてきましたね。
さらにさらに<-ってのはここにも登場?
でもって実は怪しい集会に潜入ルポって来た成果として、次のようなのも。
[(x,y) | x<-[1..100], y<-[x..100],y==x*2]
奥さんこれです。 リスト内包表記ってやつね。ここで出てくる<-ってのはまさにモサドです。ハ イ。モナドじゃなくモサドの方ね。すごくないっすか?恐らく逆で、これは数 学の表記をもとに<-な記号を採用して、その後一般化されたんじゃねーのかなー と思ったりするんですが、まさにリストもモナドなわけです。実際これ見たら、 やらずにはいられません。
Main> [1,2,3]>>=(\x->(x,x*x))
ERROR - Type error in application
*** Expression : [1,2,3] >>= (\x -> (x,x * x))
*** Term : [1,2,3]
*** Type : [b]
*** Does not match : (a,a)
あら?あーあーあーそうか。
Main> [1,2,3]>>=(\x->[] (x,x*x))
ERROR - Type error in application
*** Expression : [] (x,x * x)
*** Term : []
*** Type : [c]
*** Does not match : a -> b
え?えーっとリストにすればいーんだよね。も少し簡単なので確認。
Main> [1,2,3]>>=(\x->[] x)
ERROR - Type error in application
*** Expression : [] x
*** Term : []
*** Type : [c]
*** Does not match : a -> b
あうっ!そもそも間違ってんのか。
Main> [1,2,3]>>=(\x->x:[x*x])
[1,1,2,4,3,9]
おっけー!ちょっと意図が違うけど。
Main> [1,2,3]>>=(\x->[(x,x*x)])
[(1,1),(2,4),(3,9)]
あ、これがやりたかったの。いーんでないすかね。
Main> [1,2,3]>>=(\x-> return (x,x*x))
[(1,1),(2,4),(3,9)]
勿論こんな風にreturnで[a]なリストモナドを返すのも正しい。ってか >>=と コンビで使うならこっちのが推奨なんでしょう、きっと。 ただ、[]を使うのは、いわば(上に示した)carやcdrを使ったfoldnの定義と同 じで、リストに特化した書き方になってる。一方でreturnを使う書き方はより 一般化された書き方になってるわけさ。
もう少し頑張ってみます。
Main> [1..100]>>=(\x-> return [(x,y) | y<-[x..100], y==x*2])
[[(1,2)],[(2,4)],[(3,6)],[(4,8)],...[],[],[],[]]
あらら。おしい!所望の結果は
Main> [(x,y) | x<-[1..100], y<-[x..100],y==x*2]
[(1,2),(2,4),(3,6),(4,8),...,(47,94),(48,96),(49,98),(50,100)]
なので、一個リストの構造が余計だなぁ。えーっと、あー分りましたよ!
Main> [1..100]>>=(\x-> [(x,y) | y<-[x..100], y==x*2])
[(1,2),(2,4),(3,6),(4,8),....,(47,94),(48,96),(49,98),(50,100)]
キターーーーーーーー! ぃよーーーっし、じゃあ続いてやっちゃうよ!
Main> [1..100]>>=(\x-> [(x,y) | [x..100]>>=(\y-> [y | y==x*2])])
ERROR - Undefined variable "y"
あぅ!なんでダメなのさ?落ち付け落ち付け。
Main> [1..10]>>=(\x->[x])
[1,2,3,4,5,6,7,8,9,10]
いけるよね。じゃあ絞ろう。
Main> [1..10]>>=(\x->[x| x==3])
[3]
うーん。いけるな。ってことは、[x..100]が駄目なのかなぁ。
Main> let x=10 in [x..100]
[10,11,12,13,14,....,95,96,97,98,99,100]
勿論大丈夫だよね。うーん。えーっと、あらら?そうか。よくよく見てみたら、 [x..100]>>=(\y->[y| y==x*2])ってしたら、それは単なるリストだからそれが yだってのは結びつかないのか。
Main> let x=5 in [y | [10..100], y==x*2]
ERROR - Undefined variable "y"
みたいな感じになっちゃってんだな。そうか、そりゃダメですわ。 マジに副作用ないの?
この数日なんだかモナドについてまた考えてたんだけど、またちょっと目覚めたかも。
よく言われることなんだけど、Haskellって副作用がないって本当?とか 副作 用がなきゃプログラム書けないじゃんとかそーゆーやつです。 色んなところを調べると、モナドに閉じこめたとかモナドがそういう汚い世界 のことを一手に引き受けてくれてるとか書いてあるんだけど、相手からすると、 なーんだ結局あるんじゃん、副作用って言われちゃうわけですね。
main = do cs <- getContents
putStr cs
こちらにあるコードをGHCを使ってコンパイルします。
[email protected]> ghci cat.hs -o cat
そうするとcatっていう実行可能コードが出来ます。こいつを使ってみます。
[email protected]> ./cat
hoge
hoge
fuga
fuga
うひゃ
うひゃ^D
[email protected]>
これを見ると、readする度に違う値を返してきてるじゃん。って思うわけです。 同じ式が評価する度に違う値を返してきてるってのは副作用があるってことじゃ ん? BUT!!そりゃ早トチリってもんですね。よーくコードを見ると、どこにもread なんてないんです。勝手にreadしてるって妄想しちゃイヤーンです。
つまり、一行読んでその文字列を返すなんて関数は使われてないんですよ。 getContentsってのはreadじゃないってのがとっても重要。
getContentsは誤解を恐れずに言うならば、 私が入力する“hoge”、“fuga”そし て“うひゃ”っていう3行 (もしかしたらそれ以降にも入力されたかもしれない 行)全てを知ってて、 そいつを一種のリストのようなものに入れて返してきて いるんです。 そして、必要になったところで順番に中から取りだして表示しているに過ぎな いんです。 え?だって“うひゃ”ってのを読み込む前に“hoge”を表示してる(putStrしてる) じゃん!? そりゃそうですYo!だって遅延評価ってそういうもんじゃん。それが許されな きゃ、[1..]なんて無限リストを全部構築しなきゃなんも計算できなくなりま す。 cat.hsでは、“hoge”を表示するときには“うりゃ”が本当に必要になるわ けではないから、べつにアクセスしてないだけなんですね。嘘だと思うなら、 [1..]をHugsのプロンプトに入れてみればいいでしょう。
Main> [1..]
[1,2,3,4,5,6,7,8,9,10,11.............................
おっとっと、すぐ^Cしなきゃじゃんじゃんでちゃいます。ここでは無限大まで のリストを構築しなくても(つーかそんなんできないけど)、最初の方の表示に はなんの問題もないからちゃんと表示できてますよね。もー一個いっときましょ う。
Main> take 5 [1..]
[1,2,3,4,5]
Main>
この計算がちゃんと終了するのと同じ理由ですね。 そうなんだよね〜。リストもモナドだって衝撃を受けた時に気付くべきでした。
listA = ["hoge", "fuga", "うひゃ"]
っていうリストから順にデータを取りだした時に、「listAはアクセスする度 に、“hoge”とか“fuga”とか“うひゃ”とか毎回違う値を返してくるから副作用が ある」なんて言う人はおらんでしょう。それと同じことだったんですね。
ようやく結論ですが、副作用ってのを本当にうまく消してしまってるんですよ。 副作用はキレイサッパリ消失してしまったんです。押し付けられた一部の関数 が副作用を引き受けたんじゃなくて本当になくしたんです。にも関らず、副作 用と同じことを実現してるんです。
副作用みたいに毎回違う値が欲しいなら、その毎回変るであろう値ぜーんぶを 持つ一つの値(構造)をでっちあげて、そこから順番にデータを取り出すような 仕組み(モナディックなプログラミング)を作り上げるわけです。
まー考え方を変えただけって言われるかもしれませんが、上のようにリストに なぞらえて理解すると本当に消えちゃったでしょ?副作用。それでも言い換え ただけって思います?実際そういう仕組みを作ってコードもそーゆー感じに書 いてますってところが徹底してると思いますね。少なくとも私は自分で自分の 説明に納得しちゃたんでもういいです。はい。(^^)
最初はそんな副作用を使わずに入出力を表現するなんて無理だよーって思って ましたが、あなどれませんHaskellでもってモナド、そしてなにより本質的に それを可能にした遅延評価の世界です。 lazyだからこそ、そういう考え方が 成立するし、そういう意味付けと矛盾しない動作が実現できるんですね。
すきっぷ
ダンスダンスダンス
(「ダンスダンスダンス」を編集する)|(Haskellerへの道)
(「ダンスダンスダンス」を編集する)|(Haskellerへの道)
Last modified : 2006/06/12 13:03:32 JST