utf8everywhere.orgを読んで
UTF-8 Everywhere
インターネットにおいてはそりゃあそうだろうと思いつつ読んでみたら、WindowsにおけるC++プログラミングの話でなかなか興味深かった。
要約すると、
というところか。
重要なのは、UTF-16を使っても固定長にはならない、という点だが、完璧ではないからダメ、というのはあまり意味が無い。
危険なのは、UTF-16で完璧だと思い込むことだろうと思う。大抵の場合で問題が無いからこそ、わかりにくいバグの元になる。
自分のスタイルは、何も考えずただ_UNICODEとUNICODEをdefineして、リテラルは_Tを使って、変数にはstd::wstringを、ファイルにはboost::filesystem::fstreamを、文字列は固定長として扱い、Windowsのサポートも中途半端なサロゲートペア等は非サポート、というもの。
日本語のファイル名だとかを使う程度のことにはこれで問題無い。日本語に関しては、Shift_jisが問題ありすぎるだけで、あんまりUNICODEとは関係無い気もする。
他言語においては、やはり自分が使ってないと分からない*1、というのが全てのように思う。UTF-8にすれば解決する、という問題でも無く、固定長の文字列エンコーディングなど無い、という前提でプログラミングするしか無い。
そして、固定長で無いならUTF-16/32を使う意義は無く、UTF-8にすべきだろう、という話になるわけだが、自分としては、すでに確立してる慣習的なものから移行するには、単純な新旧比較ではなく、新しいものに移行コストを無視できるかなりの優位性が求められ、UTF-8にそこまでのそれは感じない。
Object Invocation
シンボルに続いて()があるか無いかと、そのシンボルが指すものがMethodかどうかで、インタプリタの解釈が少し変わります。
Method | 非Method | |
x | x() | x |
x() | x() | x.invoke() |
Methodの場合は同じですが、非Methodの場合は、()があればinvokeが呼ばれ、無ければそれがそのまま返ります。
そして、Object.invokeはselfを返すので、大抵のオブジェクトにおいて、x()はxと変わりなく使え、invokeを定義してるオブジェクトにおいてのみ、関数のように振る舞うfunctorとなります。*1
Object.invokeに引数が与えられた場合、self.==が呼ばれます。これにより、invokeが未定義のオブジェクトは、そのままでpredicateとして機能します。
x: {invoke: 1} x //{invoke: 1} x() //1 x(1) //x.invoke(1) -> 1.== 1 -> true x(2) //x.invoke(2) -> 1.== 2 -> false y: `*[2] y //2 y() //2 y(1) //2 z: 3 z //3 z() //3 z(3) //true z(4) //false f: `pred[pred(1).? 't', 'nil'] f(1) //t f(2) //nil f(`*[true]) //t
リテラルに()を続けた場合は、数値だと型指定、文字列だとその文字列をシンボルとして扱うことになり、このObject Invocationの対象にはなりません。
そうしたい場合は、間に.を置くことで可能です。
"abc"() //abc()と同じ "abc".() //"abc".invoke()と同じ
関数の戻り値を呼ぶ場合も同じです。
`[ `x[ x.+ 1 ] ].().(2) //3
Giraffe+ 0.6.23.1215
- ADD: COMPILE_TIME_EVAL, コンパイル時Context.eval
- ADD: Container.irange, {element index} ジェネレータ
- ADD: settings/key/ShowCurrentModeKeys, mode-localなキーイベントのメニュー
- FIX: Migemo_grouping は最初か空白に続く最後のハイフンを無視すべき
- FIX: resolve が配列リテラル内のシンボルに機能しない
- MOD: event-menu はイベントが定義されてるスクリプトファイルを開けるようにすべき
- MOD: event/config/color/mode-local をメインメニューに統合
- MOD: Method_and/or をシリアライズ可能に
- MOD: Object invocation は self.invoke が非メソッド時に再帰的に処理すべき
ContainerのC++のSTLのalgorithm系メソッドについて
- func(begin, end, ...)は、func(self.begin, self.end, ...)となる
- 破壊的なものは!が後置、それの付かない版はコピーを返す
- ArrayやMapの値は置き換え(self.[index] = value)でなく書き換え(self.[index].= value)になる
- for_eachはeachがあるので無し。eachはcontinue, breakをサポートする
- transformはmapがあるので無し。mapはcontinue, breakをサポートする
- removeは勝手にeraseする
- copyはObject.copyと名前が衝突するのでcopy_intoになってる
- C++で_ifが後置するものがある関数は常に_ifの方を呼ぶ
Giraffe+ 0.6.22.1208 から、Object.invokeに引数を渡した場合に==を呼ぶようになり、これにより、普通のオブジェクトを高階関数にpredicateとして渡せるようになりました。
例えばString.trimでは、
'1234321'.trim(\1) //23432
とできるようになりました。
これに伴い、C++のSTLの関数は全て_ifの方を使えばいいということで、そうしたんですが、x == yとなってたのが、y == xとなり、問題のある場面があります。
例えばfindでは、
s:: '2' s.as_Int: s.to_Int$ //Int.== が自動的に呼ぶ {1 2 3}.find(s) //以前なら1.== sだが、今はs.== 1なのでエラー。 {1 2 3}.find(`n[n.== s]) //以前と同じ
ということになります。こういう特殊なことをしてない限りは問題無いはずです。
Giraffe+ 0.6.22.1208
- ADD: Object は + と - を ++ と -- を使って定義すべき
- ADD: Object.negator, Method.notのシノニムでobject invocationをサポートする
- ADD: Object.to_Range, Range.make_exclude& self.begin, self.end.
- ADD: Slot_value_type は :key.=> value としてリテラル化されるべき
- FIX: ShowMenu2 がキー設定無しで動かない
- FIX: Stack overflow, Stringがunwrapに失敗したとき
- MOD: Map 風オブジェクトは全てContainer.to_MapでMapに変換可能であるべき
- MOD: Container.find みたいなメソッドは *_if だけをobject invocationによる比較で呼べる
- MOD: Context.recur/super/resend は同じ Context を継承すべき
- MOD: Object invocation の 新context無し時は、似たようなcontextを作るべき
- MOD: Object.invoke は引数を得たときに == を呼ぶべき
- MOD: Editへの貼付けは改行とタブを空白に置換すべき
- MOD: Range.at はイテレータが非ランダムアクセシブルの場合にイテレータの正当性を無視すべき
- MOD: Range.at/size は Container のメソッドであるべき
コンマとセミコロン
また変に複雑化しました。セミコロンを式の省略可能なセパレータとすることで、&による括弧省略記法時に、どこまでを引数とする、というのを表現できるようにし、それならコンマも書きたければ書けるものにと、 コンマとセミコロンを、省略可能な、C系の言語におけるそれらのようなものとしました。
これまで(ver.0.6.20)の仕様も合わせてまとめておきます。
コンマ
//全て意味は同じ f(x y) //コンマ無し f(x, y) //新用法 f& x, y //従来用法(二項演算子引数追加)
スタイルとして、全ての引数にコンマを付けたくなるかもしれませんが、二項演算子的メソッドの後では従来用法になってしまうので注意が必要です。
f(x.+ 1 y) //== f(x.+(1) y) f(x.+ 1, y) //== f(x.+(1 y))
セミコロン
メソッド名の後に&か%を付けて引数の括弧を省略する際、どこまでを引数と扱うかの指定は結局括弧を使うしかありませんでした。しかし、この省略可能式セパレータにより、どこまでが式と表現できるようになりました。
//全て意味は同じ f(x y).flush f& x, (y).flush f& x, y;.flush //新用法 f% x, y.flush //全て意味は同じ f(x y.flush) f& x, y.flush f% x, (y.flush)
セミコロンの後にドットが続くのが気持ち悪いんですが、これで%や括弧は不要になりました。
従来用法のresolve2のためのセミコロンはリテラルです。
//意図的な分かりにくい例 y: 1 [f& x, ;y;.flush].resolve2.flush //[f(x 1).flush]
ついでに、参考になりそうな表です。
Cで言うとどう言うか
giraffe | C | 説明 |
---|---|---|
x | x | xがメソッドの場合はx() |
x$ | x | xがメソッドであっても |
x y | x; y; | 空白区切り |
x, y | x; y; | 省略可能コンマ |
x; y; | x; y; | 省略可能セミコロン |
f(x y) | f(x, y) | 空白区切り |
f(x, y) | f(x, y) | 省略可能コンマ |
f(x; y) | f(x, y) | 省略可能セミコロン |
x.y | x.y | 同じ |
x.+ y | x + y | 二項演算子風メソッドは次の式を引数化する |
x.+ y.* z | (x + y) * z | 演算子そのものでは無いので演算子の優先順位も無い |
x.+ (y.* z) | x + y * z | ただのメソッドコール、これと同じ: x.add(y.* z) |
f(x.+ y z) | f(x + y, z) | 空白区切り |
f(x.+ y, z) | f(x + (y z)) | コンマによる二項演算子引数追加 |
f(x.+ y; z) | f(x + y, z) | 無意味なセミコロン |
f(x.+ y;, z) | f(x + y, z) | セミコロンによる式の終端 |
Cで言うあれはどう言うか
C | giraffe | 説明 |
---|---|---|
if(x) y | x.? [y] | ifもただのメソッド |
if(x) y else if(z)... | x.? [y], [z]... | testとbodyを引数として続けていくだけ |
if(!x) y; | x.! [y] | !はunless |
!x | x.not | 前置演算子は無い |
~x | x.complement | 補数 |
switch(x){y: z;} | x.case(y [z]) | switchもあるが、caseの方が使いやすい |
シンタクスシュガー
略記 | 省略なし | 説明 |
---|---|---|
x& y z | x(y) z | &の後置による括弧省略 |
x& y& z | x(y(z)) | 入れ子可能 |
x& y.z | x(y.z) | 引数のドットも食う |
x% y.z | x(y).z | %は食わない |
x& y, z | x(y z) | コンマで引数追加 |
x& y;.z | x(y).z | セミコロンで式終了 |
x: y | def(:x y) | コロンの後置はdef |
x.y: z | x.def(:y z) | ドット後でも同じ |
x@ y | set(:x y) | @はset |
x$ | get(:x) | $はget |
x* | call_safe(:x) | *はcall_safe |
{x y z} | Array.new(x y z) | 配列 |
{x: y} | [x: y].to_Object | オブジェクトリテラル |
Giraffe+ 0.6.21.1201
- ADD: 省略可能な引数コンマと終端セミコロン
- FIX: Giraffe\ManualとメインメニューのManualが動作しない
- MOD: Container/String.shell_menu はverbの数値を返すべき
- MOD: EvalText (デフォルトでF1) は入力テキストが空白の時に説明を出すべき
- MOD: Event.Deactivate は実行中に呼ばれないようにすべき
- MOD: Nullをデフォルト引数に渡した場合に、デフォルト値になるようにすべき
- MOD: デフォルト引数のデフォルト値が記述されなかった場合にNullになるべき
- MOD: mode:enter はブロックを受け取り、mode:on_enter前に実行すべき
- MOD: mode:on_before_enter, mode:on_after_return