2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
Haskellを導入した話とHRRの紹介(全1記事)
リンクをコピー
記事をブックマーク
khibino氏(以下、khibino):「Haskellを導入した話とHRRの紹介」ということで発表させていただきます。
自己紹介スライドですけど、Twitterアカウントは「@khibino」です。
僕は8年ぐらい職業Haskellプログラマとして働いています。最近転職したので、前職の話なんですけど、とあるISPで契約管理とか認証バックエンドのシステムの開発の仕事をしていました。
この時にHaskellを導入した時の話をしようかなと思ったのと、あとそれだけだとネタが不足しそうだったので、私が開発している「Haskell Relational Record」というライブラリの紹介を後半に入れています。
まずHaskellを導入した話なんですけど、きっかけは、Perlに代わるGlue言語を検討したくなったことです。
これはもうすでに10年ぐらい前に、これらを置き換えたくなったみたいなところから始まっている話なんですけど(笑)。
Perlでいろいろ書いてありました。Javaのバッチプログラムを呼び出す大量のPerl Scriptが書いてあって。気がついたら私しかメンテナンスしてないんですよね。どうしようかなと思って。
あと、複雑な処理が書いてある部分があるので、実行前に検査したいんですけど、基本的にあんまり検査できない。一応Perlにも、本当に実行が始まる前に走らせる仕組みとか一応あるんですけど、やっぱりそれだけで全部やるのはつらいので。
この頃だと、JavaがUNIXとの親和性あまり高くないんですね。いろいろと。だから「Javaはプラットフォームを選ばない」とか言ってるけど、「むしろ『どのプラットフォームでもうまく動かない』みたいになってたんじゃないかな?」とちょっと思ってたんですけど。
(会場笑)
「困ったな」と思って。
欲しかったのはけっこう単純で、実行前になにかいろいろと、型検査とかでいいんですけど、検査をできること。あとUNIXとの親和性がちゃんと欲しい。あとPerlがスクリプト言語だったので、「スクリプト実行もできるといいんじゃないの?」みたいなのもちょろっと思ったんですけど、ここはどっちがいいのかよくわからなくて。
これは私1人でいろいろ選んで検討してた時期もあったので、けっこう適当なんですけれど。
一時期Common Lispとか検討したこともあったんですけど、これもちょっと(笑)。いくらコンパイル時にマクロでいろいろできるといっても、これだけ全部やるのはつらいんじゃないですか。やっぱり型欲しいですよね、と思ったり。
あと、OCamlとかも考えたんですけど、OCamlを試しているときにHaskellに興味出てきちゃって(笑)。
それでHaskellは、unix packageをその時読んでみたら、けっこうよくできてそうだなと思って。あと「Template Haskellにマクロもあるぜ!」みたいな感じで、これやってみようかなと思いました。
あとこのへんのunixとかfilepathとかdirectoryとかprocessとかって、もうコンパイラにライブラリが入っているんですよね。だから、GHCさえ入っていれば絶対使えるんですよ。
だからそういう面倒くささがないなと思って。いろんなライブラリを入れなきゃいけないということも、このぐらいだったらあんまりないだろうと思って。いよいよ2010年ぐらいから導入しだしました。
すごいですよこれ! むちゃくちゃ古いですよ! みなさんもうわからないんじゃないですかね。Debian lennyとかもう知らないでしょ? Debian 5.0です。5.0! あと、GHC6.8ですから! 8.6じゃないですからね? 6.8ですから!
(会場笑)
6.8です。ヤバイ! cabal installがこの頃なかったんですよ。すごくないですか!?
(会場笑)
まあそれはそれとして。でも、別に小さいプログラムを書くだけだったら大したことないので。
新しく書く部分はもうどんどんPerlで書くのをやめてHaskellにしていって。まぁ私1人でなんですけど。でも、ファイル操作とかJavaをひたすら呼び出すとかそんなのばっかりなので、けっこう簡単なプログラムですね。
1人なんですけど、Haskellのプログラムはあんまり大きくないので、アプリの本数に比べて行数がぜんぜん少ないんですね。アプリ16個で2,700行とか、アプリ22本で5,800行とか、そんな感じなんですけど。あんまり大きくないです。
あとは、Perlで書いてる頃よりだいぶ精神的負荷が下がりましたよね。
それはそうと、最初はバッチプログラムを呼び出すプログラムとか書いてたんですけど、ちゃんと使えるのでだんだんいろんなことに使い始めて。それもほとんど全部私がやってたんですけど。
だから、ふだんから仕事で使っててやっぱり感動したのは、Parser Combinatorが使いやすさですね。
ISPとかだと変なテキスト処理がいっぱいあるんです。とにかくよくわからない整理されていないログがいっぱいあるので、そこから無理やり必要な情報を取り出すはめになるみたいのがけっこう多くて。
Parser Combinatorはここに書いてあるような……山ほどくるトラフィックデータを全部加工してDBに突っ込んだり、社内のIRCがやたらめったらチャンネル立っているのをログを検索したいとかって言われて、一生懸命それ用に加工したり。
そうですね、接続ログ。みなさんがプロバイダとかでこのフレッツとかプロバイダを使うときに、「PPPoEで認証して」みたいな認証ログがものすごい数あるんですけど、そいつを集計するプログラムをHaskellで作りました。
このAttoparsecは、当時Parsecよりだいぶ高速に動作するParser Combinatorとして有名で、この速度に助けられたこともあります。
あと、いろいろトラブルが起きるんですけど、そうすると、実際なにが起きたのかをログから解析しなきゃいけなくて、そういったことを抜き出す仕事もいっぱいありましたが、これもすごい助かりました。
あと、認証バックエンドを作った時に、古い認証サービスの認証データを加工して新しい認証サービスに入れたりする時とかに使いました。
ビルドシステムもHaskellで書きました。
実は前職のISPはバックエンドのリレーショナルデータベースでDB2を使ってたんですけど、そのDB2のパッケージをIBMから配られているDebian用に変換するプログラムを書いて、それ全部ビルドするみたいな。
あと、内製のHaskellライブラリもいっぱい作ったので、それを全部Debianパッケージに変換するプログラムを書いたりしました。全部ビルドすると、気がついたら40分ぐらいかかるようになりました。びっくりしましたけど。
認証バックエンドの認証サーバを作ったんです。Haskellって Software Transactional Memory(STM)が簡単に使えるので、状態のあるマルチスレッドプログラムがすごい書きやすくて助かりました。
オープンリゾルバ検出システム、これけっこうおもしろかったんですけど。
実はISPのお客様にオープンリゾルバの方がいっぱいいらっしゃって、「どのぐらいいるんだろう?」と思って探しにいくというプログラムを書いていた。これもDNSクエリーするコマンドを呼び出すだけのすごい並列度の高いプログラムで、たぶん今でも毎日900並列ぐらいで動いているんじゃないかと思います。
あとは、バックエンドの接続認証サーバはやっぱりマルチスレッドのものを書きました。こういうものが書きやすかったんですね。
あと、やっぱりDomain Specific Language(DSL)ですね。
解こうとした問題に合ったDSLを定義するみたいのは、やっぱりHaskellはけっこうやりやすいんじゃないかと私は思っていて。なんといっても、まずMonadが入ってたりするんですけど。実際どういうふうに使うかみたいのは、今日はあまり詳しく説明できないですけど。
あと、私が作ってるHaskell Relational Recordってライブラリがあって。これはSQLを組み立てるDSLなので、これを使ってデータベースのトランザクション処理みたいなのを書いて、実際に債権データの集計処理とか、さっき言った認証サービスのバックエンドのデータ管理のシステムとかを作りました。
では、ここらへんで一応導入した話のまとめです。
まず、Glue言語の置き換えとしてHaskellを導入しました。書きやすかったのは、テキスト処理とかプロセス制御とかマルチスレッドとか、あとDSLだったという話でした。
ここから後半で、Haskell Relational Recordの紹介なんですけど。これは入門者向けの内容じゃなくなってるかもしれないので、注意事項という感じなんですけど。あんまり厳密な「どういうふうに動いているんだ?」みたいのはとりあえず気にしなくても大丈夫です。ただ、SQLがわからないと厳しいかもしれません。
要するにHaskellのコードとSQLが対応づいている、みたいなのが今日出てくる予定です。なのでそれがなんとなくわかれば、細かいところは気にしなくていいと思います。実際DSLって、そういうふうに適当に書くと、うまく展開されて、なんかいい感じになる、というためのものなので。
一応このURLがプロジェクトページになりまして、「SQLを組み立てるHaskell内DSL」、Haskell Relational Recordライブラリです。
売りは部品化と型安全です。だから、なにか小さいクエリを組み合わせて、より複雑な大きいクエリを作ることができて、部品化がちゃんとできていて、あと部品を組み合わせるときに型をチェックするので、変なものが変なところに組み合わさっても、誤りがあればコンパイル時に発見できるでしょうという感じです。
あと、実はコンパイル時にTemplate Haskellのマクロの機能を呼んで、Database Schemaをデータベースから読んできて、それに合った型定義を生成するので、データベース側ともだいたい型が合っていることがわかります。
このライブラリは2013年ぐらいから開発してました。
いきなりですが、SQLのJOIN(結合)ですね。
これがどういうものかという話なんですけど、これほとんど説明がなにも書いてないですけど。今「person」というテーブルと「birthday」というテーブルがあって、「person」にはnameとageとfamilyが入ってて、「birthday」にはnameとdayいうふうに入っているんですね。こいつをJOINしました。
これはこういう集合演算です。数式アレルギーの人はうんざりする話なんですけど、数式だとこういうものだと思ってください。だからpersonの集合から1個取って、birthdayの集合から1個取って、その1個取ったやつのnameを取り出してるのが等しいようなやつだけ、組を全部集めてくださいって言ってるわけですね。
SQLはこの数式を書こうとしてるんですよ。たぶん……、というか最初に考えた人はそう考えたと思ってて。
Haskellはこの数式に似た記法があるんですよね。例えば「List内包表記」というのがそれで、集合のこの「∈」のところの記号と、この「<-」のですね。「似てるでしょ?」みたいな。まぁ、気分だと思うんですけど。
このリストから1個取り出して、そいつのnameというフィールドが等しくなっていれば、こんだけ全部リストにして返してくださいというのが「List内包表記」ですね。
それで、見た目はちょっと違うけど、これとまったく意味が同じものをdoで書けるんですよ。まったく同じなので読み替えるだけです。この中がどうなったかは気にしなくてもいいんです。
「これと同じようにSQLを組み立てられたらいいんじゃない?」と思ってやってみました。
ゴテゴテしてますけど、personから1個取り出して、birthdayから1個取り出して、そいつのnameというフィールドを取り出したやつを、その結果の2つをくっつけて返してみたいなものが、このList内包表記と同じような、Listモナドと同じようなことをやるクエリがほしいなと思って作ったら、こんなふうになりました。
だから、こういうふうに書くと実はこういうSQLが生成されると。
こうやって定義したやつを展開すると、こういうSQLが生成されます、みたいな。だから、SQLをあんまり知らなくても、もしかして書けちゃうかもしれないみたいな感じのやつです。
このpersonのnameとかbirthdayのnameとかを選ばせるのが面倒くさいという人が多いので、最近は「もうnameというフィールドがあればいいんだろう」みたいなふうに書ける機能が増えたりしました。最近はこれが短くなった。
こうすると型がわかりにくくなることがあるんです。だからこれは一応トレードオフがあるんですよね。なので、みなさん書いてて、「型が決まらなくてびっくり!」みたいなことがあるかもしれないですけど、そういうときは元の書き方とかで工夫してみてください。
ここからはどんどん例が出てくるんですけど。これはLEFT JOINの例ですね。
LEFT JOINをやると、左側が……そうですね。左側はだからMaybeがつかないんですよね。右側がないかもしれないからMaybeがついてます。ここqueryMaybeってやるとLEFT JOINになります。
でも、型を合わせるための書き方が面倒くさい。Maybeじゃないやつから出てるから、justがついてないからjustつけたりしなきゃいけないとか。Maybeのやつから取り出すからfmapみたいのは面倒くさい。逆にここは面倒くさいみたいな。
あと、これは集約関数を使う……あそこでgroupByしてますね。要はfamilyのフィールドでgroupByしたやつが出てきて。
ここでgroupByしたやつじゃないと結果が作れないように型検査になっているのもおもしろいところです。groupByしたやつとsum、集約したやつだけを組み合わせて結果が返ってくるように書いてある。集約してないやつとかを書くと、型エラーになります。
絞り込み。
これはwhereの例ですね。集約をしてるんだけど、whereで条件を絞り込んで出したりもして。これだいぶ複雑なクエリに見えますけど、平成生まれで同じ誕生日の人を数えますみたいな感じですね。これ長いので、この次のページに生成されるSQLが出ますけど。
これ、実はよく見ると同じ表現が何ヶ所か出てくるんですよ。
「#day b」とか、この「count ($ #name p)」とかを使いまわしたりするので。
だけど、これただのHaskellなので、letでこうやって変数に割り当てて再利用できるんですよ。ここの「count $ #name p」とか再利用できる。
だからこのSQLには、COUNTがこことここに2回出てくるというのを、Haskellの中では同じ変数に割り当てて再利用できたり。
あと、これはorderByの例ですね。orderByもこんなふうに書けます。
これは「誕生日」と「名前」の2つのキーでもって複合キーでソートしてますね。単純ですよね。
これでもうほとんど終わりなんですけど、placeholderの例です。
一応placeholderも使えます。placeholderは型を安全にするのが難しくて、今ここにいらっしゃる山本悠滋さんと一緒に取り組んでいる最中なんですけど。型が不安になるけど、一応安全でないところまで書けるようにしてあって。
あとこれがwindow関数というやつです。
window関数はおもしろいですよね。使ったことある人はけっこう少ないかもしれないですけど。説明に書いてあるように、家族ごとの年齢の順位を出すみたいなSQLですけど、こんなやつですよね。
最後のスライドですね。
まとめということで、簡単でしたが、Haskell Relational Recordの紹介でした。
1つめは合成可能性ですね。最初にも言ったんですけど、結局、組み合わせ可能なDSLになっていてSQLの組み合わせができるというのと、定数の定義が再利用できるのでcomposabilityが高いということ。
あと、組み合わせるときに、小さいクエリを組み合わせてより大きなクエリを構成するときに、静的型検査でちょっと安全になりますよと。そういう話でした。ありがとうございました。
(会場拍手)
関連タグ:
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05