Guile では call-with-input-file の proc 内でエラーが起きるとポートが閉じられない件

問題

あんまり問題とは言えないかもしれないが、 (proc port) 内でエラーが起きた場合、ポートを閉じる手続きが呼ばれない。
助けてドラえもん!!

とはいえ、プログラムが終了する時に全てのポートは自動的に閉じられるとかどこかに書いてあった気がするので、 そんなに問題にはならないかもしれないんだけど、Ruby で言うような File.open :

# ものすごく大雑把な File.open() の実装:
class File
  def open(path, mode = "r", perm = 066)
    f = File.new( path, mode, perm )
    yield f
  ensure
    f.close
    # 何らかのエラーが起こったとしても、ensure 節で
    # 絶対に閉じられるため、安心。
  end
end

と同じようなことをしたくなった時に困るので、考えてみたいと思う。

検証

Gauche においての call-with-input-file

試しに Gauchecall-with-input-file を見てみる:

(define-in-module scheme (call-with-input-file filename proc . flags)
  (let1 port (apply open-input-file filename flags)
    (unwind-protect (proc port)
      (when port (close-input-port port)))))

これは:

(define (call-with-input-file filename proc . flags)
  (let1 port (apply open-input-file filename flags)
    (unwind-protect (proc port)
      (when port (close-input-port port)))))

と一緒。

unwind-protect というなんかすごそうなシンタックス?で守られてる。 でも Guile にはそんなものはない。

Guile においての call-with-input-file

Guile においての call-with-input-file は 2 つある。 1 つ目は R5RS の call-with-input-file だけど:

(define* (call-with-input-file
          file proc #:key (binary #f) (encoding #f) (guess-encoding #f))
  "PROC should be a procedure of one argument, and FILE should be a
string naming a file.  The file must
already exist. These procedures call PROC
with one argument: the port obtained by opening the named file for
input or output.  If the file cannot be opened, an error is
signalled.  If the procedure returns, then the port is closed
automatically and the values yielded by the procedure are returned.
If the procedure does not return, then the port will not be closed
automatically unless it is possible to prove that the port will
never again be used for a read or write operation."
  (let ((p (open-input-file file
                            #:binary binary
                            #:encoding encoding
                            #:guess-encoding guess-encoding)))
    (call-with-values
      (lambda () (proc p))
      (lambda vals
        (close-input-port p)
        (apply values vals)))))

2 つ目は R6RScall-with-input-file で:

(define (call-with-input-file filename proc)
  (call-with-port (open-file-input-port filename) proc))

便利な call-with-port が付いて………:

(define (call-with-port port proc)
  "Call @var{proc}, passing it @var{port} and closing @var{port} upon exit of
@var{proc}.  Return the return values of @var{proc}."
  (call-with-values
      (lambda () (proc port))
    (lambda vals
      (close-port port)
      (apply values vals))))

やっぱ、ダメじゃねぇか!!!
やっぱり call-with-values 使ってんじゃねーかァァァァァァァァァァァァァァ!!!!!!!!!!!!!!!

マジでそうなの?

Guile で call-with-values を使ってるけど、本当に後の処理(ポートを閉じる処理)が呼ばれないのか検証してみた。

(define error-happen? #t)

(call-with-values
    (lambda ()
      (when error-happen?
      ;;;
      ;;; producer で起こったエラーはそのまま突き抜けてしまう。
      ;;; 
        (error "*Oops!* teleporter"))
      (values 4 5))
  (lambda (a b)
    (display "handler")
    (newline)))

上記のコードを demo-call-with-values.scm に保存して実行してみた結果が以下である:

% guile ./demo-call-with-values.scm
Backtrace:
In ice-9/boot-9.scm:
 160: 7 [catch #t #<catch-closure 1cac460> ...]
In unknown file:
   ?: 6 [apply-smob/1 #<catch-closure 1cac460>]
In ice-9/boot-9.scm:
  66: 5 [call-with-prompt prompt0 ...]
In ice-9/eval.scm:
 432: 4 [eval # #]
In ice-9/boot-9.scm:
2404: 3 [save-module-excursion #<procedure 1cce9c0 at ice-9/boot-9.scm:4051:3 ()>]
4058: 2 [#<procedure 1cce9c0 at ice-9/boot-9.scm:4051:3 ()>]
In /home/rihine/workspace/guile-cwv/./demo-call-with-values.scm:
  10: 1 [#<procedure 1d9ebc0 ()>]
In unknown file:
   ?: 0 [scm-error misc-error #f "~A" ("*Oops!* teleporter") #f]

ERROR: In procedure scm-error:
ERROR: *Oops!* teleporter

上記の通り、 handler とは出力されていない(そりゃそーだ)。 替わりにテレポーターに引っかかってしまった。

解決法みたいなもの

すごく簡単な解決方法として、 Guile で unwind-protect を実装するというものが考えられる。
あるいは、 finally のようなものを継続を使って実装する、とか。

そういえば、 catch の後にポートを閉じれば同じようなことができるはず。

バックトレースとか、スタックフレームのこと

さて、現在のスタック・トレースを得るには make-stack 関数を呼び出します。
引数の #t を忘れないようにしてください:

(define a-stack (make-stack #t))

a-stack の型をとりあえず <stack> としましょう。
<stack> オブジェクトの中には <frame> オブジェクトが幾つか含まれています。
<frame> オブジェクトを取り出すには stack-ref 関数を使います。

また、<stack> オブジェクトの中に含まれている <frame> オブジェクトの数は stack-length 関数で知ることができます。
<stack> オブジェクトの中に含まれている全ての <frame> オブジェクトをリストとして得たいということだってありますよね。
stack->frames 関数を使えば、それができます。
これは、guileソースコードの中のテストコードから取ってきたものです:

(define (stack->frames stack)
  "Return the list of frames comprising STACK."
  (unfold (lambda (i)
            (>= i (stack-length stack)))
          (lambda (i)
            (stack-ref stack i))
          1+
          0))

<frame> オブジェクトに対しては frame? だとか frame-procedure などの関数が使えます。
が、関数を呼んでいるファイルパスやら行数などはなんかとれなさそうです。

Why Guile People!!!??

1 の脳内で動くと思われる Uva

オレオレ言語の Uva 熱が続いているので Uva のことについて書く。

Uva って何?

Uva は Ceylon をベースにしています。
Ceylon は結構良い構文だし、Uva*1 は Ceylon のサブセットらしいので、そんな感じの名前にしました。
多分、色々と Ceylon と違うくなるはずです…………。

変数定義

Uva は識別子の前の方に型を書く言語です。
hoge という整数型の変数を定義してみましょう:

int hoge;                 // Syntax Error!

さて、これはシンタックスエラー*2です。
Uva の変数はデフォルトで変更不可なので、上記のようにすると hoge に値を再設定*3できません。

変更可能な変数にはアノテーションとして variable を付けます:

variable int hoge;        // OK

暗黙的に型指定されるローカル変数

コンパイル時に型推論されるようにするには C# であれば このように書きます:

var hoge = 42;

Ceylon では:

value hoge = 42;

と書きますが、Uva では:

auto hoge = 42;

です。

variable の後に value と付くことも考えられるので、var が被ってしまうのは美しくないような気がします。
なので、C++ から取って auto としました。

また、auto は関数の戻り値の型としても使えます*4

auto plus(int a, int b) => a + b;             // plus の型は int(int, int)

Uva での auto は疑似型*5で、キーワードです。
どのような型の値でも格納できる型は別にあります。
それが any です。

anyAnythingエイリアスです。
uva.language 名前空間に恐らく以下のように定義されているはず*6です。

alias any => Anything;

(๑´ڡ`๑) おしまい

*1:tea の方

*2:Warning でもいいかもしれないけど、再設定できないわけで意味がない

*3:代入と書いても良い気がする

*4:使えるといいなーって思ったんだけど、できるかどうかは不明

*5:コンパイル時に実際の型が決定されるのであって、auto という型は存在しない

*6:あるいはそのように定義されているように処理系が振る舞う

uim が起動しない件

半角全角キーを押しても、全角入力モード?に切り替わらない。
不思議だなーと思って

% ps ax | grep uim-xim

と打ってみると、見つからなかった。

.xsession にはこのように書いたんだけど:

# -*- mode: shell-script; coding: utf-8; -*-
export GTK_IM_MODULE='uim'
export QT_IM_MODULE='uim'
uim-xim &
export XMODIFIERS='@im=uim'

# # 
# uim-toolbar-gtk3 &
# # 
# uim-toolbar-gtk3-systray &

どうも、.xsession が実行されていないらしい。

% echo $GTK_IM_MODULE

% echo $QT_IM_MODULE

% echo $XMODIFIERS

.xsession どころか .xinitrc.xprofile にも同じように書いているのに、実行されていない様に見える。
GDM の X セッションをどうにかするらしい /etc/gdm/Xsession にはこのように書いてある:

# First read /etc/profile and .profile
test -f /etc/profile && . /etc/profile
test -f "$HOME/.profile" && . "$HOME/.profile"
# Second read /etc/xprofile and .xprofile for X specific setup
test -f /etc/xprofile && . /etc/xprofile
test -f "$HOME/.xprofile" && . "$HOME/.xprofile"

ので、.xprofile は読み込まれているはず……。

Steam が動くようになった件

ただし、以下のコマンドで、だけど。

% STEAM_RUNTIME=0 steam

STEAM_RUNTIME=0 は Steam 側のライブラリ*1じゃなくて、/usr/lib のライブラリを使うというスイッチだよ。

あと、cef_extentions.pak はどうしてもどうやって取得すればいいのかわからなかった*2ので、
外付け HDD の Windows にインストールされていた cef_extensions.pak をコピペしたらロードできないエラーは無くなった。

steamclient.so がどーたらこーたら言っていたので、そーいえば、Steam が依存している全てのライブラリを steam-libs でインストールできたっけなーと思い出したので:

% yaourt -S steam-libs

と打ってみた。
案の定、まだインストールしていないライブラリがあった。

で:

% STEAM_RUNTIME=0 steam

って打ってみたら、ログイン画面が出た*3

f:id:noqisofon:20160923230426p:plain

キタ━━━━(゚∀゚)━━━━!!

参考

*1:.so 的な意味で

*2:めんどくさかったというのもある

*3:一旦 ~/.local/share/Steam を全部削除してしまったせいで、ログイン情報も抹消されてしまったんだと思う

最近よく使ってるコマンド

そのいち

これかなぁ:

% find -name '*.zip' -exec unzip -O cp932 {} \;

元の unzip がそうなのかはよくわからないけれど、unzip-iconv では
unzip <ファイル名> -O cp932 ってやると、-O cp932 が無効なオプションだよって言われるのでとっても悲しい。

そういうわけなので、unzip <ファイル名> ってタブってから、-O cp932 とか付けたりする。
unzip -O cp932 と先に付けてしまうとタブが効かないのでもっと悲しい。
Zip ファイルが 1 つならいいけれど、複数ある場合もあるので、これを使って全部解凍している。

そのに

これ:

% STEAM_RUNTIME=0 steam

または:

% LIBGL_DEBUG=verbose STEAM_RUNTIME=0 steam

まだ、解決していない。

また Steam が動かなくなった件

カテゴリに Arch_Linux って書いてあるけど、私が使っているのは Antergos なんだ。
とにかく、steam を実行するとこんなメッセージが出る:

% steam
/home/rihine/.local/share/Steam/steam.sh: 行 154: VERSION_ID: 未割り当ての変数です
/home/rihine/.local/share/Steam/steam.sh: 行 154: VERSION_ID: 未割り当ての変数です
Running Steam on antergos  64-bit
/home/rihine/.local/share/Steam/steam.sh: 行 154: VERSION_ID: 未割り当ての変数です
STEAM_RUNTIME is enabled automatically
Installing breakpad exception handler for appid(steam)/version(1471977975)
libGL error: unable to load driver: i965_dri.so
libGL error: driver pointer missing
libGL error: failed to load driver: i965
libGL error: unable to load driver: i965_dri.so
libGL error: driver pointer missing
libGL error: failed to load driver: i965
libGL error: unable to load driver: swrast_dri.so
libGL error: failed to load driver: swrast

また Steam が動かないインシデントだ!と思って、remove-bad-steam-libs を実行してみた。
こっちにも書いてあるように、エイリアスとして remove-bad-steam-libs を定義してる。

### Steam を動かすために必要
#
# --------------------------------------------------------------------
alias remove-bad-steam-libs='find ~/.steam/root/ \( -name "libgcc_s.so*" -o -name "libstdc++.so*" -o -name "libxcb.so*" \) -print -delete'
alias remove-bad-steam-libs-local='find ~/.local/share/Steam/ \( -name "libgcc_s.so*" -o -name "libstdc++.so*" -o -name "libxcb.so*" \) -print -delete'

原因は忘れたけど、Steam の中の(・∀・)イクナイ!! .so ファイルを削除すると、Steam が高い確率で動くようになる。
……んだけど、やってもダメだった。

これはまいった。
最初に Steam が動かなくなったときも、上と同じ libGL error が出ていたように思う。

上記のエラーメッセージで言っている i965_dri.so を find してみると:

% find /usr -name 'i965_dri.so'
/usr/lib32/xorg/modules/dri/i965_dri.so
/usr/lib/xorg/modules/dri/i965_dri.so

64 bit 版も 32bit 版も存在する。
ちなみに、swrast_dri.so もこの中にある:

% ls /usr/lib32/xorg/modules/dri/
i915_dri.so        nouveau_dri.so        r300_dri.so    radeonsi_dri.so 
i965_dri.so        nouveau_vieux_dri.so  r600_dri.so    swrast_dri.so
kms_swrast_dri.so  r200_dri.so           radeon_dri.so  virtio_gpu_dri.so

過去の私はこれをどーにかして動かしていたらしい。
いったい、どーやったんだ?!

と思ったら、STEAM_RUNTIME=0 steam したときのログに cef_extensions.pak をロードできなかった的なことが書いてあるのに気がついた。

/home/rihine/.local/share/Steam/steam.sh: 行 154: VERSION_ID: 未割り当ての変数です
/home/rihine/.local/share/Steam/steam.sh: 行 154: VERSION_ID: 未割り当ての変数です
Running Steam on antergos  64-bit
STEAM_RUNTIME is disabled by the user
Installing breakpad exception handler for appid(steam)/version(1471977975)
Installing breakpad exception handler for appid(steam)/version(1471977975)
Gtk-Message: Failed to load module "canberra-gtk-module"
Installing breakpad exception handler for appid(steam)/version(1471977975)
[0913/173630:ERROR:main_delegate.cc(779)] Could not load cef_extensions.pak
[0913/173630:ERROR:browser_main_loop.cc(217)] Running without the SUID sandbox! See https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the sandbox on.
Installing breakpad exception handler for appid(steamwebhelper)/version(20160823182455)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471976695)
[0913/173630:ERROR:main_delegate.cc(779)] Could not load cef_extensions.pak
Installing breakpad exception handler for appid(steamwebhelper)/version(20160823182455)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471977975)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471977975)
../common/steam/client_api.cpp (331) : Assertion Failed: ClientAPI_InitGlobalInstance: InternalAPI_Init_Internal failed, most likely because you are missing a 32-bit dependency of steamclient.so (the Steam client is a 32-bit app).

Assert( Assertion Failed: ClientAPI_InitGlobalInstance: InternalAPI_Init_Internal failed, most likely because you are missing a 32-bit dependency of steamclient.so (the Steam client is a 32-bit app).
 ):../common/steam/client_api.cpp:331

Installing breakpad exception handler for appid(steam)/version(1471977975)
crash_20160913173630_5.dmp[7571]: Uploading dump (out-of-process)
/tmp/dumps/crash_20160913173630_5.dmp
SteamStartup.cpp (838) : Assertion Failed: ! "There was a problem with your Steam installation.\n" "Please reinstall steam.\n"
crash_20160913173630_5.dmp[7571]: Finished uploading minidump (out-of-process): success = yes
crash_20160913173630_5.dmp[7571]: response: CrashID=bp-bbaf019d-9bcc-4a93-80fb-d7f282160913
crash_20160913173630_5.dmp[7571]: file ''/tmp/dumps/crash_20160913173630_5.dmp'', upload yes: ''CrashID=bp-bbaf019d-9bcc-4a93-80fb-d7f282160913''
[0913/173631:ERROR:main_delegate.cc(779)] Could not load cef_extensions.pak
[0913/173631:ERROR:browser_main_loop.cc(217)] Running without the SUID sandbox! See https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the sandbox on.
Installing breakpad exception handler for appid(steamwebhelper)/version(20160823182455)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471976695)
[0913/173631:ERROR:main_delegate.cc(779)] Could not load cef_extensions.pak
Installing breakpad exception handler for appid(steamwebhelper)/version(20160823182455)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471977975)
Installing breakpad exception handler for appid(steamwebhelper)/version(1471977975)
[2016-09-13 17:36:30] Startup - updater built Aug 23 2016 18:24:59
[2016-09-13 17:36:30] Verifying installation...
[2016-09-13 17:36:30] Verification complete
[2016-09-13 17:36:31] Shutdown

あと、「なんかインストール時に問題起こったから、Steam を再インストールお願いプリーズ(意訳)」って書いてあるように見える。

% find ~/.steam/root/ -name 'cef_extensions.pak'

確かに cef_extensions.pak が無いかもしれない…………。
~.local/share/Steam を削除して steam を実行してみたが、やっぱり cef_extensions.pak が無かったのでダメだった。

( ◠‿◠ ) cef_extensions.pak をロードできませんでした♡

\(^o^)/オワタ

なぜこの書き方がいいの?

LiveScript では、普通の文字列の別の書き方として \hoge というのができる。

      SliderService.add-column account.serv, account.name, type, params, (ok, msg) !->
        if ok
          console.log 'create column done.'
          respond { result: \ok, content: { column: msg } }
        else
          console.log "create column failed, error: #{msg}"
          respond { result: \error, reason: 'failed to create the column' }

他の人はどう使っているのかわからないけれど、RubyLisp にあるキーワード的な使い方をしている。

LiveScript では add-column みたいなのは、JavaScript に変換する時に addColumn みたく camelCase になるので
ハイフン大好きマン*1な私としてはとてもうれしい。

ちなみに \slot-nameJavaScript に変換されると 'slotName' になる。
このため、どうしてもアンダーバーで区切りたい文字列には普通に \delete_account と書くことになるが、これはこれで良いと思う。

*1:で、JavaScript では camelCase したい

めっちゃシンプルな更新監視型ビルドツールが欲しい

Hotot3 の core/scriptsCoffeeScript で書かれているので、LiveScript で書き直したりするようなことをしていた。

じゃっばすくりぷよにコンパイルする時に lsc--watch 付けてやってたんだけど、コンパイルに失敗するとわけ不明なエラーを吐いて落ちる。
これが一番ムッカーって来たような感じ。

それで他のツールを探してみたんだけど、 Ruby の Guard とか Watchr とかなんかエラー起こるし、よくわからないので、 全然更新を監視しないんだけど、LiveScript ファイルを取ってきて全部コンパイルする Ruby すくりぷよ書いた。

# -*- mode: ruby; -*-

def watch(pattern)
  found_files = Dir.glob pattern

  yield found_files
end

def echo_and_system(command)
  puts command
  system command
end

watch 'core/scripts/*.ls' do |matching|
  matching.each do |filename|
    echo_and_system "lsc --no-header --bare -c #{filename}"
  end
end

単に watch メソッドを呼びだすと更新されたほげファイルを見つけてブロックを実行してくれるやつが欲しい。

Socket.IO やってみた

const Http      = require( 'http' );
const Express   = require( 'express' );
const Socket_IO = require( 'socket.io' );

const app = Express();

const server = Http.createServer( app );

const io = Socket_IO( server );

const port = 3000;

app.get( '/', (request, response) => {
    response.sendFile( __dirname + '/client/index.html' );
} );

server.listen( port, () => {
    console.log( 'server listening at port %d', port );
} );

io.on( 'connection', socket => {
    console.log( 'a user connected' );

    socket.on( 'chat message', message => {
        console.log( `message: ${message}` );
    } )

    socket.on( 'disconnect', () => {
        console.log( 'user disconnected' );
    } );
} );

a user connected が表示されない………。
\(^o^)/オワタ

const server = require( 'http' ).createServer();

と:

const Http = require( 'http' )

const server = Http.createServer();

は一緒のはずだよね???