Haskell で Ctrl-C を制御する(Windows)

Ctrl-C 等の割り込みの扱い方です。

tl;dr

System.Win32.Console.CtrlHandler を使います。


Ctrl-C が押されたらクロージングの処理を伴って終了するプログラムを書いてみます。

import Control.Concurrent
import Control.Monad
import System.Exit
import System.IO
import System.Win32.Console.CtrlHandler

main :: IO ()
main = do
    tid <- myThreadId
    let
      handler event = do
        if event == cTRL_C_EVENT
          then do
            putStrLn "goodbye!"
            killThread tid
            pure True
          else
            pure False
    pHandler <- mkHandler handler
    success <- c_SetConsoleCtrlHandler pHandler True
    when (not success) $ do
      putStrLn "SetConsoleCtrlHandler failed"
      exitFailure

    let
      loop n = do
        putStr $ show n ++ ", "
        hFlush stdout
        threadDelay 1000000
        loop (n+1)
    loop 0

実行してみましょう

> runhaskell Main.hs
0, 1, 2, 3, 4, 5, goodbye!
Main.hs: thread killed

ちゃんと goodbye と出力されて終了しました!killThread は例外を伴って終了するので Main.hs: thread killed というメッセージが出てしまっています。もし気になるなら例外を握りつぶすか MVar を使って終了を監視する仕組みを作るといいでしょう。

肝心の割り込みを制御する関数は c_SetConsoleCtrlHandler です。

c_SetConsoleCtrlHandler :: PHANDLER_ROUTINE -> BOOL -> IO BOOL
mkHandler :: Handler -> IO PHANDLER_ROUTINE
type BOOL = Bool
type Handler = CtrlEvent -> IO BOOL
type PHANDLER_ROUTINE = FunPtr Handler

c_SetConsoleCtrlHandler の第1引数にはコールバック関数(のポインター)を、第2引数には追加時に True を、削除時に False を指定します。

CtrlEvent の値は予め用意されていて下記があります。

cTRL_C_EVENT :: CtrlEvent
cTRL_BREAK_EVENT :: CtrlEvent

Handler の返り値は、イベントを処理して次のハンドラーにイベントを伝播させないなら True を、そうでないなら False を指定します。上の例では handler という関数にメッセージの表示とメインスレッドの停止の処理を書いて Handler として渡していました。


この記事は HaskellでCtrl-Cを制御する - Qiita のオマージュです。

Help wanted

runhaskell を使うと確かにこの動作をするのですが、ビルドして実行バイナリーを作ると Ctrl-C を2回打たないと止まりません。原因と背景を説明できる方いませんか?

> stack exec windows-interruption-exe
0, 1, 2, 3, goodbye!
goodbye!
windows-interruption-exe.EXE: thread killed

あと、Ctrl-Break だとハンドルせず終了するはずだけどしないような……

ちゃんと C のドキュメント読もう。→ 読んだ。理解は合ってそう。(追記)

議論は Haskell-jp Slack で。参加方法はこちら

Windows で Haskell iconv をビルドする

GHC 8.0 以前についてはこちらを参考に。

teratail.com

GHC 8.2 以降で stack を使う場合をここではとりあげる。

確信はないのだが、GHC 8.2 から GHC 自体が iconv に依存しなくなったのか、$(stack path --programs)\ghc-8.0.2\mingw\lib から libiconv.alibiconv.dll.a がなくなっているため GHC 8.0 以前のようにビルドができなくなっている。

なので、まず libiconv を取得する。

stack exec -- pacman -S libiconv-devel

インストールされる場所は stack がデフォルトでは見に行ってくれないので明示してやる。MSYS バージョンは将来的に変わることもあるだろう。

stack build --extra-include-dirs="$(stack path --programs)\msys2-20180531\usr\include" --extra-lib-dirs="$(stack path --programs)\msys2-20180531\usr\lib"

これでコンパイルはできるがリンクに失敗する。

