hkoba blog

プログラマーです。プログラミング言語ミーハーです。ツッコミ歓迎です。よろしくどうぞ(能代口調)

謹賀新年/2017目標

あけましておめでとうございます

昨年度仲良くして下さった皆様、ありがとうございました。

本年もよろしくお願いします。

昨年の良かったこと

  • プログラミング方面
    • 吉祥寺pm で、 自分の Perl プログラミング・スタイルの大きな柱である fieldsexporter 周りの知見を発表させてもらえた事。
    • 久々に俺々言語の実装実験に取り組めた事。 自分の構想する VM 実装技法の上に(仮組みレベルでも) lisp を実装できたことは、大きな前進だった。
  • 暮らし
    • 引越し先候補を幾つか下見できた事。
    • 荷物減らしが進んで、部屋のレイアウトを変更できた事。
    • (人生3度めにしてやっと)メガネからコンタクトへの移行が出来た事。

昨年の反省点

  • 俺々言語の実験成果を公開できるレベルにまとめられなかった事。 普通の lisp を作るだけならそう難しくないが、 そこから離れた独自の意味論を作る所をどう実験していくかが今の課題。
  • 結局、引越し先を決めるに至らなかった事。(探す方向性を変える必要があると気付けたのは収穫)

要するに 昨年の目標 と見比べて ほぼ進展が無いこと(><

今年の目標

  • 引っ越ししたい
  • 俺々言語に進捗を
  • fsharp 勉強しなきゃ… docker もそろそろ…
  • 彼女(嫁)探しを諦めない…

Smalltalk の勉強会に参加してきました(ハッカソン編)

smalltalk.connpass.com

私にとって Smalltalk は学生時代から憧れの言語で、にもかかわらず何度挑戦しても (Emacs keybind が使えないがゆえに) 挫折を繰り返していた言語です。何とか突破口を開けられないかと参加させてもらいました。

以下、教えて頂いたこと・調べたことの備忘録です。間違いなど有るかもしれませんのでツッコミ歓迎です。

ハッカソン(午前)

私は Smalltalk 初心者なので、Pharo5 のキーボード・マッピング周りの仕組みを調べて学ぶことにしました。

途中、梅澤さん mumez (Masashi Umezawa) · GitHub に Pharo のショートカット周りのクラスやそのオーバーライド方法、 クラスブラウザの操作方法を色々教えて頂きました。

  • PharoShortcuts current で取り出されるインスタンスを通して、ショートカットが設定されているらしいので、ここをオーバーライドすれば出来そう、とのこと。(ただ、そのために PharoShortcuts クラス自体もいじらなければならない。)
    • この使用箇所を探すとき、クラスブラウザ上でクラスを選んで右クリック→ AnalyzeClass refs... が役に立つことを見せて頂きました。
    • インスタンス変数は大文字はじまり、と。
  • (Web ブラウザの開発ツールに有るような) マウスで画面要素を直接指して中身を Inspect したい時は、 Shift + Mouse MiddleHalo を呼びだせば良いそう。

雑談

お昼ごはんの時にも雑談で色々教えて頂きました。

  • イベントループはどこに有るの? → WorldState にあるよ
  • Pharo にも Object Table は有るの? → ある。 Newspeak, Pharo, SqueakVM (の設計?) が同じ
  • allInstance の逆はあるの? → allOwners というのがある。(Morph のみ?)
  • バージョン管理機能があると聞いたけど、 CompiledMethod はイメージ内に複数持つの?一つだけ? → 一つだけ。

あと、皆さん pharo を一番使ってたのが印象的でした。 pharo のコア開発者はフランス inria 繋がりの人が多いとか…

<keymap> は Pragma だった!

以前の挫折ポイントの一つが、この↓ <keymap>

TxTextEditorMorph>>#buildTextEditorKeymapsOn: aBuilder    
    <keymap> 
     
    {  
        Character home. #moveToLineStart.
        Character home shift. #selectToLineStart.
           ...

これは Pharo の Pragma 構文で、所謂 コード注記 と同じ役割を果たしているそうです。 (メソッドに属性としてぶら下がる)。 Pharo は Pragma を色々活用しているとのこと。

ハッカソン (午後)

ところが PharoShortcuts の中身をよく見たところ、肝心のカーソル移動が定義されていないことに気づきました。 これでは Emacs 風のカーソル移動は定義できません。

そこで方針を変えて、そもそもキーマップ機能と、イベントループ周りがどんな仕組みになっているのかを調べることにしました。

キーマップ関連

TxTextEditorMorph>>#initializeShortcuts: aKMDispatcher
    aKMDispatcher attachCategory: #TxTextEditorMorph

KMDispatcher というのが関係が深そう…

クラスのコメントを順々に抜き書きしてみます。

KMDispatch

I'm an object that saves a buffer of keyevents for the morph I'm attached.
I am the one that dispatches the single and multiple shortcuts.
If the morph has a keymap that matches the keyboard event,
I tell the keymap event to execute with the morph I'm attached.

KMRepository

I have a singleton instance which can be accessed by executing the following:
"self default"
 
I am currently a god object to be refactored =D.
KMRepository>>#reset
   World setProperty: #kmDispatcher toValue: nil.
   self default: self new.
   KMCategory allSubclasses
      select: [ :c | c is GlobalCategory ]
      thenDo: [ :c | c new installAsGlobalCategory ].
   KMPragmaKeymapBuilder uniqueInstance reset.

KMPragmaKeymapBuilder

I am a singleton object, subscribed to system events, to listen to the creation of
methods marked with the <keymap> and keymap:> pragmas.
  
When I listen one of those events, I reinitialize the KMRepository default instance
and reload it with all declared keymaps.

う〜ん、らちがあかない…

イベントループ

下から追うのが難しそうなので、今度はイベントループを調べてみます。

教えて頂いた WorldState と、グローバル変数 World (こちらは WorldMorphインスタンス) が関係が深そう。

WorldMorph>>#doOneCycle
    worldState doOneCycleFor: self.
    
WorldState>>#doOneCycleFor: aWorld
   self interCyclePause: MinCycleLapse.
   self doOneCycleNowFor: aWorld.
WorldState>>#doOneCycleNowFor: aWorld
    "Immediately do one cycle of the interaction loop.
   This should not be called directly, but only via doOneCycleFor:"

    DisplayScreen checkForNewScreenSize.

    "process user input events"
    LastCycleTime := Time millisecondClockValue.
    self handsDo: [:h |
        ActiveHand := h.
        h processEvents.  "…ここが肝ぽい…"
        ActiveHand := nil
    ].

    "the default is the primary hand"
    ActiveHand := self hands first.

    aWorld runStepMethods.      "there are currently some variations here"
    self displayWorldSafely: aWorld.
displayWorldSafely: aWorld
    "Update this world's display and keep track of errors during draw methods."

    [aWorld displayWorld] ifError: [:err :rcvr |
        "Handle a drawing error"
        | errCtx errMorph |
        errCtx := thisContext.
        [
            errCtx := errCtx sender.

                        ....

            "If the morph causing the problem has already the #drawError flag set,
           then search for the next morph above in the caller chain."
            errMorph hasProperty: #errorOnDraw
        ] whileTrue.
        errMorph setProperty: #errorOnDraw toValue: true.
        "Install the old error handler, so we can re-raise the error"
        rcvr error: err.
    ].

↑今読み返すと WorldState>>#doOneCycleNowFor: aWorld の中の ActiveHand processEvent が鍵ぽいのですが、 この時点では読み取れませんでした。

キー入力周り

再び下から、でも目線を変えてキー入力に関係するコードを探してみようと考えました。

Smalltalk なので、メタキーを扱うコードが有るはず… #meta の sender を見る…

SimulateKeystrokesSpecification>>#testSimulateCmdKeystroke の中に self simulateKeyStrokes: ... というコードを発見。

Morph>>#simulateKeyStroke: aCharacter
   |event|
   event := KeyboardEvent new
     setType: #keystroke
     buttons: 0
     position: 0@0
     keyValue: aCharacter charCode
     charCode: aCharacter charCode
     hand: ActiveHand
     stamp: 0.
   self keyStroke: event

KeyboardEvent クラス! この Class ref から、 HandMorph>>#generateKeyboardEvent を見つけました。 後はこれの sender を探すのみ。

HandMorph>>#processEvents
    "Process user input events from the local input devices."

    | evt evtBuf type hadAny |

    [(evtBuf := Sensor nextEvent) isNil] whileFalse: 
            [evt := nil.  "for unknown event types"
            type := evtBuf first.
            type = EventTypeMouse         ifTrue: [recentModifiers := evtBuf sixth. evt := self generateMouseEvent: evtBuf].
            type = EventTypeKeyboard      ifTrue: [recentModifiers := evtBuf fifth. evt := self generateKeyboardEvent: evtBuf].
            type = EventTypeDragDropFiles ifTrue: [evt := self generateDropFilesEvent: evtBuf].
            type = EventTypeWindow        ifTrue: [evt := self generateWindowEvent: evtBuf].                

            "All other events are ignored"
            (type ~= EventTypeDragDropFiles and: [evt isNil]) ifTrue: [^self].
            evt isNil 
                ifFalse: 
                    ["Finally, handle it"

                    self handleEvent: evt. "多分ここで、 HandMorph>>#handleEvent:evt が呼ばれる"
                    hadAny := true.
                                        ...]].
        ...

そして HandMorph>>#handleEvent:anEvent が呼ばれる、のではないか…

github.com

今日たどり着いたのはここまで!

(でもキーマッピングの話はまだ遠いにょ…)

おしまい

これからビールを飲みながら勉強会です。

zparseopts で posix style long option と一文字オプションを両立させる

Zshzparseopts を使って

  • -t TYPE
  • --type TYPE
  • --type=TYPE

全部の書き方をサポートしたい場合にどう書くか。 (代入先の配列名を同じにするだけ、だけど)

値の取り出しの書き方を忘れがちなので、メモ。

(我流なので、ツッコミ歓迎です)

zparseopts がどう働くか

% set -- -t foo
% zparseopts -D -K h=o_help t:=o_type
% typeset o_type
o_type=( -t foo )
%
% set -- --type foobar
% zparseopts -D -K h=o_help t:=o_type -type:=o_type
% typeset o_type
o_type=( --type foobar )
%
% set -- --type=foobar
% zparseopts -D -K h=o_help t:=o_type -type:=o_type
% typeset o_type
o_type=( --type '=foobar' )
%

オプションの検査

配列のサイズを見るのが、良いのではないかと…

if (($#o_type)); then
   # …オプションが指定されている時の処理…
fi

値の取り出し

値の先頭が = で始まる =なんとか みたいな文字列を渡すことを諦めれば ↓この書き方で行ける。

% print -- $o_type[2]
=foobar
% print -- ${${o_type[2]}#=}
foobar
%

.oO(…もっと短く覚えやすい書き方があれば、是非教えて下さい…)

YAP(achimon)C::Asia Hachioji 2016 にボランティア参加してきました

yapcasia8oji-2016mid.hachiojipm.org

最初は予定が合わなくて諦めてたけれど、自分の予定が変わったタイミングで ボランティア足りないって話を聞いたので、思い切ってお手伝いさせてもらいました。

担当は A部屋の第3の司会(交代制)でした。とはいえ喉の調子が悪かったので、 二日目は部屋内遊撃みたいな役目にさせてもらってました。

(…失礼がなかったか、内心ドキドキしています…)

全体感想

開催中にスタッフのある人が『yapc は良いよな、ここで笑いが出るもの…他だと笑ってくんないんだぜ…(意訳)』 と言っていたのが、私にも同感で…この陽気さ・笑いで解決する文化というか、血脈みたいなものこそが、 yapc が培ってきてここに受け継がれたものなんだろうな、などと思いました。

それにしてもよい会場でした… Microsoft さん、ありがとうございました。

トーク感想

どのトークも面白かったのですが、全部は書けそうにないので、3つだけ…発表順で。

pastak さん

speakerdeck.com

Browser 拡張は私もいつか再挑戦したくて…現在の状況がよく分かって、とても有益でした。

ytnobodyさん

http://blog.ytnobody.net/entry/2016-07-04-10-33-02.htmlblog.ytnobody.net

チーム作りと、経営層の巻き込み方の所が、凄いなぁと脱帽しました。 (野村再生工場を連想したり…)

どの項目もサラッと書いてましたけど、実行するのは大変だったはず…

しんぺい a.k.a. 猫型蓄音機さん

speakerdeck.com

MVC 的なものづくり、私も悩んできたし、悩みすぎて テンプレートエンジン+Webフレームワークを作っちゃう タイプなので、 非常に勉強になりました。 『Presentation とそれ以外を分けることこそが肝心なんだ!(意訳)』 って主張とか、 『Model の小分けの仕方はケースバイケース!(意訳)』 って話は、とても納得が行きました。 (とはいえ私の書いてるコードがしんぺいさんの考える適切さの範囲に該当するのかは、なんともですが…><)

最後に

楽しかった!

とある方のブログでみたスクリプトが、なぜ暴走したのかについて

Twitter で流れてきたリンクで、Perl を勉強中の方のブログが目に止まりました。

note103.hateblo.jp

読んでいて気付いた点があったので、それについて書いてみます。 (最初はコメントで書いていたら文字数制限で途切れてしまったので… 途切れるなら字数制限の警告が欲しい…)

id:note103 様、はじめまして

id:note103 様、はじめまして、興味深く読ませて頂きました。

上記の現象のうち、 Perl 関連の部分について、多分こうではないか?と気付いた所があるので書かせて下さい。

なぜこのスクリプトは暴走したのか

第一に、 Sample.pm の中に間違って init(); が書かれた状態で何が起きるかです。 この場合、 単に use や require で他のスクリプトから読み込んだだけで Sample::init() の実行が始まる ことになります。 このことは以下のようにして確認できます。

perl -e 'require Sample'

第二に、状況からの推測ですが、 MacVim の syntastic (perl の構文間違いの自動検査) が呼び出した perl -wc main.pl の中で Sample.pm が読み込まれるので、そこで Sample::init() が呼ばれて入力待ちが発生。 syntastic が応答しない→ MacVim も固まる、となったのではないかと。

ここから先は、ご自身で書いておられる通り、 "具体的には最後のelse文の中で同じサブルーチンを呼び出しているのが悪手" となり、無限に再帰呼出しを行う状況だったのではないかと。

試しに

perl -we 'use Sample' </dev/null

と端末で打ってみると、無限ループが再現できるはずです。 (端末からなら ^C で停止させることが出来ます。)

あと、この Sample::init で使っている

chomp($bar = <STDIN>);

は、perl の普通の入力待ちループ

while ($bar = <STDIN>) {...}

と違って defined 検査が足りていないため、 eof が来ても停止しない、という点も、無限ループになった要因の一つですね。

ではこの辺りで失礼します〜

実験: SQLite の `in (...)` 句を *雑に* zsh の配列展開で生成してみる

はじめに

  • ツッコミ歓迎です。
  • 対象DB は SQLite です。
  • あくまで入力データの特性を完全に把握しコントロール出来る場合しかおすすめしません…

値に single quote (') が入らない保証が有る場合

% values=(
  foo
  bar
  baz
)
% print " in (${(j/,/)"${(@qq)values}"})"
 in ('foo','bar','baz')
% 

こっちは割と、仕事で使ってます。

解説

  • ここでは配列変数 values に値を格納
  • @qq は配列の要素ごとに qq (single quote で囲む) を適用
    • それを一旦 " " で囲むことで、 "$@" と同じ効果を出す
  • j/,/ は配列を , で join.

値に single quote も入る可能性が有る場合

qq フラグで自動 quote させる場合、文字列中の single quote は '\'' という文字列に置換される

% foo="Foo's Bar"
% print -r ${(qq)foo}
'Foo'\''s Bar'
%  

これを '' に置き換えれば行けるのではないか…

% values=(foo bar "Foo's Bar")
% print -r "in (${${(j/, /)${(@qq)values}}//'\''/''})"
in ('foo', 'bar', 'Foo''s Bar')
%

出来た気がする…

追記2018-05-08+:配列じゃなくてスカラーの場合の書き方、コピペ用

実務でよく使うようになったので、これも。

% foo="Foo's Bar"
% print -r "${${(qq)foo}//'\''/''}"
'Foo''s Bar'
%

関数にまとめるとこうかしら?

function zqsql {
  local val=$1
  print -nr "${${(qq)val}//'\''/''}"
}

[linux]作業メモ:luks で暗号化した EFI ベースの notepc で HDD を換装した際の作業記録

単なる自分メモ。

  • ディストリは Fedora Linux
  • 起動モードは EFI
  • 作業環境は root の zsh

1. GParted で GPT で partitioning

  • EFI System Parition を作る。 (sdb1)
    • サイズは 384MB にしてみた
    • format 実行するまでは boot フラグが立てられなかった? 気のせい?
    • フラグを立てる画面に esp フラグもあるので、これを立てる
  • /boot も作る (sdb2)
  • 最後の一つを unformated で確保 (sdb3)

2. /boot/efi をベタコピー

mount /dev/sdb1 /mnt/tmp
cp -va /boot/efi/* /mnt/tmp
umount /mnt/tmp

3. /boot をベタコピー

mount /dev/sdb2 /mnt/tmp
rsync -av -x -A -X /boot/ /mnt/tmp
umount
  • pax -r -w -X -pe だと何かエラー出た。 selinux?

4. luks

cryptsetup luksFormat /dev/sdb3
cryptsetup luksOpen /dev/sdb3 newsecure
pvcreate /dev/mapper/newsecure
vgcreate vghk16 /dev/mapper/newsecure
vgchange -ay vghk16

5. (今回は lv の統合も行う)

mkdir /mnt/srcimg
lv=/dev/vghk15/root
snap=snap-$lv:t
lvcreate --snapshot --name $snap -L2G $lv
mount -r /dev/vghk15/$snap /mnt/srcimg
function lvsnap {lv=$1; snap=snap-$lv:t; lvcreate --snapshot --name $snap -L2G $lv}
lvsnap /dev/vghk15/var
mount -r /dev/vghk15/$snap /mnt/srcimg/var
lvsnap /dev/vghk15/libvirt_images
mount -r /dev/vghk15/$snap /mnt/srcimg/var/lib/libvirt/images
mkdir /mnt/destimg
mkfs.ext4 -m 1 /dev/vghk16/root
mount /dev/vghk16/root /mnt/destimg
lvcreate --name var.log --size 8G /dev/vghk16
mkfs.ext4 -m 1 /dev/vghk16/var.log
mkdir -p /mnt/destimg/var/log
mount /dev/vghk16/var.log /mnt/destimg/var/log
time rsync -a -A -X /mnt/srcimg/ /mnt/destimg
umount /dev/vghk15/snap-libvirt_images  /dev/vghk15/snap-var  /dev/vghk15/snap-root
lvremove -f /dev/vghk15/snap-libvirt_images  /dev/vghk15/snap-var  /dev/vghk15/snap-root
time hktools/sysadm/lvsnapbackup.zsh /dev/vghk16/ /dev/vghk15/home /dev/vghk15/*.hkoba

… snapshot 作成と mount は一つの関数にまとめるべきだった…読みづらい…

6. lvm vg name と luks の uuid 更新.

前回のメモ>

今回も grub2-mkconfig 使うべき所を変に気を回して失敗した。

以下は本来こうすべきだったはず!という事後の想像>

volume group 名の変化への対処

HDD 引越しで Fedora が起動しなくなるのは大抵 fstab の書き損じ。

  • 最後に chroot して mount -a 通るまで確認するべきだった。
    • lv を削ったので fstab 編集は必須
    • /boot/boot/efi については /dev/sda1 とかに書き換える方法だと chroot からの mount -a での確認が出来ない (この時点では sdb1 とかだから)。 だから真面目に uuid の置き換えをやるべきだった。
  • 一度やり直しが発生したせいで、fstab の確認が甘くなった。 (こういう再演をもっと確実にこなすには…?)
files=(/etc/fstab /etc/default/grub)
sed -i -e "s/vghk15/vghk16/g" /mnt/destimg$^files
vim /mnt/destimg/etc/fstab

luks uuid の変化への対処

files=(/etc/crypttab /etc/default/grub)
read vol olduuid rest < /etc/crypttab
newuuid=$(cryptsetup luksUUID /dev/sdb3) 
sed -i -e "s/$olduuid[6,-1]/$newuuid/g" /mnt/destimg$^files 

7. chroot して、 dracut

mount -t proc none /mnt/destimg/proc
mount -t sysfs none /mnt/destimg/sys
mount -t devtmpfs none /mnt/destimg/dev
chroot /mnt/destimg

chroot 内での作業

mount -a が上手く行くか確認し、大丈夫なら initramfs と grub2-efi.cfg を生成し直す。

mount -a

dracut --force
# 確認
lsinitrd -f etc/crypttab

grub2-mkconfig -o /etc/grub2-efi.cfg

exit
…余談…

この時作った chroot 空間の中で zsh の行編集の一部 (Ctrl キーと alphabet の組み合わせだけ)が 無効になってて…これは何が原因なのか、どなたかご存知でしたら是非ツッコんで下され〜

8. umount

umount /mnt/destimg/boot/efi /mnt/destimg/boot
umount /mnt/destimg/{proc,sys,dev}
umount /mnt/destimg

grep sdb /proc/mounts

で、後は物理交換して再起動するだけ(のはずだった…次こそは一発でやりとげたいなぁ…)

結末

追記. 更に後日談

/var/log を新たに lv として分けてあったのに、 fstab に書き忘れてて全然活かせてなかったと後で気付いた。 これも最初は正しいつもりの fstab を書き上げた後で、手戻りが発生したことで抜けになったケース。

そう言えば、増やした lv の fstab への書き忘れは mount -a でも検出できないなぁ…

折角 /etc を git で管理してあるのだから、一旦 git diff で変更を保存しておいて、 後で git apply すれば良かったと、今更ながら思う。