hkoba blog

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

五反田pm#19 楽しかったです〜

五反田pm#19 に参加してしてきました。

gotanda-pm.connpass.com

感想あれこれ

@kfly8 さんと運営の皆様お疲れ様でした! 最近では珍しく Perl の濃い話を全力で話してもドン引きされるどころか喜ばれる会で、とても有難かったです。 懇親会のお酒とお寿司も美味しかったです!モバファクさんありがとうございました!

  • 最初のコマンド行 Perl の話から質問が出て皆で答えて盛り上がりましたね〜
  • JSON 関連モジュールの知見が色々聞けて勉強になったです。
  • Perl TeX の話はびっくりしました。 TeX であそこまで出来るんですね…
  • 初心者枠からの Perl で辛い所の話も、リファレンス、コンテキストと、あー詰まるよね分かる有るある!って感じで。
  • PadWalker 登場で盛り上がるの、とても好きです〜
  • トミールさんの、Perl での経験が ErlangGolang でも色々思い出される話も良かったです〜。

私の発表

私は3月頃から書き続けてきた Language Server の発表をさせてもらいました。

hkoba.github.io

use fields の布教が少し出来たかも?しれないのが、一番の収穫だったかも? もし fields (というか our %FIELDS ) の活用法に興味が湧いた方がおられたら、 昔の吉祥寺pm7 のスライドもどうぞ〜>

https://hkoba.github.io/slides/kichijojipm7/hkoba.github.io

(うぉーogを後から変えたから展開されぬ…)

謹賀新年/2019目標

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

仲良くして下さった皆様、ありがとうございました。また今年もよろしくお願いします。

2018 の良かったこと

  • Perl方面
  • Tcl/Tk方面
  • Emacs方面
  • Linuxデスクトップ方面
    • リモート勤務者に linux notepc を支給して、開発用サーバー兼クライアントとして使ってもらう業務フローを開拓出来たこと。
      • そのための linux notepc のインストール&設定の手順をほぼ自動化出来たこと。
    • Windows ベースの開発者のための、 WSL + VcXsrv ベースの開発環境を作れたこと。
  • サーバー方面
    • イントラネット上の社内サーバーの letsencrypt 移行が出来たこと。これでようやく、社内向け WebApp を安全に作れる土台が出来たよ…
    • azure と gcp への移行実験を開始できたこと。
  • 生活面
    • ジム通いの結果、以前より体が疲れにくくなった?こと。
    • 新居の生活に順応出来たこと。

2018 の反省点

  • オレオレ言語の研究が完全にストップしたこと…(仕事は充実してた気がするものの、これでは本末転倒…)
  • それに限らず、昨年立てた目標のうち、体力づくり位しか進展が無いこと。
  • 引越し後の荷解きが、予想通り、ほとんど進まなかったこと。
  • 通勤が伸びたことにより、起床が一時間早まったこと。
  • 体の故障が増えてきたこと。時間もお金も掛かった上に、原因不明瞭で終わることが多いのが厳しい。けどそういう年齢よねと。

2019 の目標

  • 新人プログラマーさんが来てくれたら、育ってもらえるよう教育を頑張る。
  • 仕事も個人生活も gcp / gsuite に土台を移す。
    • sshcomm + tkhtml3 ベースで、gcp にも使える deploy / admin ツールを作る。 Ansible とは違う、アドホック性の強い分野に REPL + GUI でアプローチする。
    • yatt_litegcp + oauth2 サンプルを作り、その上に自分の情報生活環境を構築する。
      • 自作会計システムの Web化?
  • 荷解き→物の整理。自宅での研究開発に支障が無いように。(オレオレ言語の研究開発に戻れるように。戻れるように…)

perldebugger (perl -d) を楽に使うコツ的な話

この記事は Perl Advent Calendar 2018 の 12/4 の記事です

Perl Debugger (perl -d) を効率的に使うコツ的な話を書きます。 perl の標準デバッガなんて役に立たないと思っている人のための記事です。

TL;DR step/next は最低限に。break したい個所が予め決まっているなら、デバッグ対象コードにダミー関数の呼び出しを加える手も有る。

Perl Debugger とは

Perl Debuggerperl に標準で添付されたデバッガで、perl 起動時に -d オプションを渡すことで利用できます。使い方は一般的なデバッガとよく似ていて、

  • l 関数名 で関数のソースを表示
  • b 行番号b 関数名 で breakpoint を設定
  • c で実行を継続, s で関数の中までステップ実行, n で次の行まで実行

といった感じです。Term::ReadLine::Gnu などをインストールすると、lb で関数名のタブ補完も効くようになります。 (参考)

h で出るヘルプ画面には沢山コマンドが並びますが、 使うコマンドはそう多くはありません。よく使うコマンドに印をつけたので 参考にどうぞ。

f:id:hkoba501:20181203212405p:plain
perl debugger の help 画面(hkoba のよく使うものに青印をつけてあります)

動的にロードされるコードに 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に参加してきました。

と言っても、私は仕事のトラブルで大遅刻!

勉強会って遅刻すると、すっごく行きづらいと言うか、気持ちが落ちませんか? 私は落ちます。でも懇親会の参加者が減ると主催者さんを困らせちゃうし…と思い、会場へ。

そんな落ち気味な気持ちで会場に着き、部屋に入ると、なぜかプロジェクターでゲーム…???

自作ゲームでプレゼンらしく、こりゃ凄いと思いました。

その後は休憩から LT タイム

