私が歌川です

@utgwkk が書いている

詳説「感情がない」

「感情はないです」と発話したり、耳にしたりすることがあると思う。同じ「感情がない」という言葉でもいくつか意味が分かれていそう。

どれでもいい場合

「決めの問題」のパターン。選択肢がいろいろあるけど、思い入れや強く推したい根拠があるわけでもないので、好きに決めてほしい (あるいは決められてほしい) と思っているときの「感情がない」はこれ。とりあえずlinterやformatterを入れたいけど、設定はなんかうまく動くならなんでもいいからよしなに頼む!! というときもこれになる。

どうでもいい場合

話題に対する興味がとくにないパターン。聞いた話に対して心を動かされることがなくて、そっか~と思ったときはこれに該当すると思う。共感も反対もしていない、無関心であるということを表明している。話を聞くことはできるけど共感は得られないと思いますが大丈夫でしょうか? と言っている状態で、くまさんデバッグと同じ出来事が起こると思う。

感情がなくなっている場合

周囲のさまざまな出来事に対して、心がとくに動かされず、非常に穏やかな気持ちで向き合っている (あるいは把握した上で無視している) パターン。これを突き詰めると悟りを開くことになるのか? 「感情が切り離されている」という表現を使うこともある。

ほか

他のパターンがあったら教えてください。みなさまは、感情がありますか?

DBスキーマからGoのstruct定義を生成するグッズを書いた

GoでSQLを書いて実行するとき、素のdatabase/sqlだけだとさすがに心もとないのでsqlxなどのライブラリを使ってDBの行をstructにマッピングすると思います。db struct tagでマッピング元のカラム名を指定できるのが便利ですね。

一方で、このstructを定義する作業ですが、テーブルのカラムが多いと大変だし、typoしていたので直して再チャレンジする……ということが往々にしてあると思います。

こういうときのためにstruct定義を生成するツールを書いてみました。 go install github.com/utgwkk/rowstructgen@latest を実行したら使えるようになると思います。今のところMySQLにしか対応していません (普段はMySQLしか使っていないため)。main.goに全ての実装が書いてあってひどい感じなので、気が向いたらなおします。

github.com

以下のようなDBスキーマが schema.sql というファイル名で用意されていたとします。論理削除には目をつぶってください。

