2015-07-27

語学学習

ふとしたことから新しい言語を学ぶことについて議論をしたのだが、そこからなんとなく自分の中の言語感覚が分かったような気がするのでメモ。単なる駄文。

言語間の距離


日本語は欧州で話される言語から見ると最も遠いところにある言語の一つである。(参照: Language Difficulty Ranking - Effective Language Learning) これはどうしようもない事実なので受け入れるしかない。思えば中学で初めて英語に触れたときに、日本語との類似点を見出せずとにかく意味不明なものであった記憶がある。ELT(今はALTか?)で来てたアメリカ人(だったはず)に「英語好きか?」と
聞かれて「パズルみたいだから、好きだ」と答えたのだが、答えられた方は意味が分からないという顔をしていた気がする。それくらい意思疎通の道具ではなく、何かしらクイズみたいなものだったということである。

オランダではオランダ語が話されているというのはある意味当たり前で、6年も住んでいれば多少は喋れる、理解できるようにはなるのだが(もちろんそれなりには勉強しているが)、僕の中のオランダ語は基本的にオランダ語との距離が近い英語をベースにしている。英語とオランダ語の間は割りと一対一の関係に近い。それとは逆に日本語と英語の間には言語間の意味を言語ではなくイメージで捉えるような抽象的な層があるように思われる。これら3つの言語の関係を図にするとこんな感じになる。
  +-----------------+
  |                 |
  |     Dutch       \
  |                 |\
  +-------++--------+ \
          ||           \
          ||            \                     .........
          ||             \               ...........   ......
          ||              \         .....                 ...
          ||               \    .....                       ...
  +-------++--------+       \ ...                             ..             +-----------------+
  |                 +--------..                                  ..----------+                 |
  |    English      +------..      Abstract language layer        ..---------+    Japanese     |
  |                 +------.                                       ..--------+                 |
  +-----------------+      ..                                      .         +-----------------+
                             ..                                   ..
                               ...                          ......
                                   ..         .. ............
                                     ................
線が多い方が密に繋がっているとする。日本語と英語の繋がりは(例外もあるけど)大体言語ではない何かで繋がっている感じ。なので英語で話してるときは日本語で考えることが辛い。逆もまた叱り。逆にオランダ語は抽象空間を経由しないので割りと簡単に英蘭をスイッチできる。言語間の距離が離れている言語を学習する際はこの違いを吸収する層の構築が重要になるのではないかと思っている。

オランダ語話者から見た英語


そこまで具体的に聞いたわけではないのだが、オランダ語を母語とする人からみた英語というのは母語+α(下手するとマイナスα)くらいの感覚のようである。学校で一応文法や英語の文章がどのように構築されているかとかやるらしいのだが、学校でやったら後は忘れても問題ないくらいで細かいことは気にしなくても喋れるようだ。人の頭の中は覗けないので実際のところはどうかは分からないが、おそらくこれくらいの勢いで繋がっているのであろう。
  +-----------------+
  |                 |
  |     Dutch       |
  |                 |
  +----+++++++++----+
       |||||||||
       |||||||||
       |||||||||
       |||||||||
       |||||||||
  +----+++++++++----+
  |                 +
  |    English      +
  |                 +
  +-----------------+
方言とまでは行かないが、かなり近い言語であることは間違いないのでここまでではないにしろ英語を話せるオランダ人の中ではこれくらい近いものではないだろうか。なんともうらやましい限りである。

オランダでは(多分欧州の多くの国では)高校卒業までに少なくとも2ヶ国語を学ぶのだが(オランダでは英語とフランス語かドイツ語)、どちらの言語も日本語に比べればはるかに近い、それこそ限りなく0に近いレベルなので(言いすぎだが)上記の図の近くにもう一つ言語が追加されるレベルのものだろう。

オランダ語話者から見た日本語


実はこれが本題。当然だがオランダ語と日本語にはほとんど類似点はない。文法、発音、動詞の格変化挙げればキリがないが、本当に何一つない。まぁ、これは英語でもいえることではあるが。これくらい何もないと学習するさいの取っ掛かりがないように思える。平面に点が2つあるだけとかそんなレベル近い気がする。(これは憶測だが)それまで言語学習といえば母語と紐付けて考えることができたものが、いきなり手探りになるので近寄りがたい感じになるのではないだろうか?

個人的には一度抽象化の層を構築してしまうとなんとなく他の言語を学習するコストが下がる気がしないでもない。もちろん発音とか聞き取りとかは物理的な問題があるのでそれなりに時間がかかるが、頭の中の切り替えは大体同じようにできる気がしている。

