はじめに
こんにちは。Shizen Connect の入澤です。最近、娘のために電動アシスト自転車を買いました。楽しくて一人で乗っています。子供が昼寝している時間が、自分のやりたいことをやるチャンスです。妻は仕事です。子育てエンジニアの休日ログです。
「この部分が変更されたのはいつ頃?」とか、「いつの terraform apply によってこの変更が入ったの?」とか、「このログが出始めたのはいつ頃から?」とか、日頃のちょっとした面倒な確認作業は、完全に Claude Code(※以降、この人のことを「C君」と呼びます。)に任せっきりになっています。自分で調べようとしなくなったエンジニアの行く末は墓場かもしれません。
でも、ちょっと言い訳させてください。コマンドなんて、いったい何個あると思いますか?そのコマンドが、それぞれに複雑極まりないオプションを持つんです。それらを正確に覚えて操作するなんて、そもそも人間のやることではなかったんです。コマンドの使い方を覚えるよりも、もっと注力すべきことが我々にはあるはずです。
C君 はパースが得意
C君 は、どんなコマンドの出力も器用に解析してしまいます。git log、gh run view、terraform plan などの出力を python -c にパイプして、コネコネして無理難題に応えてくれます。確かに今の Python は encoding/decoding が得意だし、便利ですね。
aws lambda list-functions | python -c "import sys, json; funcs = json.load(sys.stdin)['Functions']; [print(f['FunctionName']) for f in funcs]"
そこで厄介おじさんは思いつきました。
「python を禁止して ghc にしてやろうか」
最新の Haskell 環境
思い立った厄介おじさんは Haskell 環境をおさらいします。
C君 に訊いてみると、なんと GHCup というインストーラーが主流であるといいます。GHCup は $HOME/.ghcup/ 以下にシステムを構築するため、以前のように /usr/local/ を汚さずに ghc のバージョンを整えることができるといいます。すごい!
ついでに Haskell Language Server も入れてくれました。
ghc によるパイプ処理
試行錯誤の結果、C君 は ghc -e を見つけました。python -c のように、長いコードスニペットをパイプして実行させることができるようです。
ところが、ghc -e を用いたワンライナーでは import ができないことが発覚しました。
ファイルを作って runghc すれば動きますが、やりたかったのはこれではありません。
なんと。ghc は無理なのか... と思ったら、-e を複数指定できるらしいです。ほんとに?
やはり、-e は複数書けるようです。どうしてこのようなデザインになっているのだろう、と思ったら、-e は --interactive の対話無し版、なのだそうです。なお、ghc にはたくさんのオプションがあるらしく、「If you really want to see every option, then you can pass '--show-options' to the compiler.」というのでオプションを付けてみたところ、1720個のオプションがありました...
結果として、こんな感じで文字列の解析が成功しました。以下は AWS Lambda 関数を列挙したものです。
最終的なコマンド
aws lambda list-functions | ghc \
-package aeson \
-package bytestring \
-package text \
-e ':set -XOverloadedStrings' \
-e 'import qualified Data.ByteString.Lazy as BL' \
-e 'import Data.Aeson' \
-e 'import Data.Aeson.Types (parseMaybe, Parser)' \
-e 'import qualified Data.Text as T' \
-e 'import qualified Data.Text.IO as TIO' \
-e 'import Data.Maybe (fromMaybe)' \
-e 'do { src <- BL.getContents; let { pad n t = t <> T.replicate (max 0 (n - T.length t)) " "; parse o = (o .: "Functions") >>= mapM (\f -> (,,,) <$> f .: "FunctionName" <*> f .: "LastModified" <*> (fromMaybe "N/A" <$> f .:? "Runtime") <*> (f .: "MemorySize" :: Parser Int)); result = decode src >>= parseMaybe parse }; case result of { Nothing -> putStrLn "parse error"; Just rows -> do { TIO.putStrLn $ pad 65 "FunctionName" <> pad 18 "Runtime" <> pad 32 "LastModified" <> "Memory"; TIO.putStrLn $ T.replicate 120 "-"; mapM_ (\(n,d,r,m) -> TIO.putStrLn $ pad 65 n <> pad 18 r <> pad 32 d <> T.pack (show (m::Int) <> " MB")) rows } } }'
Haskell で AWS Lambda
さて、AWS Lambda というと、その手軽さから、Python や Node.js で書きがちのように思いますが、Haskell で Lambda 関数をデプロイできるのでしょうか?C君 に訊いてみました。
書けます。
ライブラリを使うと簡単にビルドできるみたいですね。ここでは二つのライブラリを紹介されました。
- https://theam.github.io/aws-lambda-haskell-runtime/ (The Agile Monkeys)
- https://hackage.haskell.org/package/hal (Nike)
上の方が新しく、下は Nike の公式 OSS ということです。こちらも気になってしまいますね。ランニングアプリなどで使われているのかな?hal を使ってみます。
新しいディレクトリーを作って C君 にやりたいことを伝えると、ササッと整えてくれました。こういう時、正解がわからなくて一日を費やしたりしてましたよね。
あとは、cabal build が出力した bootstrap というバイナリーを .zip にして aws lambda create-function~ でデプロイです。ランタイムに provided.al2023 を指定しました。provided.al2023 は、bootstrap というバイナリーを実行してくれることになっています。
ダイナミックリンクにビルドした場合、GLIBC を一致させておかないと正しく実行できないことはポイントの一つです。サイズによる致命的な損失が無ければ、スタティックリンクの方が安心と言えるでしょう。
さいごに
かくして、Haskell 版の AWS Lambda 関数をデプロイし、呼び出すことができました。
中身は Hello World なので参考にはなりませんが、気になる処理速度は以下の通りです。
- COLD START 時の初期化含む処理時間: 225.49 ms (中央値)
- WARM START 時の処理時間: 1.73 ms (中央値)
今日は娘の2歳の誕生日なので、自宅で焼肉です。そろそろ買い物に行く時間です。
それではまた!
2026年6月28日 入澤









