謹賀新年/2019目標
明けまして、おめでとうございます
仲良くして下さった皆様、ありがとうございました。また今年もよろしくお願いします。
2018 の良かったこと
- Perl方面
- CLI_JSON を複数の業務で実戦投入して有効性を確認できたこと。 その実用性を更に向上させられたこと。
- 言語処理系勉強会 で yatt_lite の発表をさせてもらえたこと。
- Tcl/Tk方面
- tktable と tkhtml3 を fedora の copr に出せたこと。 これで yum/dnf でどこでも tkhtml3 をインストール可能になった。
- Emacs方面
- Emacs で Language Server を使うための lsp-mode に対して、それを自分流に活用するための道具 https://github.com/hkoba/emacs-on-save-jump-to-lsp-error を作れたこと (スライド)。(これで安心して typescript の開発に進める、はず…)
- Linuxデスクトップ方面
- サーバー方面
- 生活面
- ジム通いの結果、以前より体が疲れにくくなった?こと。
- 新居の生活に順応出来たこと。
2018 の反省点
- オレオレ言語の研究が完全にストップしたこと…(仕事は充実してた気がするものの、これでは本末転倒…)
- それに限らず、昨年立てた目標のうち、体力づくり位しか進展が無いこと。
- 引越し後の荷解きが、予想通り、ほとんど進まなかったこと。
- 通勤が伸びたことにより、起床が一時間早まったこと。
- 体の故障が増えてきたこと。時間もお金も掛かった上に、原因不明瞭で終わることが多いのが厳しい。けどそういう年齢よねと。
2019 の目標
perldebugger (perl -d) を楽に使うコツ的な話
この記事は Perl Advent Calendar 2018 の 12/4 の記事です
Perl Debugger
(perl -d
) を効率的に使うコツ的な話を書きます。
perl の標準デバッガなんて役に立たないと思っている人のための記事です。
TL;DR
step/next は最低限に。break したい個所が予め決まっているなら、デバッグ対象コードにダミー関数の呼び出しを加える手も有る。
Perl Debugger とは
Perl Debugger
は perl に標準で添付されたデバッガで、perl 起動時に -d
オプションを渡すことで利用できます。使い方は一般的なデバッガとよく似ていて、
l 関数名
で関数のソースを表示b 行番号
やb 関数名
で breakpoint を設定c
で実行を継続,s
で関数の中までステップ実行,n
で次の行まで実行
といった感じです。Term::ReadLine::Gnu
などをインストールすると、l
や b
で関数名のタブ補完も効くようになります。
(参考)
h
で出るヘルプ画面には沢山コマンドが並びますが、
使うコマンドはそう多くはありません。よく使うコマンドに印をつけたので
参考にどうぞ。
動的にロードされるコードに breakpoint を仕込むには
そんな Perl Debugger ですが、いざバグ取りに使おうとした時に 肝心のデバッグしたい関数に breakpoint を置けなくて困ることがあります。 この問題は、Perl がその関数が定義されているモジュールをまだ読み込んでいない時に起こります。
(一応、 b load FILE
コマンドを使えば、 require でファイルがロードされる所に breakpoint を仕込むことが出来る、らしいのですが、
FILE のパスの扱いで私は失敗してばかり…なので使わなくなってしまいました)
特に Plack や Mojolicious のようなフレームワークを用いた開発で、動的に読み込まれる .psgi ファイルやモジュールの中のコードをデバッガで調べたい場合に、 step/next を打ち続けて絶望する初心者も散見されます。
正攻法で行く場合
このように動的にロードされるコードをデバッグする場合、正攻法では動的ロードを行う側のコードの挙動を理解して、 step/next を打つ回数を減らすことが大事です。
手順としては、
- そのフレームワークがモジュールの読み込みを行う関数を探し、
- そのローダー関数に最初の breakpoint を置いて実行、
- ローダー関数に達したら、おもむろに
r
で return して自分のコードを実際に読み込ませる - その後、自分のコードに breakpoint を置く
という流れになるでしょう。以下の例は、Mojolicious から呼ばれる startup をデバッグする流れです。
逆転の発想:breakpointを置くためだけのダミー関数を使う
ただ、上記のやり方は個々のフレームワークの内部実装に対するある程度の理解が必要になるため、 準備に時間がかかりますし、正直面倒です。
それよりも、もし breakpoint を仕込みたい個所が予め決まっていて、 かつそのコードを書き換えることが可能なケースであれば、 breakpoint を置くためだけの(中身のない)ダミー関数を使う方法が有効です。
すなわち、
- ダミー関数の名前を決める(例えば main::breakpoint)
- break したい個所に、そのダミー関数への呼び出しを書き入れる(ただし注意有り)
# This method will run once at server start sub startup { my $self = shift; + + main::breakpoint(); # Load configuration from hash returned by "my_app.conf" my $config = $self->plugin('Config');
- デバッガを起動
x sub main::breakpoint {}
などしてダミー関数を定義- (予めプログラム内で定義しておいても良い)
b main::breakpoint
でダミー関数に breakpoint を設定c
でダミー関数まで飛ぶr
で該当コードに移る
この方法なら実質 c
一発で該当のダミー関数まで到達出来るため、途中のフレームワークの実装に
関する知識は実質的に不要になります。
注意点:ダミー関数を呼んだままでは本番でコケる
ただし、このままでは(ダミー関数は本番には存在しないので)、
本番で動かないコードになってしまいます。ですので、このダミー関数の存在の有無を perl の $pkg->can($methodName)
で確認してから呼ぶようにすると良いでしょう。
if (my $sub = main->can('breakpoint')) { $sub->(); }
手順としては、こんな感じです>
なお、自作のコードの場合は、ダミー関数の定義を正式なコードに予め含めておくほうが楽です。
その場合は main::breakpoint
ではなく、もっと別の名前空間に入れる方が衝突のリスクが少なくて安全です。breakpoint だけを入れたモジュール XXX::Breakpoint
みたいなものを用意しておく手も有るでしょう。
終わりに
Perl Debugger で今どきのコードをデバッグする時に困りがちな『 breakpoint を置くのが大変』問題を、breakpoint 用のダミー関数で軽減する方法について解説しました。
明日は @ytnobody さんです。
(最後しか聞けなかったけど)吉祥寺pm16感想とりいそぎ
昨夜は 吉祥寺pm#16に参加してきました。
と言っても、私は仕事のトラブルで大遅刻!
勉強会って遅刻すると、すっごく行きづらいと言うか、気持ちが落ちませんか? 私は落ちます。でも懇親会の参加者が減ると主催者さんを困らせちゃうし…と思い、会場へ。
そんな落ち気味な気持ちで会場に着き、部屋に入ると、なぜかプロジェクターでゲーム…???
#kichijojipm 起業クエストww pic.twitter.com/mUhow0FKpO
— teckl (@teckl) November 22, 2018
自作ゲームでプレゼンらしく、こりゃ凄いと思いました。
なんとかエンドロールまでいけて良かった! ありがとうございました! #kichijojipm
— :craftsman/kawasima (@kawasima) November 22, 2018
その後は休憩から LT タイム
どの LT も良かったのですが、個人的に一番刺さったのはこの LT、 スタートアップの一人技術者の立場からの採用の話でした。 曰く 『面接で高印象だった人に、お試しで働いてもらう』 作戦です。
- 転職か副業を希望している人で、面接で高印象だった人に、
- 業務委託として、お試しで働いてもらう。
- リモートワーク+週一のミーティング
昨日のLTのスライドです!遅くなりましてすみません... スタートアップで開発速度を上げるためにやった事 https://t.co/2VHdD67rSk #kichijojipm
— kakakazuma (@kakakazuma20) November 23, 2018
確かに、マッチするか否かは一緒に仕事をしてみないと分からない部分が大きいので、これは合理的だと思いました。
実際にどんな仕事をリモートワークで依頼しているのかという質問には、(Webの)フロントエンドとスマホのアプリを例に挙げておられました。確かにそれなら仕事として切り出して発注しやすそうだなと。
なお、週一のミーティングは(相手の都合に合わせるので)どうしても土日どちらかに入れることになるそう。 経営・管理側でないと出来ない技だなあと思いつつ、体調管理についてうかがうと、職住近接で8時間?睡眠を死守しているとのことでした。
たまたまその後の懇親会でもお隣の席だったので、幾つもアドバイスを頂けました。
私も(私がお客さんに提供している)技術スタックのコア部分が、自分にしか扱えない、完全なトラック係数1、SPOF になっています。ビジネスの継続性のために、この技術の継承者をどう育てるかという点が、ずっと懸案でした。
それに対して頂いたアドバイスは、ずばり、それって弟子取りですよねと。そして>
弟子を取るなら、最初の面接のハードルを上げるべしというアドバイスを頂いた。
— hkoba (@hkoba) November 22, 2018
愛すると決めてから取るべしと
相手をよく知らないまま採用して、後から相手の良い所を探しながら教育するのは、大変だから…と。
そうではなく、先に相手の中の「尊敬できる所」「愛せる所」「見どころ」を見出しておく。見いだせた人だけを、 採用する。つまり、採用する側の、「愛する覚悟」が定まった時だけ、採用するように…しないと辛いよねと。
…これは、とても響く話でした…
そうして、愛すると決めた弟子が出来たら、以外に仕事の切り出し・委譲は出来てくるものだ、という話を聞いている
— hkoba (@hkoba) November 22, 2018
上赤さん、有難うございました。
吉祥寺pm15 で Runnable Module パターン(仮)の話をしてきました
コードを2つに分けて書く - 一つは自分のため、もう一つは顧客のために
はじめに
この記事は、身近なプログラマー仲間に向けて書かれたまとめです。 私的な、身内向けの解説文ではあるのですが、 仕事でプログラムを組む人全般に通じるように書く、つもりです。
主張
私は基本的に Perl プログラマーです。ですが仕事では、 Perl で 1ファイルで全部書けるような小さな案件でも、
のように、2つのプログラムに分けて書いたほうが良いと考えています。 この記事ではその理由を解説します。(この考えは Perl と Zsh の組み合わせ以外にも、応用できます)
どんなコード量・規模を想定した話か分からないと納得できない人もいると思うので、 参考として、この考えに基づいて書いた昔のコードを載せておきます。 (コードの解説はありません。要望があれば書きます) github.com
仕事のコードを2つの層に分けるメリット
1. 適材適所
一つの理由は、言語毎の得意領域の違いです。 例えばファイル名操作やプロセスの単純な起動とデータのやり取りは、少なくとも私にとっては、 Perl よりも Zsh の方が短時間で書け、 かつ動作検証もコマンド行で出来て楽である、といった風に。
ですが、理由はそれだけではありません。
2. 汎用を目指すコードと、汚れ役になるコードをファイル単位で隔離できる
もっと大きな理由は、ファイルを分けることにより、プログラマーとしての自分を納得させるためのコーディングと、 顧客第一でオーバーエンジニアリングを避けるコーディング、 2つのコーディング方針を使い分けることが出来るからです。
言うまでもないことですが、仕事のコードは顧客・案件に特化した定数 (ファイル名・ディレクトリ名・アカウント名・サーバー名…)を扱う必要が有ります。 汎用的なプログラムを書きたいなら、こうした定数を何らかの形で プログラムの外部に追い出す必要が有ります。
早くからプログラムをファイルとして2つに分けておくことにより、
- 『汎用性があるよう、キレイに書きたいコードを入れるファイル』
- プログラマーが納得できる抽象度のコードを書くためのファイル
- 『定数をハードコードして良い、 汚れ役 ファイル』(雑事吸収層 と呼んでいます)
という風に、2つのプログラムで別々のゴール・価値基準を追求できるようになる、という考えです。 一つのプログラムで満たそうとすると無理が出るので、別々のプログラムに分ける、ということです。
脱線しますと、私がこの方針に至った背景には、よく知られたフレーズの一つ↓の影響が大きいように思います。2つの直交した問題領域を扱うなら、間にマッピングをかまさないと、片方に漏れ歪みが出るのは自然な現象ですよね。
『計算機科学のあらゆる問題は別のレベルのインダイレクション(間接参照)で解決できる』
好き>
— hkoba (@hkoba) 2018年3月14日
"All problems in computer science can be solved by another level of indirection," is a famous quote attributed to Butler Lampson, the scientist who in 1972 envisioned the modern personal computer.
Beautiful Code: Another Level of Indirection https://t.co/hRLxSe3HYv
この記事によれば誰が言い出したのかは諸説あるぽいですね>
設定ファイルは無限に拡張され、半端なプログラミング言語に成り果てる。なら最初から…
ここで、定数のハードコードを避けたいだけなら、 yaml や ini, json などの設定ファイルの読み込み機能を提供するだけで十分ではないか? プログラムをもう一つ用意するほどではないのでは?と疑問に思う人も多いかもしれません(要出典)。 これに対する私の意見は、(例え設定ファイル読み込み機能を提供する場合でも) ユーザーに渡す汚れ役プログラムを別途作成したほうが、費用対効果が良い、というものです。
その理由は、
- 本来、設定ファイルは定数を書くためのもの。ロジック・ダイナミックな値を書くために設計されたものではない。
- 顧客の事情にもロジックはあるし、ダイナミックな値を使って記述したいことは沢山有る
- 共通の値(変数にしたい)、条件式と分岐、繰り返し、ワイルドカード・パターン…
- 故に、設定ファイルで無理にロジックを表現しようとすると、あちこちに Ad-Hoc な eval を行う拡張ルールを定める必要が出る。
- それは実質的に、不完全なプログラミング言語を作っているようなもの
- その拡張?が延々と続く…
ここでもう一つの名フレーズが思い出されます。
「十分に複雑なCまたはFortranの プログラムは全て、後付けで、正式な仕様がなく、バグがてんこもりの、 遅い、CommonLispの半分の実装を含んでいる」
(訳はこちらの脚注より)
Greenspun's tenth rule - Wikipedia
http://practical-scheme.net/wiliki/wiliki.cgi/Lisp:GreenspunsTenthRule
あくまで私の解釈ですが、このフレーズが言う後付の出来損ない common lisp というのは、 設定ファイル機能の成れの果てを指しているのではないか、と思うのです。
いずれそうなる定めなら、最初から(チューリング完全な)プログラミング言語を持ち出したほうが良いのでは、と。
最後に
ここまで読んで下さりありがとうございました。
文章を書くのって、本当に時間がかかりますね…
メモ:Zsh で TSV を読みたいとき、どうするか
(いつも通り、ツッコミ歓迎です)
問題
例えば TSV (タブ区切りテキスト)形式の正誤表があり、そこから SQLite のデータベースファイルへ更新をかけたい… そんな時、皆さんだったらどうしますか? なお、レコード数は1,000行程度とします。
私はこういうケースだと Zsh を使って雑に UPDATE 文の列を作ることが多いです。 ただ、雑に書きすぎると入力の形式によっては上手く行かないので、その辺りをまとめてみました。
TL;DR IFS で分けるな、一行丸々 IFS= read -r line
で読み込んでから col=( "${(@ps:\t:)line}" )
で配列に分けよう。
なお、SQLite は文字列をシングルクォートで囲って表現しますが、その中にシングルクォートが出現する時は ''
のように二重にする必要があります。
今回は、この処理に以下の関数を使います。この SQL文字列をクォートする仕組みの解説は以前の日記を参照して下さい
function zqsql { local val=$1 print -nr "${${(qq)val}//'\''/''}" }
部分的にしか動かない方法: IFS を使う
まず、read と IFS の組み合わせを使う方法を書いてみました。
入力ファイル input1.tsv
はこんな内容です。
id old new 1111 ああああ かかかか 2222 いいいい きききき 3333 うううう くくくく
実行
% i=0; while IFS=$'\t' read -r id old new; do ((i++)) || continue print "update t set name = $(zqsql $new) where id = $(zqsql $id);" done < input1.tsv update t set name = 'かかかか' where id = '1111'; update t set name = 'きききき' where id = '2222'; update t set name = 'くくくく' where id = '3333';
残念ながら、このやり方だと次の input2.tsv
のように、入力に空文字列が入るケースで困ります。
id flag1 old flag2 new 1111 foo ああああ bar かかかか 2222 1 いいいい きききき 3333 うううう 2 くくくく
実行の様子です。 1111
は正常ですが、 2222
, 3333
の update 文は期待と違います。
% i=0; while IFS=$'\t' read -r id _ old _ new; do ((i++)) || continue print "update t set name = $(zqsql $new) where id = $(zqsql $id);" done < input2.tsv update t set name = 'かかかか' where id = '1111'; update t set name = '' where id = '2222'; update t set name = '' where id = '3333';
これは $IFS
に(タブなどの)空白文字を設定した場合、複数の空白文字の連なりを一つのフィールド区切りとして認識しているから、でしょう。
# IFS にタブを設定した場合 % IFS=$'\t' read -A foo <<<$'foo\t\t\tbar' # 配列の中は2要素 % print $#foo 2 % print -l "$foo[@]" foo bar % # IFS にカンマを設定した場合 % IFS=, read -A foo <<<"foo,,,bar" # 配列の中は 4 要素 % print $#foo 4 % print -l "$foo[@]" foo bar %
解決策:IFS では分割せずに、読み込み後に分割する
変数展開フラグの s:string:
を使いました。
以下 zsh 公式マニュアルの引用です。
s:STRING: Force field splitting at the separator STRING. Note that a STRING of two or more characters means that all of them must match in sequence; this differs from the treatment of two or more characters in the IFS parameter. See also the = flag and the SH_WORD_SPLIT option. An empty string may also be given in which case every character will be a separate element. For historical reasons, the usual behaviour that empty array elements are retained inside double quotes is disabled for arrays generated by splitting; hence the following: line="one::three" print -l "${(s.:.)line}" produces two lines of output for one and three and elides the empty field. To override this behaviour, supply the '(@)' flag as well, i.e. "${(@s.:.)line}".
今回の例に適用すると、こんな感じになります。
% i=0; while IFS= read -r line; do ((i++)) || continue # 変数 $line の中身を s(split) フラグで分割 # タブをエスケープシーケンスで \t と書きたいので p (print) フラグを立てる # 空文字列を残したいので全体を "" で囲みつつ @ フラグも立てる col=("${(@ps:\t:)line}") print "update t set name = $(zqsql $col[5]) where id = $(zqsql $col[1]);" done < input2.tsv update t set name = 'かかかか' where id = '1111'; update t set name = 'きききき' where id = '2222'; update t set name = 'くくくく' where id = '3333';
謹賀新年/2018目標
あけましておめでとうございます
昨年度ご縁の有った方々、仲良くして下さった皆様、ありがとうございました。
今年も一年、よろしくお願いします。
2017の良かったこと
- やっと引っ越し先が決まったこと
- (多少は) fsharp, docker, golang, nodejs, jupyter notebook をかじれたこと…本当に多少だけど…
2017の反省点
- 若手プログラマーに魅力的な場を提供できず、他社に引き抜かれてしまったこと
- オレオレ言語の試作が停滞したこと
2018の目標
- この本の内容の実践を試みる
- 作者: 田原祐子
- 出版社/メーカー: 秀和システム
- 発売日: 2017/09/27
- メディア: 単行本
- この商品を含むブログを見る
- 仕事の手離れ改善する
- 自分の担当業務を Job Description へと書き起こし、業務内容の言語化(明文化)とモジュール化を進める。
- タスク毎に、手順書、チェックリスト、(背景の)解説書を分けて書く。
- OSS作り, Life work に充てる時間を少しでも増やす
- Tcl/Tk 方面の成果もアピールしていきたい。特に sshcomm と tkhtml3
- YATT::Lite 、取っつきやすくする(でも誰に?)
- オレオレ言語の意味論練りのため、試作の数を打つこと
- 仕事が忙しくなっても生活が荒れないような対策を取る
- 筋肉つけたい
- ルンバとかハウスキーパーさん頼む?