なんでこんなことを思ったかというと、日本語を話したい言ってはいるがこの取っ掛かりのなさに絶望気味になっているという話しを聞いたのだ。僕はこの絶望も20年前に味わっている+義務教育という性質上克服する以外に逃げ場はなかったのでなんとなくなんとかなってしまったという。ひょっとしたら言語の距離というのは一度克服してしまうと後は楽になるのではないのかなぁ、と思ったのであった。オチなし。

2015-07-16

あとらんだむ

時間があるときにまとめてとも言う。

MSVCとスタック

最近AppveyorというWindows用CIサービスでもCIをし始めたのだが(なぜかx64版が0xc0000005で落ちるのだがなぜだろう、起動すらしてない感じに見えるのだが?)、試用期間が終わって無償版に自動移行した際に起きたスタックオーバーフロー例外が起きるようになった。起きている場所は毎回同じ。移行される前は動いていたテストなので不思議な感じがしていたのだが、原因がCPUが1個にされたためにテストケースがシングルスレッドで走ったため発覚したバグだった。

ちょっと前にCPUが1個のときはテストケースをシングルスレッドで走らせた方が速いということを確認したので、ランタイムでCPUの数を確認して適当にディスパッチさせるようにしたのだが、これが原因(もしくは功を奏したの)だった。問題になったのは10万くらいネストしたリストの書き出し。これくらいネストするとC側のスタックが尽きるのでスタックの深さが既定値(メインスレッドで1MB、子スレッドで64KB)を超えたら&i/o-errorを投げるようにしていたのだが、これが上手いこといっていなかった。MSVCは既定では1MBしかスタックを割り当ててくれないので、リンカオプション(具体的には/STACK)で8MBを指定していたのだが、どうもこのオプションでは上手く行かなかったらしい。とりあえずどれが上手く動くのか分からないのでC_FLAGSとCXX_FLAGSに/Fオプションを渡し、さらにCMakeが持っている2つの内部リンカフラグ両方に/STACKオプションを追加した。これで試すと手元の環境ではスタックオーベーフロー例外は起きなくなったのだが、Appveyor上ではまだ起きる。起動時にスタック領域を制限されるのか、Windowsサーバーの仕様なのかは不明だが起きるものは起きる。

どうしたものかと適当にGoogle先生にお伺いを立てたところ以下の記事を発見。

How to trap stack overflow in a Visual C++ application

_try_exceptを使って例外を捕捉したらVirtualFreeとVirtualProtectでスタック領域を元に戻してやるというもの。これいけるんじゃね?と思い早速追加。

https://bitbucket.org/ktakashi/sagittarius-scheme/src/031c6cbaf9c75df5815b7885a2c9ba5c34bb119f/src/writer.c?at=default#cl-1065

インラインアセンブラの部分は適当に変更(そうしないとx64ではコンパイルできない)。なんとなく動いているようである。

これとStackWalk64を使うとスタックトレースが取れそうな気がしないでもないので、組み込んだら原因不明の0xc0000005の発生場所が特定できるかもしれないなぁ。

タイムゾーン

どうもPOSIXやMSVC付属のCRTにあるタイムゾーン関係のAPIは使いにくい。というか、基本的にはタイムゾーンオブジェクトみたいな風にできないようである。別にシステムの時間とかを変更したいわけではなく、単にある地域のGMTオフセット等が知りたいだけなのだが、そういう使い方がやりづらい。Javaだとjava.util.Timezone(Java8からは非推奨になったけど)等があるのでなんとかできるのだろうとちょっと調べてみた。

結果、やれるけど割りと茨の道。Javaは自前のタイムゾーンDBを持っててそこから情報を取得している。Java8からはtzdb.datというファイルに変更されて、更新があってもとユーザがJREの更新を待つ必要がなくなったというのはあるが、OSから取得していないというところは一緒である。

タイムゾーンの情報は現在はIANAが管理していて、データ及びPOSIXの時間関係のソースはパブリックドメインで公開されている。そこに山があると登りたくなるのが登山家であれば、そこに問題があると何とかしたくなるのがプログラマである。別に面白みがあるものではないが、OSが上手いことAPIを提供してくれないのであれば、自前でなんとかせねばあるまいということである。

