haskell-ja > Archives > 2012/11/14

2012/11/14 05:30:02 UTCshiro
#
最近のHaskellってimport Ratioしても%が定義されないん? on GHCi 7.4.1
#
Prelude> import Ratio
Prelude> :t (%)

<interactive>:1:1: Not in scope: `%'
#
でも
#
Prelude> toRational 1/3
1 % 3
2012/11/14 05:32:31 UTCkazu
#
階層化されてないモジュールは obsoleted です。
#
Data.Ratio ではどうですか?
2012/11/14 05:33:51 UTCshiro
#
おお、ありがとうございます。haskell ratioとかでぐぐってもHaskell 98の方が出てきたりして悩んでました。
2012/11/14 05:38:15 UTCkazu
#
http://www.haskell.org/ghc/docs/latest/html/libraries/index.html
#
これの右側に haskell98 とあるやつは、obsoleted です。
2012/11/14 05:42:27 UTCshiro
#
印字できるのに読めないってLisper的にはちょっと気持ち悪い RT: @nobsun そっちは印字表現なので...
#
(twitter -> chaton 方向はまだ動いてません。さっさと直さなきゃ…)
2012/11/14 06:00:42 UTCnobsun
#
型シノニム RationalがPreludeからエクスポートされているので read と show は可能ですよ.
#
Prelude> let r = read "1 % 3" :: Rational
Prelude> show r
"1 % 3"
Prelude> :t r
r :: Rational
2012/11/14 06:09:00 UTCnobsun
#
toRationalでは欲しいものはえられないかも
#
Prelude> toRational (22/7)
7077085128725065 % 2251799813685248
2012/11/14 06:16:07 UTCshiro
#
む、readできるのにghciのプロンプトから打てない (プロンプトでは"1%3"というリテラルではなく(%) 1 3という式として読まれる) っていうのも違和感あったり。
#
あーでもhomoiconicでない言語では式とリテラルが全く別でも構わないのか。
2012/11/14 06:19:09 UTCnobsun
#
文字列はあくまでも文字列ですよねぇ.lispでも.
#
"(cons 1 nil)" と (cons 1 nil) は別ものですよね.
2012/11/14 06:22:03 UTCshiro
#
その左から右に変換するのがreadなんですが、違和感の原因は、Lispだと「プログラムの解釈系」が一番最初にreadすることになっているんで。
2012/11/14 06:22:19 UTCnobsun
#
Haskellでは read :: Read a => String -> a ですので.
2012/11/14 06:23:45 UTCshiro
#
Lispでもそうっすよ > read。 で、Lispではプログラムの解釈実行系は eval $ read なんで、 1%3 をreadしてRationalが得られるなら、それをプロンプトから打ったら Rational がどうにかされる、というのを期待しちゃうんですな。
#
(あ、正確にはLispのreadは stream -> a か。)
2012/11/14 06:30:58 UTCshiro
#
でも一般にプログラムの外部表現とデータの外部表現が一致する必要はないとはいえ、プログラム中のデータリテラルとデータの外部表現は合わせるのが慣習なわけで、例えば浮動小数点数を印字したら "1.23" になってそれをreadすると数値の 1.23 が得られるけどプログラム中に 1.23 って書いてあったらそれは (.) 1 23 と解釈される、って仕様があったらやっぱり違和感ありません?
2012/11/14 06:36:26 UTCnobsun
#
??
#
あれれ?どういうことですか? 1.23 は 1.23 としてしか解釈されませんが...
2012/11/14 06:41:49 UTCshiro
#
私がここで取り上げてるのは、Haskellではプログラムのパーズ (仮にparseとしましょうか) とデータの read は別々なんだなあということです。1.23ではたまたまparseもreadも同じ動作をするけれど、 1%3 はそうではない。
2012/11/14 06:42:26 UTCnobsun
#
10 と書いて 「じゅう」と解釈するか「に」と解釈するかは恣意的な選択ですよねぇ.
2012/11/14 06:43:12 UTCshiro
#
で、そりゃ文法をそう決めてるんだからそれ自体はおかしくないんですが、parseの前段に必ずreadがある世界の住人から見ていると、なんとなく不統一な感じがするっていう話です。
2012/11/14 06:44:36 UTCnobsun
#
ああそのとおりです.> parse と read は別
2012/11/14 06:45:25 UTCshiro
#
不統一と言うより、抽象化の破れを見ちゃったみたいな感じかなあ。
2012/11/14 06:47:19 UTCnobsun
#
そこのところがよく判らないんですが.> parseの前段にreadがある
2012/11/14 06:47:42 UTCshiro
#
というかLispだと「parse=read」だ、と言った方がいいですね。
2012/11/14 06:49:35 UTCnobsun
#
Lispだと構文がfirstクラスになっていますが,Haskellでは構文はfirstクラスではないです.
2012/11/14 06:53:54 UTCnobsun
#
read "1:[]" :: [Int] は当然エラーです
2012/11/14 06:54:04 UTCshiro
#
それは承知しています (上で"homoiconicでなければ云々と言ったのはそういうこと)。ですから、データのreadとプログラムのparseが全く違う言語があったっていいわけです。例えば、read "1.23" -> 1.23 だけどプログラム上で 1.23 と書いてあったら (.) 1 23 だよ、という言語hogeがあってもいい。でも、ほとんどの言語ではあまりそういう設計はしないですよね。データリテラルはプログラムの表記のサブセットですが、それに合わせてreadをparseのサブセットにしている (readで処理できる構文については、parseでも同じように処理される) 言語が多いんじゃないですか。
#
もちろん多くの言語で逆は真でない (parseで処理できるからといってreadできるとは限らない) で、nobsunの上の例はそれですね。
2012/11/14 06:58:31 UTCnobsun
#
はい,Haskellでは read はあくまで Read クラスのメソッド(正確にはreadPrec)なのでデータ型の性質を規定するものとして考えられています.
#
不統一というよりは,parseとreadは全く別ものということです.
2012/11/14 07:05:55 UTCshiro
#
アーキテクチャ上別物であって構わないんですが、デザイン上同じように振る舞わせるのが良いっていうのは大まかなコンセンサスとしてあるんじゃないかって話です。だって1.23はreadされてもparseされても数値であることを普通期待するでしょう? まあHaskellの場合、readは後づけで拡張できるけどparseは拡張できない(ですよね?)こと、pure functionalな性質から、演算子%さえ見えていれば 1%3 が一つのRationalとして読まれようが式として読まれようが実用上違いは無いってことだと思うんですが。今の場合、デフォルトで (%) が見えてないために齟齬が露になっただけで。
2012/11/14 07:20:52 UTCnobsun
#
はい.そうです.さきほどparseとreadは別ものといったのは的はずれでした.別ものなのではなく,構文木というデータ型をReadクラスのインスタンスとすれば,構文木に対するreadはまさにparseなわけでそういう意味では一致します
#
ところで,「parseとreadをデザイン上同じように振る舞わせるのが云々」がピンと来ません.同じように振る舞うのならどちらか一方でよいのではと思ってしまいます.
2012/11/14 07:24:34 UTCshiro
#
いやまさにLispでは一緒なわけですが。
#
homoiconicでない言語では、あくまでデータリテラルはプログラムの表記のサブセットにすぎず、readが対象にするのはデータだけなので、一般には「readの認識する構文」⊂「parseの認識する構文」になるんじゃないか、てことです。
#
例えばjavascriptとjsonの関係。json readerの動作はjavascript処理系がリテラルを処理するのと同じですが、json readerでjavascriptソースを読めるわけではない。
2012/11/14 07:32:30 UTCnobsun
#
う〜ん.データ表記はプログラム表記のサブセットではないです.プログラム表記としてデータリテラルはあくまでもプログラムという抽象的な値の一部であって,データが違えばそれ相当の表記が個別にあるのが自然だと思います.
2012/11/14 07:36:33 UTCnobsun
#
もちろん一致させてはいけないというわけではないです.
2012/11/14 07:38:18 UTCshiro
#
どこまで「自然」とみなしますか? 1.23は? "abc" は? [4,5,6] は? これらでプログラム中の表記とreadの解釈が一致するのは偶然ですか? いえ、わざわざ合わせたんですよね。で、 1%3 がparseによって単一のリテラルと見なされないのは、現実的にはlexical syntaxを後からいじれないという実装上の理由があるわけですが、もしlexical syntaxをいじれるとしたら、これもreadと一致させたくありません? っていうかわざわざread/showで
#
演算子と同じ '%' を使っているってのが、
#
「一致させたい」って願望の現れに見えます。
2012/11/14 07:43:37 UTCnobsun
#
あれれ.そのとおりですね.私は何を言いたかったのでしょう orz
#
shiroさんの「抽象の破れ」というのに反論しようとしていたのですが...
2012/11/14 07:46:15 UTCshiro
#
「抽象の破れ」という言い方がおかしかったかも。本当に抽象が破れてるわけじゃなくて、むしろ幻想の破れかな。readとparseがそれなりに同じように振る舞うだろう、というのはアーキテクチャ上は全然保証されてなくて、慣習でそういうふうにしてるというイリュージョンであった、その隙間がたまたま見えた、って感じです。
2012/11/14 07:47:23 UTCnobsun
#
そもそも,REPLに対して read "1 % 3" :: Rational が通るのに,1 % 3 :: Rational とは書けないのは気持わるいということでしたよね.
2012/11/14 07:49:11 UTCshiro
#
あと、データリテラルとreadを別にしなければならないのには、もしかして静的型特有の事情がある? readは戻り値の型が決まらないとどの関数を呼べばいいか決まらないですよね。で、プログラムをパーズしている段階ではまだ型推論は走ってないとすれば、パーズ中にユーザ定義のreadを呼ぶことはできない。
2012/11/14 07:50:52 UTCnobsun
#
ああそれは無理ですね.
#
readの定義をパースするのにそのreadの定義を使えないという意味ですよね.
#
あれ,それってLispでも無理じゃない?
2012/11/14 07:53:36 UTCshiro
#
そです>気持ち悪い。というかその前段階として、(1) import Data.Ratioしてない状態でも toRational 1/2 とすれば 1 % 2 が表示される → (2) import Data.Ratioすれば 1 % 2 ::Rational を読んでくれる、でも (3) import Data.Ratio してない段階で 1 % 2 ::Rational を読んでくれない、というのがなんか非対称だなあってのが気持ち悪さの理由です。
#
いえ、readの定義じゃなくてもいいんだけど、例えばpathnameオブジェクトっていうのを定義して、 #p"/foo/bar" という外部表現にしたとします。read :: String -> Pathname も定義した。でも、プログラム中にpathnameオブジェクトのリストを map foo [#p"/foo/bar", #p"/foo/baz"] とか書いてあったとして、パーザは map foo [ まで読んだところでどのreadを呼べばいいのか、型情報がないのでわからない。
2012/11/14 07:57:58 UTCnobsun
#
え?それわからない.
2012/11/14 07:58:29 UTCshiro
#
わからないのはどっち? 「気持ち悪さ」「#p"/foo/bar"」
2012/11/14 07:59:58 UTCnobsun
#
fooの型は?
2012/11/14 08:01:46 UTCshiro
#
foo :: Pathname -> Int と foo :: String -> Int があったりしたら?
#
ってかfooの定義がこの後に出てくるかもしれないしなあ。
2012/11/14 08:04:00 UTCnobsun
#
Pathname が String の型シノニムであれば決まりますよね.というよりそもそも foo は 1つしかない.
#
そうでなければ 構成子 でどちらの型であるか判断できる.
#
fooの定義が後で出現しても出現するのであれば問題はないです.
2012/11/14 08:08:24 UTCshiro
#
構成子で判断するためには、まず構成子呼び出しの構文がパーズできてないとだめですよね。でも#p"/foo/bar" ってもともとHaskellの構文に無いからパーズできないんじゃないですか? と書いてきて、もしかして # p "/foo/bar" とパーズさせればいいのかと気づいた。)
2012/11/14 08:11:23 UTCnobsun
#
実際は # p "/foo/bar" 単独では正しくparseできません. p # "/foo/bar" もしくは (#) p "/foo/bar" ならできます.
#
あるいは (# p "/foo/bar") ならOK
2012/11/14 08:13:28 UTCshiro
#
まあでもそのへんちょっと妥協してHaskellのparserに合わせれば、「ユーザ定義のreadを呼ばせる」必要がなくなるわけですね。あとはreadの認識する構文をコンストラクタ呼び出しと合わせておけば、あたかもreadされているような幻想を見せられると。
2012/11/14 08:18:34 UTCnobsun
#
ちょいと再確認ですが. read :: String -> a は a に当て嵌まる具体的な型を規定するものですが,これ自身は気持ち悪くはないですか?
2012/11/14 08:19:32 UTCshiro
#
「戻り値の型もディスパッチに使ってる」と考えれば気持ち悪く無いです (Haskellerは「ディスパッチ」ととらえないかもしれませんが)
2012/11/14 08:20:43 UTCnobsun
#
Lisp の read はなんとなく read :: a -> Lisp という感じがするんですが,私のこの認識はあってますか?
2012/11/14 08:22:27 UTCshiro
#
いや、readは敢えて書くなら IO Char -> Lisp とかそんな感じじゃないですか。型ではディスパッチされません。入力の文字によって登録された特殊化リーダが呼ばれることはありますが。
#
s/readは/Lispのreadは/
2012/11/14 08:23:03 UTCnobsun
#
ああでも read の返り値は Lisp 固定なんですよね.
2012/11/14 08:25:08 UTCshiro
#
ですです。これも敢えて言うなら、 readString :: IO Char -> String, readNum :: IO Char -> Num みたいのがあって Lisp = String | Num | ... と言う具合になってて、 read :: IO Char -> Lisp。そんでreadはちょろっと読んだ文字によって適切なreadXXXに処理を振る。CommonLispではそのreadXXX をユーザ定義型向けに拡張できるって感じ。
2012/11/14 08:26:31 UTCnobsun
#
ああそこです.Haskellerとの齟齬の原因は > Lisp = String | Num | ...
#
たぶん
2012/11/14 08:29:01 UTCshiro
#
Lisp = String | Num | ... ってやっちゃうとどのreadXXXを呼ぶのか静的に決まらないですからね。
2012/11/14 08:30:33 UTCnobsun
#
Haskeller としては data Lisp = S String | N Num | ... で,S String はあくまでも Lisp 型であって String型でないので. S String と String が別の印字表現をもつこと自体不思議ではないんです.
2012/11/14 08:32:41 UTCshiro
#
ふーむ、そんでもって、パーザが内部的に構文木をそんな感じで扱ってるとイメージすれば納得もできるというものか。
2012/11/14 08:32:57 UTCnobsun
#
はい.