`libiconv_open' に対する定義されていない参照です

iconv パッケージの cabal ファイルを編集してやる。

--- a/iconv.cabal
+++ b/iconv.cabal
@@ -26,7 +26,7 @@ library
   includes:        hsiconv.h
   include-dirs:    cbits
   c-sources:       cbits/hsiconv.c
-  if os(darwin) || os(freebsd)
+  if os(darwin) || os(freebsd) || os(windows)
     -- on many systems the iconv api is part of the standard C library
     -- but on some others we have to link to an external libiconv:
     extra-libraries: iconv

iconv を利用するプロジェクトの stack.yaml で、編集後のリポジトリーを extra-deps に指定して stack build するとビルドできる。

Leica M3

Lomography Konstruktor F・Diana Mini から始まったフィルムカメラ熱ですが一瞬で行くところまで行ってしまった感じです。

Leica M3。

現在まで続く M 型 Leica の始祖を買ってしまいました。1962年製の56歳です。

(この写真を撮ったレンズも Asahi Super Takumar 55mm F1.8 なので60年代のレンズです。)

Leica 意識前

初めはフィルムカメラの入門機としていくつかの記事に紹介されていた Asahi Pentax SP か Olympus OM-1M-1)かと考えて中古カメラ屋めぐりをしていました。

このころはひたすらサンライズカメラ*1の記事を読みあさっていました。

めぐったのは新宿・中野・秋葉原御徒町・上野の次のお店です。(抜けがあるかも。)

  • 新宿
    • マップカメラ
    • 新宿中古カメラ市場
    • 中古カメラボックス
    • ラッキーカメラ
    • アルプス堂
    • レモン社
  • 中野
  • 秋葉原御徒町・上野
    • 東京カメラ
    • 喜久屋カメラ
    • 秀光
    • 千曲カメラ

東京は中古カメラ屋さん充実してますね。

で、東京カメラで Asahi Pentax SP か Olympus OM-1 が気になるんですよという話をしていたはずなのに、店員さん(昨日カメラさん)と話していたらいつのまにかどれが至高の Leica なのかとう話になり、帰るときにはそれぞれの Leica がどう違うのか調べないといなという気持ちになっていました。(Bessa R3M 気になってるとか言った気がするからかな。)

値段でいうと Asahi Pentax SP か Olympus OM-1 が3万あればレンズ含め買えるのに対して、Leica は M 型だと少なくとも10万円で本体が買えるかどうかというレベル。

どうしてこうなった。

Leica 意識後

それから Leica が気になって、昔の雑誌などを読んで(スキャンされたのが Kindle で出版社から売ってある)、やっぱり欲しいなぁという気持ちになったので、世界の中古カメラフェアで買うことにして買いました。

露出計付きの M6 にしようかと思ったんですけど、M6 修理してくれるところがほとんどないので、修理してもらえる M3 に外付け露出計を使った方がよいと昨日カメラさんから助言をもらい、M3 にすることにしました。

東京カメラには悪いんですけど(東京カメラの買うか悩んでいたのが売れてしまっていたのもあって)、結局早田カメラで買いました。(東京カメラにはお世話になったのでまたレンズなんかを買いに行きます。)

早田カメラは自店でオーバーホールもしていて永年保証してもらえるのがすごくいいですね。

25万円以上して綺麗な見た目のものと、傷・当たりがあるけど14万円のものがあって、さすがに25万円以上は払えないなぁと14万円の方にしました。

早田さんとお話ししたところによると、5年から10年ぐらい使うとシャッター音が変わったなということがあるので、そうすると修理に持ってきてほしいとのことでした。

レンズは Elmar f = 9 cm 1:4 にしました。元々 APS-C で 55mm が好きだったのでじゃあ 90mm かなということでそうしました。Leica だと 35mm や 50mm が人気らしいですね。レンズ前玉に拭き傷と塗装剥げのせいで、無保証の B 級品の2.5万円ほどとやすいものでしたが、特に写りには問題なさそうです。

写真

フィルムは、富士フイルム 記録用 ISO 100 だったはず。

スキャン後、切り抜きだけしています。(理由は後述。)

これ以降は Lomography Color Negative 400。

使ってみて

9cm のレンズを使ったときのパララックスが結構ひどくて次の写真が分かりやすいんですが、これファインダーでは左右対称に撮ったはずなんですよね。左側に大きくずれています。それで上記の画像の何枚かは切り抜きしています。

f:id:kakkun61:20180930005458j:plain

それが分かってからは気持ち右にずらして撮っています。

あとは、レンジファインダーの二重像がちょっと上下にずれてて、それさえなければなという感じですね。これは本体を買うときにもっとお金を積むかどうかの問題ですね。

追記:上下像ずれはレンジファインダー機ならすぐ直せるようになっているらしく、数分で直してもらえました。

レンズとしてはゴーストが大きく出ますね。

なので、後で新宿中古カメラ市場でフードを買い足しました。Leica 純正フードが高いのなんの。金属の筒が1万円する。中国とかでプラスチックで作ってくれないかなという気がしてきますね。そうするとたぶん1000円もしないでしょう。量が売れないから高くなるかな?

あと、シャッター音は唯一オペラハウスでの使用が認められた静かさとのことですが、なんだか自分には腑抜けた音に思えます。別にいいんだけど。

その後

Elmar 50mm 1:2.8 が増えました。早い。

9cm 使っててやっぱり窮屈だなと思ってしまって。APS-C 55mm との差はわりとありました。これはレモン社で6.5万円ほどで買いました。(東京カメラじゃないんかい。9cmのフードを一緒に買いたくて新宿に行っちゃったので。)

50mm 使うときの問題としては、自分が眼鏡をかけているのでファインダーのブライトフレームがケラれてしまうことですね。視度補正レンズが Leica やマップカメラから出ているなんですけど -3 が限度で、どうも自分は -4 よりきついみたいなのでムリそうですね。今は眼鏡をかけてピントを合わせた後、眼鏡を上げて裸眼でぼんやり像でフレームしてます。これもうバルナックと手間変わらないな?

50mm の写真

フィルムは富士フイルム 記録用カラーフィルム ISO 400。F2.8 で ISO 400 なら夜も平気ですね。だいたい1/125秒で撮ってました。現像してみるまで本当に撮れているのか半信半疑でしたが。

夜の新宿です。

フィルムしてみて

フィルム代と現像・スキャン代が想像以上にかさむ……

やすい記録用フィルムが1本330円ほどで現像・スキャンが1本1000円。8月末にフィルムを始めてもう12本撮っていて、となると1.6万円。高え……

スキャンだけでも家でと考えてヨドバシカメラでスキャナーの話を聞いてきたんですが、スキャン時間がかなりかかる(1フレーム2分とか)みたいで断念。2分×36枚で72分しかもその間放置ではダメで作業が生じる。

デジカメでフィルムを撮るのはほこり除去が大変でおすすめしないと言われたが、実際どうなんでしょう?

しばらく撮りまくって満足したらデジカメの比率を上げることにします。

使ったフィルムは次の6種類。正直ネガは利きフィルムされてもどれがどれか分からないと思います。

使ってないけど買ってあるのは次の4種類。

さて次はどこに撮りに行きますかね?

*1:なぜ URI は sunset camera なのか?

プラモデルカメラ Lomography Konstruktor F

本来はフジヤカメラに行くことが目的だった。

カメラバカにつける薬 in デジカメ Watch」でも取り上げられてて、その前から気になってたのもあって中野に行った。

特に買うつもりもなかったので雰囲気だけ知れればいいやというようにそそくさと出たのだが、ジャンク館が中野ブロードウェイにあるということで行ってみた。

そういえば中野ブロードウェイも気になってたんだし一石二鳥だった。

するとコイデカメラがあった。Lomography Konstruktor F があった。

動くしくみを理解しながら自分で組み立てるのが好きでカメラも好きな自分が惹かれるのは当然という感じだった。

そのときは散財する予定はなかったしそのまま帰ったんだが高々4000円ぐらいだし買ってもよかったなと後から思った。

帰って Lomography を調べてその写真にやられてしまった。買うしかなくなった。

直営店 Lomography+ が末広町にあるみたいだからせっかくだしそこで買うことにしつつ、メルカリで同じく Lomography のトイカメラの Diana Mini も買った。

前書きはこの辺りにして、これが組み立てる前の Konstruktor F だ。

おしゃれな箱に入っているがちょっと雑な作りで中身の精度が心配になる。しかし、中身は削ったりお湯で曲げたりしなくてもちゃんと嵌まる精度だ。

材質はランナーパーツが ABS と POM。ランナーに付いていない部品がおそらく PS だろう。

ABS はねばりのある素材で、POM は摩擦係数の低い素材である。ABS は昔から、POM は最近のミニ四駆でも使われている。PS はプラモデルで一般的な素材で塗装がしやすい。

そう塗装だ。

ただ組み立てるだけじゃおもしろくない。せっかく自分好みにいじくれるのだから何かしたい。ということで、塗装することにした。

塗装で注意するのは、ABS と POM にはプライマーというものを事前に塗装すること。そうしないと、塗装がパラパラと剥がれてしまう。そしてそのプライマーは PS には絶対に塗装しないこと。プライマーは PS を溶かし、ひどいときには中に浸透し脆くしてしまう。

難しい場合は、PS のパーツつまりランナーに付いていない外装のパーツだけを塗装するのがいいと思う。

もう1点注意するのは、内側には塗装しないこと。これは自分が失敗してしまったんだが、塗装してしまうとギアの回りが悪くなる箇所がある。ちゃんとマスキングをしよう。自分は事前に下記の記事で注意されていることを読んだにもかかわらず雑に塗装してしまい、調子が悪い原因を特定してそこでやっと注意を思い出した。頭が悪い。

pentaxian.hatenablog.com

後は組み立てなんだが説明書が分かりやすいとはいえない。プラモデルメーカーの組み立て説明書がいかに分かりやすいのかが分かる。

しかし、説明書と現物とそのしくみを考えれば組み立て自体はすぐ終わる。

ここでも注意点があって、1つは下記の記事でも言及されているがねじを留めるところの説明が1箇所抜けている。この記事は1つ前のモデルの名前に F が付かないもので、ここで言及されている誤謬のうちそのねじ留めの箇所1点以外は改修されている。

av.watch.impress.co.jp

個人的なこのプラモデルのハイライトは巻き上げダイヤルのロック・解除機構で、こういうアイディアを実感できるところが動き物を作る醍醐味だ。とても楽しい。

しくみを言葉で説明しても、実物を見ないことには実感できないのでぜひ自分の手で作って納得の快感を得てほしい。

(B22 パーツは誰によって回されるのか?P8 パーツが P1 パーツをロックした後、それを解除するのは誰なのか?巻き戻し時はなぜロックされないのか?辺りを考えるとよいと思う。)

f:id:kakkun61:20180910022628j:plain

手順13・14でギアの位置を細かに指定されるけど、これが意味があったのかが不明。なんだったんだろう。

おそらく複雑だったんだろうミラー跳ね上げ部分は組み立て済みなんだが、説明書に組み立て方が載っているのでバラして動作の確認ができるところもいい。

そんなこんなで組み上がったのがこちら。(ところで α6300 + Vario-Tessar T* E 16-70mm F4 ZA OSS で特に考えずさくっと撮ったのにいい味が出ている。)

ファインダーを覗くとこんな感じ。よく見るアイレベルファインダーではなくウェストレベルファインダーというやつだ。プリズムがない。

意外と写ることに感動するはず。

せっかくなのでフィルム1本撮ってきた。フィルムは Fujifilm Superia X-Tra 400。(撮り終わったと思ったら7枚ぐらいまだ残っていた。しくみ上最後になるほどダイヤルが重くなるのでそれで勘違いしたのかもしれない。)

ちゃんと写ってる。すごい。

左右反転像なので慣れないと左右の向きと回転を合わせるのがこんがらがる。(少女2人の写った写真はいかにもこんがらがったもの。)

Diana Mini と比較するとびっくりするほどブレていない。あっちは目測フォーカスでこっちはちゃんと確認できる。そしてシャッター押下時のブレの大きさの違いだろう。Diana Mini はシャッターを切った後のレバーの遊びが大きすぎてすごくブレる。

巻き上げ・巻き戻しが固くて指の皮が痛くなるけど、しばらくはこれで撮影しようと思う。

キャンペーンでもらったクローズアップアダプターレンズとマクロアダプターレンズをまだ作っていないのでまだ楽しめる。

『Yesod 入門』商業誌化

同人誌で発売していた『遠回りして学ぶ Yesod 入門』がこのたびインプレス R&D より『Haskell で作る Web アプリケーション 遠回りして学ぶ Yesod 入門』として商業誌化されることになりました。

www.impressrd.jp

f:id:kakkun61:20180901081452p:plain

同人誌版からの変更点は主に、

  • 日本語が読みやすくなった
  • 対応バージョンが上がった

点です。章が増えたり減ったりはしていません。あと、表紙がかわいくなりました。かわいい。

商業誌では Amazon と honto にてオンデマンド印刷の紙の書籍も購入できるようになっています。紙はこれまで即売会でしか販売していなかったので初めて通信販売で買えるようになりました。

よろしくお願いします。

初フィルム

厳密にいうと小学校の旅行などで「写ルンです」を使ったりしたことがあるので、初フィルムじゃないんだけど自分で装填するものとしては初ということで。

中野のコイデカメラで自分でプラモデルのように組み立てるカメラを見つけて「なんだこれは!?」と調べていたら Lomography にあてられてどうしても自分でも撮りたくなって中古の Lomography Diana Mini を買った。

それで2本撮ったので現像・スキャンしてもらってきた。(本当はもう1本もあったんだけどダメにした。)

70枚撮った中で奇跡的に手ぶれもなくピントも合っている1枚。

Lomography Diana Mini

鏡筒の向かって左のレバーがシャッターボタンなんだけどこれがまたぶれやすいのなんの。

いじれるのは、絞りが F8 もしくは F11、シャッター速度が 1/60 もしくはバルブ。写ルンですのような2眼でピントは目測で 0.6m からで 4m 以降は無限遠までパンフォーカス。

フィルムは富士フィルム Superia X-Tra 400 と、Superia Premium 400。日中は ISO 100 でもよかったように思う。

で、ちゃんとしたフィルムカメラLeica のどれにしようかなどと悩んでいる。こわい。

Windows で haskell-ide-engine をビルドする

手順

1. ソースコード取得。

git clone git@github.com:haskell/haskell-ide-engine.git

2. Unicode を扱う ICU の古いバージョンが要るので取得。

自分の使うバージョンの text-icuchangelog を見て、必要な ICU のバージョンを探す。執筆時点では 53 だった。

http://site.icu-project.org/download/53#TOC-ICU4C-Download

任意の場所に展開する。以降、展開先の箇所を $icu と表記する。

$icu\bin64 にある dll の名前を変える。(要らないかもしれない。) 管理者権限の cmd で下記を実行する。

mklink icuuc.dll icuuc53.dll

他の dll も同様に。

うまくバージョンが一致したとき

うまくバージョンが一致したときは上記のかわりに下記でもインストールできる

stack exec -- pacman -S mingw64/mingw-w64-x86_64-icu

このときはビルド時に --extra-include-dirs--extra-lib-dirs の指定が要らない。

3. ビルド

対象とする GHC のバージョンごとに stack.yaml があるのでバージョンを指定する。(8.0.2 は hie-0.1.0.0 ブランチにある。)オプションで ICU の場所を指定してやる。

stack --stack-yaml=stack-8.0.2.yaml --extra-include-dirs="$icu\include" --extra-lib-dirs="$icu\bin64" install

複数バージョンの GHC に対応できるように、stack path --local-bin が示すパスに生成された実行ファイルの名前を変える。Makefile を見ればどう変えるか書いてある。

copy hie hie-8.0.2
copy hie hie-8.0

4. dll にパスを通す

実行時に参照する必要があるので $icu\bin64 と stack path --extra-library-dirs の示すパスの bin ディレクトリーの方にパスを通す。(PATH 環境変数に追加する。)