とりあえずFTPサーバーからデータファイルを落として、そいつらをS式に落とし込む部分までは書いたので後は落とし込んだ情報を元に時間をなんとかするだけなのだが、そこが面倒ともいう。とりあえずは、GMTオフセットと夏時間が取得できればいいのだが、今週末(というか明日)予定 している0.6.6のリリースには当然間に合わない。ただ、tzdb.dat相当のファイルをリポジトリに入れたくない(自動生成されるファイルを入れたくない)ということから、ここまでは今のうちにやっておかないと(0.6.6が提供するAPIのみで書けるようにしておくという意味)間延びするか、ポリシーに反してリポジトリを汚すかの2択になってしまうので。

IANAのデータ、閏秒のデータも入っているから何とかすれば外だしにできそうなのだが、何を血迷ったのかC側に入れてあるのでちょっと辛い気がしないでもない。まぁ、もう少し機能をScheme側に押し出してやればやれなくもないのだが、どうしようかね。


なんかもう一つ書くことがあった気がしたのだが、完全にわすれたようなのでオチもなくお終い。

2015-07-08

Conditions and stack trace

One of the things I don't want to see is stack traces. Whenever I saw this, I'd always be disappointed. There are basically 2 reasons: the first one is it usually means something went wrong, the second one is sometimes it doesn't show where the condition is actually raised. The first one can be considered a bug so I just need to snatch it. The second one is more for implementations issue.

Currently, Sagittarius shows stack trace whenever default exception handler is invoked, means no guard nor with-exception-handler. The stack trace is collected in the default exception handler. This is fine most of the time since I don't use them that much in my script. (well, I don't argue if it's good or not here.) However, sometimes I want a script fail safe or make sure it releases resources or so. Then I saw the fangs. As a simple example, you have the following script:
(guard (e (else (release-all) (raise e)))
  (open-something-and-fail-it)
  (release-all))
Suppose open-something-and-fail-it may raise an error and you want to release all resources after the process. Now, you saw a stack trace, what would you expect to be shown? I would expect where the root cause is raised. Or at least I can see it. However because the stack trace is collected in the default exception handler, you don't see those information at all.

It was okay until I had written a server program which shows a stack trace whenever unhandled exception is raised. If you write this kind of script, then you want to know which one is the actual culprit, server itself or the application program. As long as I'm writing both, it's just a matter of good old days printf debug but this is pain in the ass. If the stack trace shows where this happened, I don't have to do this type of thing in some cases. So I've changed the timing of collecting stack traces.

The basic idea is whenever raise or raise-continuable is called with compound condition, then it appends stack trace object. Only one stack trace object can be appeared in the compound condition. So if the condition is re-raised, then new stack trace object is created and the old one is set to the new one as its cause. It's pretty much similar with Java's exception. Of course, there are bunch of differences.
  1. Java's exception collects stack trace when it's created
  2. java.lang.Throwable's cause property may have root cause while Sagittarius' only takes stack trace object.
  3. The stack trace object is implicitly added, means given condition is copied entirely while Java's exception can be the same.
The item #1 is the biggest one. On Java, if you throw an exception created before the throw syntax, then the stack trace indicates where it's created. This can be very inconvenient like this type of case:
public class Foo {
  private Exception exn = new Exception();
  public void foo() throws Exception {
    throw exn;
  }
}
Don't ask me who would do this but the stack trace would be shown indicates the second line. (I didn't check the Java's spec, so this might depend on the implementation.)

The item #2 is because of implementation of condition. It flattens the given compound conditions if there are. For example, suppose you catch a condition and re-raise it with adding specific condition. The script would be like this:
;; suppose (fail) raises a compound condition
(guard (e (else (raise (condition (make-some-other-condition) e))))
  (fail))
In this case, the compound condition e will be flattened and merged into the new condition object. R6RS doesn't say how the condition procedure construct compound condition so it can decompose it by need.