どの LT も良かったのですが、個人的に一番刺さったのはこの LT、 スタートアップの一人技術者の立場からの採用の話でした。 曰く 『面接で高印象だった人に、お試しで働いてもらう』 作戦です。

  • 転職か副業を希望している人で、面接で高印象だった人に、
  • 業務委託として、お試しで働いてもらう。
  • リモートワーク+週一のミーティング

speakerdeck.com

確かに、マッチするか否かは一緒に仕事をしてみないと分からない部分が大きいので、これは合理的だと思いました。

実際にどんな仕事をリモートワークで依頼しているのかという質問には、(Webの)フロントエンドとスマホのアプリを例に挙げておられました。確かにそれなら仕事として切り出して発注しやすそうだなと。

なお、週一のミーティングは(相手の都合に合わせるので)どうしても土日どちらかに入れることになるそう。 経営・管理側でないと出来ない技だなあと思いつつ、体調管理についてうかがうと、職住近接で8時間?睡眠を死守しているとのことでした。

たまたまその後の懇親会でもお隣の席だったので、幾つもアドバイスを頂けました。

私も(私がお客さんに提供している)技術スタックのコア部分が、自分にしか扱えない、完全なトラック係数1、SPOF になっています。ビジネスの継続性のために、この技術の継承者をどう育てるかという点が、ずっと懸案でした。

それに対して頂いたアドバイスは、ずばり、それって弟子取りですよねと。そして>

相手をよく知らないまま採用して、後から相手の良い所を探しながら教育するのは、大変だから…と。

そうではなく、先に相手の中の「尊敬できる所」「愛せる所」「見どころ」を見出しておく。見いだせた人だけを、 採用する。つまり、採用する側の、「愛する覚悟」が定まった時だけ、採用するように…しないと辛いよねと。

…これは、とても響く話でした…

上赤さん、有難うございました。

吉祥寺pm15 で Runnable Module パターン(仮)の話をしてきました

kichijojipm.connpass.com

私のスライドはこちらです。 (以前書いたこの日記を、整理し直したものです)

hkoba.github.io

かんそう

毎度ながら、プロマネ・チーム作りの話から、PC Engine の開発の話とか、パンチカードの頃から戦ってこられた方が今はRiotjs って話まで…話の幅がすげーって思いました(こなみ

コードを2つに分けて書く - 一つは自分のため、もう一つは顧客のために

はじめに

この記事は、身近なプログラマー仲間に向けて書かれたまとめです。 私的な、身内向けの解説文ではあるのですが、 仕事でプログラムを組む人全般に通じるように書く、つもりです。

主張

私は基本的に Perl プログラマーです。ですが仕事では、 Perl で 1ファイルで全部書けるような小さな案件でも、

  1. Perl で書かれたモジュールと、
  2. (上記を呼ぶ) Zsh で書かれたスクリプト

のように、2つのプログラムに分けて書いたほうが良いと考えています。 この記事ではその理由を解説します。(この考えは PerlZsh の組み合わせ以外にも、応用できます)

どんなコード量・規模を想定した話か分からないと納得できない人もいると思うので、 参考として、この考えに基づいて書いた昔のコードを載せておきます。 (コードの解説はありません。要望があれば書きます) github.com

仕事のコードを2つの層に分けるメリット

1. 適材適所

一つの理由は、言語毎の得意領域の違いです。 例えばファイル名操作やプロセスの単純な起動とデータのやり取りは、少なくとも私にとっては、 Perl よりも Zsh の方が短時間で書け、 かつ動作検証もコマンド行で出来て楽である、といった風に。

ですが、理由はそれだけではありません。

2. 汎用を目指すコードと、汚れ役になるコードをファイル単位で隔離できる

もっと大きな理由は、ファイルを分けることにより、プログラマーとしての自分を納得させるためのコーディングと、 顧客第一でオーバーエンジニアリングを避けるコーディング、 2つのコーディング方針を使い分けることが出来るからです。

言うまでもないことですが、仕事のコードは顧客・案件に特化した定数 (ファイル名・ディレクトリ名・アカウント名・サーバー名…)を扱う必要が有ります。 汎用的なプログラムを書きたいなら、こうした定数を何らかの形で プログラムの外部に追い出す必要が有ります。

早くからプログラムをファイルとして2つに分けておくことにより、

  1. 『汎用性があるよう、キレイに書きたいコードを入れるファイル』
  2. 『定数をハードコードして良い、 汚れ役 ファイル』(雑事吸収層 と呼んでいます)
    • 顧客の今回の案件に特化した、具体性を殺しすぎないコードを書くファイル
    • (この部分は最初はファイルにせず zsh の shell 変数を使った repl 操作で済ませ、徐々に shell関数へ、そしてスクリプトファイルへとまとめる…でも有りです)

という風に、2つのプログラムで別々のゴール・価値基準を追求できるようになる、という考えです。 一つのプログラムで満たそうとすると無理が出るので、別々のプログラムに分ける、ということです。

脱線しますと、私がこの方針に至った背景には、よく知られたフレーズの一つ↓の影響が大きいように思います。2つの直交した問題領域を扱うなら、間にマッピングをかまさないと、片方に漏れ歪みが出るのは自然な現象ですよね。

『計算機科学のあらゆる問題は別のレベルのインダイレクション(間接参照)で解決できる』

この記事によれば誰が言い出したのかは諸説あるぽいですね>

blog.unfindable.net

設定ファイルは無限に拡張され、半端なプログラミング言語に成り果てる。なら最初から…

ここで、定数のハードコードを避けたいだけなら、 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';