CREATE TABLE `users` (
  `id` BIGINT UNSIGNED NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `deleted_at` DATETIME NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

このようなスキーマに対して、rowstructgenで users テーブルに対応するGoのstruct定義を生成するには以下のようにします。

$ rowstructgen -schema schema.sql -table users -struct User -package row -out row/user.go

こうすると row/user.go に以下のようなファイルが生成されます。

package row

import "time"

type User struct {
    Id        uint64     `db:"id"`
    Name      string     `db:"name"`
    DeletedAt *time.Time `db:"deleted_at"`
    CreatedAt time.Time  `db:"created_at"`
    UpdatedAt time.Time  `db:"updated_at"`
}

ISUCONで用意されているようなDBスキーマならこれぐらいでうまく動くのではないでしょうか。ちゃんとやるならALTER TABLE文をシミュレートする必要がありそうですが、自分の目的を満たすにはこれでじゅうぶんかなーと思っています。

みどころ

sqldefをSQLパーサーとして使う

sqldefというDBスキーマの管理ツールがあります。sqldefについては以下のブログ記事などを読んでください。

k0kubun.hatenablog.com

sqldefはSQLのパーサーを内包しているので、これを使わせてもらいます。これによってDBスキーマをなめてコード生成を行うことに集中できました。

MySQLの型をGoの型にマッピングする

ベタッと書いてあってひどい感じですが、書いてあります。

https://github.com/utgwkk/rowstructgen/blob/bc3221a6d169a4f5c51a0538194bc7b8dfb49b10/main.go#L67

ENUMはstring型にしておくので、必要に応じて独自の型にマップさせましょう、というスタンスです (凝ったことをやりたくないから)。JSONはひとまずbyte型にマッピングしていますがこれも諸説あると思います。ところでJSONってbyteにマッピングできるんでしたっけ?

URLにデータを載せつつ、できるだけ短いURLにしたい

sugarheart.utgw.net

イベント支出記録君は、同人誌即売会などでの支出をすぐに記録するためのツール。プリセットに金額を登録しておけば、ワンボタンで支出を記録することができる。CSVダウンロード、TSV形式でのコピー、URLシェアなど、いろいろな方法でデータをエクスポートできる。

下にあるのは、先日のイベントでの自分の支出記録が確認できるURL。

https://sugarheart.utgw.net/event-expenses-tracker/#3AAtzwAAAYeIkjSMzQH0oM8AAAGHiIwcRM0B9KDPAAABh4iIiQ3NAligzwAAAYeIhB9GzQH0oM8AAAGHiEjof80B9KDPAAABh4hGZ8LNA+igzwAAAYeIRHAXzQH0oM8AAAGHiELJ080B9KDPAAABh4hAf3jNASygzwAAAYeIMV7ozQJYoM8AAAGHiC5brc0JxKDPAAABh4grHBPNA+igzwAAAYeIJBG/zQfQoM8AAAGHiCB7ts0B9KDPAAABh4gefvPNAfSg

このサービスはReactを使った単一ページのアプリケーションとなっている。ビルド成果物をAmazon S3上に展開しているだけなので、サーバーサイドの処理が一切ない。そのため、データをURLとして共有するにはひと工夫必要になる。

素朴には、オブジェクトをJSON文字列にしてBase64エンコードしたものURLに含めてしまえばよさそう。が、この方法だとURLが長くなるという欠点がある。先述したURLと同じデータを共有するためにfragment部に1000文字以上使うことになってしまう。データをできるだけ短い文字列に変換できるのが望ましい。

このような場合にMessagePackが使える。MessagePackはデータをコンパクトなバイナリ形式で表現するためのフォーマットである。MessagePackを使えば、JSON文字列よりも短くシリアライズできる。

更に短くシリアライズするための工夫として、オブジェクト型の要素をバラして平坦な配列にすることを考える。以下のような配列があったとして、

[
  {"price":100,"label":"","createdAt":1681602213424},
  {"price":100,"label":"","createdAt":1681602213424},
  {"price":100,"label":"","createdAt":1681602213424},
]

以下のような配列に変換すると、オブジェクト型のキーを持つ必要がなくなり、短くシリアライズできることが期待できる。配列を平坦にすることで、複数の配列ぶんのデータを持たないようにできる。

[
  100, "", 1681602213424,
  100, "", 1681602213424,
  100, "", 1681602213424,
]

実際にこのような変換を施した上でシリアライズしてBase64エンコードしたものが先述したURLになる。fragment部の長さが270文字ほどに収まっている。比べてみれば一目瞭然だと思う。

URL
before https://sugarheart.utgw.net/event-expenses-tracker/#W3sicHJpY2UiOjUwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MjM0OTU4MjB9LHsicHJpY2UiOjUwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MjMwOTYzODh9LHsicHJpY2UiOjYwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MjI4NjIwOTN9LHsicHJpY2UiOjUwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MjI1NzI4NzB9LHsicHJpY2UiOjUwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MTg2OTIyMjN9LHsicHJpY2UiOjEwMDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE4NTI4MTk0fSx7InByaWNlIjo1MDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE4Mzk5MjU1fSx7InByaWNlIjo1MDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE4MjkxMTU1fSx7InByaWNlIjozMDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE4MTQxMDQ4fSx7InByaWNlIjo2MDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE3MTQ5NjcyfSx7InByaWNlIjoyNTAwLCJsYWJlbCI6IiIsImNyZWF0ZWRBdCI6MTY4MTYxNjk1MjIzN30seyJwcmljZSI6MTAwMCwibGFiZWwiOiIiLCJjcmVhdGVkQXQiOjE2ODE2MTY3MzkzNDd9LHsicHJpY2UiOjIwMDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE2Mjc3OTUxfSx7InByaWNlIjo1MDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE2MDQyOTM0fSx7InByaWNlIjo1MDAsImxhYmVsIjoiIiwiY3JlYXRlZEF0IjoxNjgxNjE1OTEyNjkxfV0=
after https://sugarheart.utgw.net/event-expenses-tracker/#3AAtzwAAAYeIkjSMzQH0oM8AAAGHiIwcRM0B9KDPAAABh4iIiQ3NAligzwAAAYeIhB9GzQH0oM8AAAGHiEjof80B9KDPAAABh4hGZ8LNA+igzwAAAYeIRHAXzQH0oM8AAAGHiELJ080B9KDPAAABh4hAf3jNASygzwAAAYeIMV7ozQJYoM8AAAGHiC5brc0JxKDPAAABh4grHBPNA+igzwAAAYeIJBG/zQfQoM8AAAGHiCB7ts0B9KDPAAABh4gefvPNAfSg

URLをもっと短くできる方法があったら教えてください。ソースコードはGitHubで公開しています。

github.com

2023/5/26 追記

id:onk さんに、Microsoft Learnにいいページがあることを教えてもらいました。

learn.microsoft.com

週末日記

金曜日

SMTP++12(少し盛り上がるときめきパーティー) | 2023.3.24 京都木屋町 cafe la siesta でDJをやった。

土曜日

サウナの梅湯に行って、近くの京都ビアラボにビールを飲みにいった。京都ビアラボの5周年イベントをやっていて、奥でDJをやっていたので見にいった。

日曜日

ブルアカのガチャをやったら☆3がすり抜けてきて、いい状態になっていた。

月曜日

ミーハーなのでHUBに行ってトランプをもらおうとしたけど、まだなかった。

github.com/caarlos0/env を使った設定値に必要な環境変数名を列挙する

アプリケーションの設定値を環境変数から注入していますか?

Goの場合、structに設定値を集約するのには GitHub - caarlos0/env: A simple and zero-dependencies library to parse environment variables into structs. などのライブラリが使えると思います。一方でこういうライブラリでstructをネストさせていくと、結局どの環境変数が必要なのかざっと眺めるのが難しいです。

type Config struct {
    Port        int          `env:"PORT" envDefault:"8080"`
    DatabaseDSN string       `env:"DATABASE_DSN,required"` // 必須
    Nested      NestedConfig `envPrefix:"NESTED_"`
}

type NestedConfig struct {
    DSN string `env:"DSN,required"` // 必須
}

↑これぐらいならいいけど実際にはもっと設定項目があるはず。NESTED_DSN という環境変数をセットする必要があるのだけど、↑のような実装だとアプリケーションコードを NESTED_DSN でgrepしてもヒットしない!!

ということでリフレクションで解決しましょう。

package main

import (
    "fmt"
    "reflect"
    "strings"

    "golang.org/x/exp/slices"
)

type Config struct {
    Port        int          `env:"PORT" envDefault:"8080"`
    DatabaseDSN string       `env:"DATABASE_DSN,required"`
    Nested      NestedConfig `envPrefix:"NESTED_"`
}

type NestedConfig struct {
    DSN string `env:"DSN"`
}

func main() {
    // アプリケーションで使う設定値用のstructを渡す
    cfg := Config{}
    envVars := aggregateEnvVars(cfg, "", "")
    for _, ev := range envVars {
        fmt.Printf("%s: %s", ev.Name, ev.FieldPath)
        if ev.Required {
            fmt.Print(" (required)")
        }
        fmt.Println()
    }
}

type envVar struct {
    FieldPath string
    Name      string
    Required  bool
}

func aggregateEnvVars(cfg any, envPrefix, fieldPath string) []envVar {
    st := reflect.ValueOf(cfg)
    var varNames []envVar

    for i := 0; i < st.NumField(); i++ {
        field := st.Field(i)

        // skip unexported field
        if !field.CanInterface() {
            continue
        }

        fieldTy := st.Type().Field(i)

        // envPrefix tagがあるならstruct型のフィールドなので再帰的にたどる
        newEnvPrefix := fieldTy.Tag.Get("envPrefix")
        if newEnvPrefix != "" {
            varNames = append(varNames, aggregateEnvVars(field.Interface(), envPrefix+newEnvPrefix, fieldPath+"."+fieldTy.Name)...)
            continue
        }

        // env:"AAA,required"
        envTag := fieldTy.Tag.Get("env")
        if envTag == "" {
            continue
        }
        xs := strings.Split(envTag, ",")
        varName := xs[0]
        required := slices.Contains(xs, "required")

        varNames = append(varNames, envVar{
            FieldPath: fieldPath+"."+fieldTy.Name,
            Name:     envPrefix + varName,
            Required: required,
        })
    }

    return varNames
}

実行すると以下のような出力が得られます。どのフィールドの値がどの環境変数に対応するのか一目で分かって便利ですね。structのフィールドにコメントを書いていたらそれも出力されてほしいけど、それをやるには静的解析が必要な予感がします。

PORT: .Port
DATABASE_DSN: .DatabaseDSN (required)
NESTED_DSN: .Nested.DSN

YAPC::Kyoto 2023に参加した #yapcjapan

久しぶりのオフライン開催であるYAPC::Kyoto 2023に参加しました。

前日祭見た

前日祭にも参加して、RejectConと東西対抗LTバトルを見ました。みんないい話をしていて、かつ、なんだかついにYAPCが始まったのだな、という感慨がありました。

その後のHelpfeelさん主催の飲み会では、席を詰めまくった結果dankogaiさんの隣に座っていろいろ話していておもしろい感じでした。

トークした

「prototype大全」というタイトルで発表しました。発表スライドはSpeakerDeckで公開しています。

speakerdeck.com

誰もPerlのprototypeに特化した話をしていないのでは? というのが発表のモチベーションでした。もはや業務ではPerlを書いていないのですが、Perlのこういう言語機能によって表現力が広まっている、というのを伝えたかったです。

一方で最近のPerlだと解決済のものも含まれているので、そこはcharsbarさんの発表とあわせて聞けるような形にできるとよかったかもしれません。charsbarさんの発表を見ようと思ったけど、ぶつかり稽古が気になりすぎてそっちに向かってしまいました。

トーク見た

1日の間にすごい量のトークが行われていましたね。見たトークについてひとことずつ感想を書いていきます。

春のエンジニア ぶつかり稽古 2023

会場に行くまで何が起こるのかよく分かっていなかったのですが、オフライン開催の醍醐味っぽいライブコーディングが行われていました。

ジョブキューシステムFireworqのアーキテクチャ設計と運用時のベストプラクティス

TheSchwartzからFireworqに移行する用のおもてなしが行き届いているのは初耳でした。現代ならマネージドなキューを使うというのはありそうだけど、オンプレミスからの移行という要件にフィットしつつ、既存の課題をうまく解決できるように作られているのがよく分かりました。

my$talk=qr{\b((?:ir)?reg(?:ular )?exp(?:ressions?)?)\b}ig;

とにかく絵文字にマッチさせようとすると苦難の連続である、というのは知識として知っていましたが、Perlの正規表現のUnicode対応がしっかりしているというのが印象的でした。

ORM - Object-relational mapping

話の密度としては40分トーク並にあったと思うのですが、高速に前提が共有されたのちに取り組んだ内容が展開されていてよかったです。前提があってこういう決定をした、というのを軸を持って話せるようになりたい、と思いました。

"普通"のWebアプリでWASMを活用する

パーサーを多言語実装向けに展開するのにWASMを使う、というアプローチはなるほどと思いました。とはいえWASMでいけるかどうか、の検証がひきつづき必要そうに見えるのは気になるかも?

公開収録ランチセッション

ChatGPTにJSONの形式を指定して出力されて、そのままAPIのレスポンスとして返す、みたいは話があったと思うけどなかなかすごいことをしているなーと思いながら聞いていました。

ソフトウェアエンジニアリングサバイバルガイド: 廃墟を直す、廃墟を出る、廃墟を壊す、あるいは廃墟に暮らす、廃墟に死す

「廃墟」に対して丁寧に手を打ち、一方でそれぞれの手法に対してトレードオフがあることもスラスラと述べられており、moznionさんの醍醐味だ!! と思いながら聞いていました。どこの会社にもあるであろう問題に対してはっきり言語化されたのを聞けたのは貴重な体験だったと思います。

デプロイ今昔物語 〜CGIからサーバーレスまで〜

FTPでデプロイするところからAWS CLIが出るところまで、40分のうちに時代がどんどん進んでいってておもしろかったです。pull型デプロイについてはよく理解していなかったので勉強になりました。

様々な環境へコマンドラインツールを提供する上での苦労とその対策

プラグイン的な仕組みを考えると動的なプログラミング言語を使うほうがよいとか、テストの実行結果から分析するにはどういう情報があるとよいのか、といった観点は持っていなかったので興味深く聞いていました。JUnit形式のログはだいたいどこでも使える一方で欲しい情報が入っていない、というのはジレンマっぽい。

my new error...

例外を送出しないアプローチと、アラートを解決していくというのが両軸となって話されているのが印象的でした。開発を数カ月間止めて技術的な課題を解決するのに集中できるのはよさそうだけど、そういう意思決定ができた背景も気になります。

ライトニングトーク

どのトークも高速に問題が解決したり、話がどんどん展開したりしていて、LTっていう感じでした。個人的にはflip-flop演算子が活用されていることに感動しました。

キーノート

最初の方は、いつもの調子で話しているな、という印象でしたがだんだんといい話に向かっていってて良かったです。

ありがとうございました

JPAの皆様、YAPC::Kyoto 2023 スタッフの皆様、参加者の皆様、登壇された皆様、ほかみなさまありがとうございました。久しぶりにオフラインのYAPCに参加できて、少しずつですが日常が戻ってきたような感覚と、この3年間で忘れてしまったことが多いので徐々に思い出していかなければならないという感覚が両方ありました。

スピーカーディナーとか、その後のオフィスでの懇親会 (?) などでいろいろな話をしたと思うけど、あまり内容を覚えていない!! どこかで自分がいい話をしていたら誰かあとでコッソリ教えてください。

2023年にYAPCに行くことの意味

この節は全体的にポエムです。そういうつもりで読んでいただければ幸いです。

実は、私はYAPC::Kyoto 2020ではスタッフをやっており、以下の記事をブログに書きました。

blog.yapcjapan.org

今日はこの記事を書いた当時に考えたことについて思い出してここに供養しようと思います。しばらく昔話かつ自分語りをします。

✂️---昔話ここから---

高校に入るまでのプログラミング経験は、PerlでCGIを書いて遊んでいたとか、そういうのがあるけど今日はその話はしません。

高校生の頃 (2012年ぐらい) からエンジニアコミュニティに対する憧れを持っていたのですが、どこで何が開催されているのかその当時は全く分かっていませんでした。また、開催されることが分かっていても場所が遠くて行けないとか、おこづかいが足りないとか、そういうのに阻まれていました。ツイッターでくすぶり続ける日々を送っていたと思います (当時のツイートは全部消えました)。

2015年、大学に入ってKMC (京大マイコンクラブ)に入部したのですが、なんとYAPC::Asia Tokyo 2015というイベントにKMCがスポンサーしている、というのをそこで知ります。よく見るとゲストスピーカーが豪華ですね。YAPC::Asia Tokyo 2015には行きたかったのですが、前週にコミケに行って体力もお金もなくなったので断念しました。

YAPCに初めて参加したのは2017年 (さっきまで2016年だと思い込んでいたけど違った) のYAPC::Kansai 2017 Osakaです。当時のブログ記事を探したけどけっこう淡々としていますね。

blog.utgw.net

それから毎年 (2017年は2回開催されていてすごい) YAPCに行くようになって、次回のYAPC::Fukoka 2017 HAKATAでついに学生旅費支援制度を使ってYAPCに参加するようになりました。

blog.utgw.net

沖縄の回ではAirbnbの宿に紛れ込ませてもらったり、激安ゲストハウスで長めに滞在したりした思い出があります。あとは泡盛を飲みまくって二日酔いになっていた気がする……。帰りに鹿児島に寄って志布志を見れたのがよかったです。

blog.utgw.net

東京の回は卒論の締め切り直前にリフレッシュしたいという気持ちを持ちつつ参加していたと思います。

blog.utgw.net

✂️---昔話ここまで---

さて、YAPCに学生旅費支援制度を使って参加しつづけていた思い出をここまで振り返ってきました。ここまでずっとYAPCに参加できていた理由について考察すると、以下のような要因があるんじゃないかと思っています。

  • 参加するまでの敷居が低い
    • 学生はチケットが無料だし、旅費も支援してくれる
    • 学生でなくてもチケットが安い
  • 割となんでも受け入れられる
    • 「Perlを軸としたコミュニティ」なので、Perlを書いていなくても別にいい
    • Perl出身の人が多い、ぐらいに捉えている

すごい尖った言い方をすると、学生旅費支援制度を使い倒してYAPCのついでにいろいろ見て回っていた自分のような人間がいたわけですね。こういう制度を使うと日本各地で開催されるYAPCに実質無料で参加できるけど、もっとアピールしたほうがよいのでは? 地方の片隅でくすぶっていた自分がエンジニアコミュニティに溶け込むことができたのはYAPCのおかげなので、恩返しをできるとよいのでは? というモチベーションで書いた記憶があります。距離を気にせず参加していろいろ聞ける機会があるのだからどんどん使い倒してほしい、というのは今でも思っています。

これ何の節でしたっけ? 「2023年にYAPCに行くことの意味」でしたね。言いたかったのは、YAPCの門戸は誰にだって開放されているのだからどんどん来てほしい、ということでした。Perlを書いたことがあるかどうかはそんなに問題にならなくて、いろいろな話題に関するトークが聞けるので、興味のあるものだけ見ていくのでよいと思います。とりあえずトークをしていた人にひとこと感想を伝えるぐらいでもよい交流の機会になるはずだし、懇親会で話している人たちの横で話を聞いているぐらいでもよいはず。あとは自分に声をかけてもらえば誰かのところに連れて行くぐらいはできます。そそのかすのが得意なので……。

まとまりのない話になったので強引にまとめると、とにかくYAPCに来てほしい、ということにします。