The item #3 can be an issue. Suppose you want to re-use a condition and compare it by mean of eq? like this:
(let ((c (condition (make-error) (make-who-condition 'who))))
  (guard (e (else (eq? c e)))
    (raise c)))
;; -> #f
The raise procedure implicitly copy the given condition and adds stack trace object, thus the object wouldn't be the same one. I couldn't find anything mentioning this kind of thing on R6RS. So I think it's still R6RS compliant. However this might cause an issue in the future. (Using exception as a returning value or so?)

The reason why Sagittarius choose to collect stack trace  when condition is raised is because of the requirement of condition procedure. It's not so difficult to make condition procedure collect stack trace however this procedure must be able to take arbitrary number of conditions including compound conditions. So there is no way to specify which one is the actual root cause when multiple compound conditions are given. (Nobody do this? who knows!)

I'm kinda doubting that if I made a better choice. Especially item #3. But seeing irrelevant stack traces is much more painful. So I think I can live with this.

2015-07-03

Webアプリケーションサーバっぽい何か

時間というのは取れるときは取れるものである・・・

今更ながらにWebアプリ的な何かを暇を見つけて書いているのだが(世の中GUIでできるものはクリック一つでできた方が楽ですよ的な軟弱な考えに基づいている)、一ファイルに全部詰め込んで書いてるのが辛くなってきたのでいろいろいい感じに扱ってくれるフレームワーク的なのを書いたのでその紹介(と備忘録)。基本的にはPaellaの上(名前的には下だけど)に乗せてある何か。

極々簡単な何か


Githubから落としてきて以下のコマンドでインストール可能。
$ cmake .
$ make install
インストールしたら適当にアプリを作る。まぁ、こんな感じ。
$ plato-recipe.scm -i -a sample simple-app 
simple-appというディレクトリができているはずなのでそこに移動して、以下のようにサーバ起動。
$ sagittarius run.scm 
これだけやると、http://localhost:8080/sampleにアクセスできるはず。OKと表示されていたら上手いこと動いている。

仕組み


30分足らずで書いた何かなので、仕組みも糞もないくらい簡単ななのだが、端的に言えばディレクトリ決め打ちアプリケーションという感じである。上記の例だと以下のようなディレクトリ構造が作成される。
simple-app/
  + lib/
  + apps/
      + sample/
          + handler.scm
  + run.scm
handler.scmがマウントポイントのエントリーポイントになる。詳しくはソース見た方が早いレベル。

欲しげな機能


  • セッション管理の機能とか。現状クッキーを扱うライブラリとかないのでそこから作らないといけないという。
  • 設定ファイル的な何か。JEEでいうweb.xml的のがあるといいかなぁ。
  • マウントポイント別環境。
  • サブパス。今のところ一番sample/fooみたいなことができない。いるかどうかは別だが・・・

ちょっとCygwin上で動かしたらselect (2)の実装の違いにより動かなかったので、現在のHEADが必要。Linuxなら0.6.5でも問題なく動くと思う。

2015-07-02

プロセス間通信とセマフォ

書こうと決めてから1週間近くたってしまった。時間とは取れないものである。

Sagittariusにプロセス間通信用の共有メモリとその排他制御用のセマフォを入れた話。

プロセス間通信なんてソケットでもファイルでも何でもいいんだけど、もう少し軽量なものがあるといいかと思い共有メモリを入れた。っで、一つの資源に対して二つ以上の場所からアクセスがあると排他制御がいるということで、プロセス間で使えるセマフォを入れた。とりあえずこんな感じで使える。
;; process A
(import (rnrs)
        (sagittarius process)
        (sagittarius threads))

;; create a semaphore with initial counter 0
(define sem (make-semaphore "/waiter" 0))

;; opens shared memory. if this is the first process then it'll be created
;; with the size 256.
(define shm (open-shared-memory "/shared-memory" 256 (file-options no-fail)))

;; wait for the other process
(semaphore-wait! sem)

;; something was delivered
(let ((bv (shared-memory->bytevector shm)))
  (print (utf8->string bv 0 5)))

;; close and release
(close-shared-memory shm)
(semaphore-close! sem)
(semaphore-destroy! sem)

;; process B
(import (rnrs)
        (sagittarius process)
        (sagittarius threads))

;; open the semaphore created by the other process
(define sem (open-semaphore "/waiter"))

;; this must specify no-truncate option otherwise it may fail
(define shm (open-shared-memory "/shared-memory" 256 (file-options no-truncate)))

(let ((bv (shared-memory->bytevector shm)))
  ;; put some value
  (bytevector-copy! (string->utf8 "hello") 0 bv 0 5))

;; notify the other process
(semaphore-post! sem)
プロセスAは入力待ちプロセス。プロセスBは共有メモリ/shared-memoryに何か書き込んでセマフォのカウンタを一つ増やす。

正直共有メモリだとシェルから使いにくいのであんまり嬉しくないのが悲しいところ。本当は名前付きパイプも入れたいのだが、POSIXとWindowsでモデルが大分違うので上手いAPIを思いつかないでいる。(作成を考えなければどちらのモデルでも名前付きパイプは単なるファイルと変わらないので扱えなくはないのだが、せっかくなので何かほしいという話。)

なんでこんなのが入ったかといえば、最近仕事で使うツールを自前サーバに載せているのだが、サーバの再起動をリモートREPLからできると楽かなぁと思ったからというのがある。正直どちらもあまり上手く使われていないので今のところそこまでの恩恵がないという話もある・・・