書評:アーキテクトの教科書 - 価値を生むソフトウェアのアーキテクチャ構築

「設計ナイト」つながりで、@tyonekuboさんから献本いただきました!

大吉祥寺.pm参加録でも書いた通り、本書(通称:オレンジ本)の著者である米久保剛さん(@tyonekubo)から献本いただきました。この場を借りて改めて御礼申し上げます。ありがとうございます!!!

せっかく献本いただいたので、これは読んでブログに書くしかない!ということで、本書の書評を書きます。

本書の概要

アーキテクトの教科書 価値を生むソフトウェアのアーキテクチャ構築 (米久保 剛 | 翔泳社)

本書は、アーキテクトとしてのスキルを身につけるための教科書です。アーキテクトとは、ソフトウェアのアーキテクチャを設計し、開発チームに指針を示す役割を担う人のことを指します。

全6章からなる本書は、以下のような内容になっています(以下目次より引用)。

  1. アーキテクトの仕事
  2. ソフトウェアの設計
  3. アーキテクチャの設計
  4. アーキテクチャの実装
  5. 品質保証とテスト
  6. アーキテクトとしての学習と成長

よくもまあここまで絞り込んだものだと思います。本来はもっと内容を盛り込みたい、という欲があると思うのですが、それを抑えてこの6章にまとめたのは、著者の経験と知見が詰まっているからこそだと思います。

推しポイント

実コードを交えた説明が簡潔

本書は、アーキテクチャの設計や実装に関する説明が大変簡潔でわかりやすいです。特に、実コードを交えた説明が多く、理論だけでなく実際のコードを見ながら学べるのが良いですね。

Javaがコード例として使われていますが、かなりシンプルなコード例ですべて説明しきっているため、他の言語を使っている人でも十分に理解できると思います。

数多くの図表があってイメージしやすい

アーキテクチャの設計や実装に関する説明は図表が多く登場し、イメージしやすいです。アーキテクチャごとの違いや目的、考え方の違いがそのまま視覚的に示されているため、理解が深まります。

これは、アーキテクチャの設計や実装に関する知識が浅い人にとっては、非常にありがたいですね。

実践的なアーキテクチャの設計に関する知識が得られる

ベテランが読んでも役に立つレベルの内容が書かれていると思いますが、初心者が読んでも理解できるように書かれているのが良いですね。

私もそこそこ長いことIT業界でエンジニアとして働いていますけど、名前は聞いたことがあっても触れる機会がなかったアーキテクチャについて、本書を読んで理解が深まりました。

また、過去に自分がやってきた設計についても該当するアーキテクチャがあって、利点について再認識できたのも良かったです。

分厚すぎず、デカすぎない。何度も読める手軽さ

本書は、分厚すぎず、デカすぎないサイズ感が良いです。何度も読み返すことができるボリュームで、繰り返し読むことで理解が深まると思います。

さらに、この手の書籍にしては2,800円という破格で、アーキテクトとしての第一歩を踏み出すための教科書としては、非常にコスパが良いと思います。

本書をこんな人におススメしたい

  • 2年目~5年目くらいの経験を持つエンジニア
  • 設計で迷いに出くわした経験があるエンジニア
  • 設計がぐちゃぐちゃなプロジェクトに携わっているエンジニア
  • 様々なソフトウェア設計について学びたいエンジニア

私個人の意見としては、DDDについて深掘りする前に本書を読め、と言いたいです。実践的なアーキテクチャの設計や実装に関する知識を得ることができ、頭でっかちではない、地に足の着いたアーキテクトになるためのヒントが詰まっているからです。

まとめ

本書はアーキテクトとしてのスキルを身につけるための教科書として、非常に優れた内容になっています。また、アーキテクトのみならず、エンジニアとしてのスキルを高めたい人にもおススメできる内容になっていると思います。

優れた設計ができるエンジニアがいるプロジェクトでは、保全性の高いソフトウェアが生まれることが多いです。そのため、本書を読んで設計のスキルを高めることは、エンジニアとしてのスキルアップにつながると思います。

大吉祥寺.pm 参加録

7/13(土)に開催されたオフラインイベント「大吉祥寺.pm」に参加したので、その記録や感想となります。

前夜祭「生存者バイアスナイト」

7/12(金)の夜、吉祥寺のビアバー「P2B Haus」で前夜祭が開催されました。お料理、ビール、トークともに大満足の内容でした。

ただ、あまり大っぴらにしづらい話題もあり(この会はそういう会でもある)、ほかの方の発表については詳細を伏せさせていただきます。一つ言えることとしては「結構考えさせられる内容だった」ということでしょう。

なお、本来登壇予定だった@studio3104さんが病欠とのことで、急遽代打を務めさせていただきました。私の発表については特におおっぴらにしても何ら困らないものですので、こちらで共有しておきます。

本編

本編は武蔵野公会堂にて開催されました。以下、聞いたセッションの中でも特に印象に残っているものについて、感想を簡単にまとめます。

基調講演 - @yasuhiro_onishi

テーマが「黒歴史」でなかなか不穏なはじまり方でしたが、その後の話を聞いていると、ようはここで言う「黒歴史」というのは「失敗を恐れずに挑戦し続けること」であるということがわかりました。

かなり意外だったのが、某ホビー系ショップの草創期に大西さんが関わっていたというのが驚きです。

むしろ大西さんにも「生存者バイアスナイト」で発表してほしかった。というか、大西さんの発表が「生存者バイアスナイト day 2」だったのかもしれません。

多様性の時代を生き抜くキャリアプラニング - @tyonekubo

「中長期のキャリアがイメージできない」「どうしたら評価があがるのか?」という質問をしたことがある人、あるいはされたことがある人、双方にとって有益な内容だったと思います。

「ゴールデンサークル理論」(Why→How→Whatの順で話すと人心に訴えかけることができるという理論)に基づき、その一番内側にある「Why」を見つけることが重要であるという話が印象的でした。

また、国内と海外におけるキャリアラダーの違いについても話されておりました。私は海外での就労経験はないのですが(かつて出張で北京に行ったくらい)、なかなか言われてみると結構な差があるんだなと感じました。

技術力でビジネスに貢献するための公式(技術力×ソフトスキル=技術貢献力)というのもなかなか刺さる内容でしたね。

開発部に不満を持っていたCSがエンジニアにジョブチェンしてわかった「勝手に諦めない」ことの大切さ - @saku_rye

Q, なぜエンジニアになったんですか?
A, 開発部に不満があるのに、何もできない自分が悔しかったからです。

冒頭からこの感じで、エンジニアになるきっかけや、エンジニアになってからの経験談が語られていました。エンジニアになるためには「勝手に諦めない」ことが大切であるという話が印象的でした。

また、エンジニアになってからの経験談として、開発部に不満を持っていたCSがエンジニアにジョブチェンしてからの話も興味深かったです。エンジニアになることで、自分の意見を形にできるようになったというのは、なかなか感慨深いものがありますね。

一方でエンジニアとしてある程度視座が広がってきたからこその苦悩もあったようです。エンジニアになる前は「エンジニアになれば解決できる」と思っていた問題が、エンジニアになってみると「エンジニアになったからこそ見える問題」があるというのは、なかなか共感できる話だと思います。

個人的には、「勝手に諦めない。分かり合えるまで根気よく接し続ける。」というのは結構なことだと思う一方、皆が皆それだけの胆力を持ち合わせているわけではないと思うので、個々人の胆力に依存しない方法も模索していく必要があるのかなとも思いました。

組織のスケーリングと持続性 - @tunepolo

事業成長の結果、人手が足りなくなった状況下で、開発チームをスケールアウトする際のオーバーヘッドを仕組みで補うという方法とそれ以外の方法が分水嶺となるという話がありました(エンジニアの開発力を向上させる、アウトソーシングをする、人数を増やさない、の3つ)。

スケールアウトする場合も、職能ごとのチームにするのか、フィーチャーごとのチームにするのかで分けられるという話もありました。

一方、人を増やしても売上が増えない場合の逆回転についても触れられており、これはなかなか苦い経験をされたのだろうなと感じました。

共感できる部分も多かったですが、売り上げが上がっていないチームについては人を増やさない、というのが鉄則じゃないのかと思いました。仮に投資を受けて事業を大きくするタイミングで、しかし売上は伸びていない状況であれば、アウトソースをするか人を増やさずに仕組みで売上を伸ばす方法を模索していくのがいいのではないかと思いました。

その他

大井町放談というランチタイムセッションで、@myfinderさんとともに大井町.pmとして雑談タイムで登壇させていただきました。

技術の話、キャリアの話、お金の話と3つのテーマで話をしましたが、参加した皆さんに新たな気づきを与えることができていたら幸いです。

まとめ

大吉祥寺.pmは、技術者としての視座を広げることができるイベントでした。主催の@magnolia_kさん、スタッフの皆さん、登壇者の皆さん、参加者の皆さん、ありがとうございました。また、このようなイベントを開催していただいたことに感謝いたします。

その他

わいとん「あっ、バレましたか・・・」

「設計ナイト」つながりで、@tyonekuboさんから献本いただきました!

ありがとうございます!!!

@a_suenamiさんに「焼肉」をご馳走になりました!

すえなみチャンス!(概念)

ありがとうございました!

YAPC::Hiroshima 2024 参加録

広島で開催されたYAPC::Hiroshima 2024に「フル参加」してきました。フル参加って言っているのは要するに「前夜祭」「本編」「YAYAPC」の3日間に参加したということです。

なんと今回、本編については何の役割も持たずに気ままに参加したのですが、これは何時ぶりですかね…たぶんYAPC::Hokkaido 2016 SAPPORO以来のことではないでしょうか。

前夜祭

前夜祭ではHono v4に関する発表があるという事で大変集中して聞いていました。いま現在、Honoでご飯を食べている私にとっては大変重要なものですからね・・・

  • v4ではクライアントサイドにも手を伸ばしており、Reactと同一の構文でコンポーネントを書くことができる
  • HonoとViteをベースにしたフルスタックフレームワーク HonoX
  • バックエンド観点でのHonoはその互換性に手を加えることなくそのままv4に移行できる

それからキャッシュバスターズの発表もありました。こちらは晩酌をしながら聞いていたのであまり覚えていませんが、聞いていて「あるあるー」「ですよねー」という感じでした。みな同じような悩みを抱えているんだなあと。

本編

今回はスピーカーでもスタッフでもなく一般参加者として参加しました。スポンサーチケットを使っても良かったのですが、直前に念のため一般参加チケットを自費購入しており、スポンサーチケットは社員におすそ分けとしました。

今回聞いたセッションは以下の通りです。割と廊下でのんびり談話していたり個人的なハックをしていたので、聞いたセッションは少なめです。

2024年冬のPerl

charsbarさんのセッションで、まさかのリモート登壇。久々にご本人にご挨拶できると思っていたのですが、おあずけになってしまいました。

主にPerlエコシステム(とくにミラーとかセキュリティなど)に関する話題と、5.38以降の新機能についての解説がありました。

  • Security Issue(SSL証明書の検証に関する問題)があるため、HTTP::Tinyは最新版(0.083 or later)にアップデートしましょう →CVE-2023-31486
  • SSLがコアに入る未来が来るかも
  • UnicodeとWindows周りでperlにもセキュリティ対応のための更新が入るのでは
  • Strawberry Perlが復活!
  • 5.38以降の新機能 (詳しくはモバイルファクトリー社のテックブログ にも書かれている)
    • 個人的には export_lexically 関数が強すぎるなーと思いました。
    • モジュール末尾に 1; を書く必要がなくなったのは感慨深いです。
  • 今後来そうなやつ
    • 特殊変数の別名 → English モジュール相当のものかな
    • 文字列テンプレート → qt<ほげ{$val}ふが> みたいにかけるっぽい
    • 条件付きアロー演算子 → $obj?->method みたいなやつ
    • メタプログラミング → Class::MOP を現代風にしたやつかな
  • 5.8.1がツールチェーンのサポート下限から脱落
    • 今後は10年以内のものをサポート
    • 今は例外的に5.16を下限とする
    • PlackやIO::Socket::IPなどが既に5.12を下限にしている
  • Rakuについて

大変久々にPerl成分を摂取できたので、大満足でした。

Go to Cloudflare Workers ~ 移行から 0.5 年以上運用する

codehexさんのセッション。Cloudflare Workersに以降してから半年経過したというNOT A HOTELのAIアシスタント周辺の話を聞きました。

  • いくつかのフェーズを経て、Cloudflare Workersに移行した
  • デプロイが速くてまるでオフライン開発みたい
  • wrangler dev だけで環境構築が終わるので簡単
  • エッジノードで処理が行われるので、レイテンシが低い
  • アプリケーションはHono
  • monorepo構成、pnpmで管理
    • .code-workspace で複数アプリをどれもプロジェクトルートとして扱える
  • 運用にはログ・監視・トレースが必要
    • 基本的にはこれらの基盤は自作かカスタマイズか
      • (後で聞いた話だとこのあたりをサポートするサービスがCloudflareにはあるらしい)
    • リクエストの最後にログのキューを作成するHono middlewareを作成
  • pnpm patch で外部ライブラリを改造
  • cloudflare discordにへばりついてバグレポなどをしているらしい。胆力がすごい。

古い技術について—SMTP現代事情つまみ食い—

azumakuniyukiさんのセッション。SMTPについての話でした。というか、もうSMTPと言ったらこの方、この方と言ったらSMTPですよ!

メールに関する技術も最近触らなくなってきていたので、情報のアップデートができてよかったです!

YAYAPC

YAYAPC::Hiroshima。実質上のDay2とか、帰ってきた大人のYAPCとかなんとか。そんな会の司会を仰せつかっており、大人のYAPCと同様に細かいことはシェアできませんが、当日の様子を一つのツイートで表したものがこれです。

欽ちゃんのスコアボードについてはこちらがベースとなっており、私のほうでWebSocket対応をさせた次第です。もっと余力があったら審査員用の物理ボタンを用意するところだったのですが・・・

広島飯&おみやげ

正直なところ、広島飯って本当にお好み焼きと牡蛎しか知らなかったんですが、実際に行ってみると広島飯はお好み焼きと牡蛎以外にも結構バラエティに富んでいて、3日間ではまだまだ食べきれないくらいのものがありました。

お好み焼き

まぜそば

尾道ラーメン

ぷよまん

「生きとったんかワレェ!!!」

その他

まかまかさんと一緒に「広島ミックスあゆむバー」さんにお邪魔したのですが、入店するや否や女装子なあゆむさんとお客さんがテストコードの話をしていたりと、広島の懐の深さに触れることができました。聞くところによるとCOBOLerが結構いらっしゃるとかなんとか・・・あゆむさんありがとうございました!

それから、銭湯に行ったらこんなことも。銭湯民族、考えることは一緒ですね。

振り返りなど

今回は本編については特に何もせず、ただただ参加していたのですが、大変楽しい3日間でした。

広島という街の素敵さにも触れることができ、また、YAPCというイベントの素晴らしさも改めて感じることができました。

次のYAPCはどこで開催されるのでしょうか。楽しみですね。

そして広島はまたいつか行くぞ!

写真

うつ・ひきこもりの人に向けて、ITエンジニアとして働くことについて話してきた

昨年の12月に、うつ・ひきこもりの人に向けて、ITエンジニアとして働くことについて、オンライン講義という形式で話してきました。

事の発端

昨年の10月ころに、私の地元である函館にある就労支援事業所の「Ponte函館」さんから、講師をお願いしたいという依頼をメールでいただいておりました。

テーマは「うつ・ひきこもりの人に向けて、ITエンジニアとして働くことについて」ということでした。私の生まれ育ちや社会人経験をざっくりまとめたライフヒストリーと、ITエンジニアとして働くうえで必要なスキルや心構えなどについてを話しました。

資料について

スライドを以下に公開します。パワポ芸だったり、キャッチーな言葉を意識的に使っていることもあり、だいぶ情報商材っぽいうさんくさいスライドになってしまった点は反省しております。ただ、思ったことをそのまま書いているので、まあいいかという感じです。

色々ご意見等あろうかと思います。その場合は、Twitterにてどうぞ。但し、生産的ではないと思われる言論には反応しません。ご了承ください。

まとめ

私自身は少なくともひきこもりではないですが、過去にうつの傾向があった時期があります。そのような経験を踏まえて、今回の講義をさせていただきました。

より多くの方がITエンジニアとして働くために、何かしらのヒントになれば幸いです。

SAFE Stackで立ち上げてみる

※このエントリはF# Advent Calendar 2023の17日目の記事です。

※F#歴2週間かつ趣味で触っている程度(つまり経験値は実質2日ほど)の人間が書いています。

目標

SAFE Stackというものがあるようなので、SAFE StackとF#に入門してみることにしました。

SAFE Stackとは何なのか?

Isaac Abraham氏による解説を見てみましたが、どうやら以下のようなものらしいです。

Throughout this week, we've published a series of articles contributed by some well-known people within the F# community focused on web and cloud programming. Today, we're announcing the launch of the SAFE stack initiative, which brings all of the elements together as a cohesive story:

- `Saturn` model for server-side web programming
- `Azure` for cloud-based systems
- `Fable` for Javascript-enabled applications
- `Elmish` for an easy-to-understand UI programming model

Saturn, Azure, Fable, Elmishの頭文字を取ってSAFE Stackと呼んでいるようです。

Saturn - F#におけるMVCフレームワークの一つ

公式サイトによると、以下のような特徴があるようです。

- Modern programming model
  - Saturn combines the well known MVC pattern with the power of FP and F# to make web programming fun and easy.

- High performance
  - Saturn builds on top of highly optimized, and battle tested technologies such as ASP.NET Core, Giraffe and Kestrel.

- Developer experience
  - Saturn provides set of tools, templates and guides that makes creating and maintaining applications seamlessly.
  
- Created by Community
  - Saturn is created and maintained by well known members of the F# OSS Community and supported by industrial users.

要約すると、MVCパターンと関数型言語のアプローチを組み合わせた書き方ができ、パフォーマンスはGiraffeやKestrelに根ざした最適化済みのものであり、開発者向けのツール・テンプレが用意されていて、しかもOSSである、とのことです。最初の項目しか頭に入ってきていませんが、なかなか良さそうですね。

Azure - Microsoftのクラウドサービス

公式サイト

SAFE Stackの中では飛びぬけて有名なので、説明は省略します。

Fable - F#からJavaScriptを生成するコンパイラ

公式サイトを見て驚きました。まさか、F#からJavaScriptを生成するコンパイラがあるとは。Fableを使うと、F#で書いたコードをJavaScriptに変換して、Webブラウザ上で動かすことができるようです。 ちょっと凄すぎてよくわかりません・・・

Elmish - Elmの「Model View Update」を参考にした抽象化機構?

公式サイトを見てみましたが、いまいちピンと来ず。。。
もう少し深く調べてみたところ、Elm Architectureなるものがあるらしく、ElmishはそれをF#で実装したもののようです。

これらの情報から、ElmishはElmの「Model View Update」を参考にした抽象化機構っぽいな?という程度の理解しかできませんでした。

ともあれ、一旦これでSAFE Stackについてはなんとなくわかったようなわからんような感じになりました。

事前準備

VSCodeのインストールとDev Container Extensionのインストール

まずVSCodeが必要となりますので、インストールします。

次に、VSCodeの拡張機能であるRemote - Containersをインストールします。これを次の環境構築で使います。

環境構築

devcontainerを使って環境構築を行っていきます。

devcontainerの作成

まず、ブランクのディレクトリ(ここではディレクトリ名を fsharp-practice とします)を作成し、VSCodeで開きます。

次に、VSCodeのコマンドパレットを開き、 開発コンテナ―: コンテナ―機能の構成 を選択したら、イメージとして F# on Fedora を選択していきます。featuresには Node.js (via nvm), yarn and pnpm (node) を選択します。

その後、 開発コンテナ―: コンテナ―でリビルドして再度開く を選択します。しばらく待つと、 fsharp-practice ディレクトリがコンテナ―内にマウントされた状態でVSCodeが開きます。これで環境構築は完了です。

つくってみる

SAFE StackのQuickstartを参考に、 Hello, world! を返すJSON APIを作ってみます。

SAFE Stackのテンプレートをインストールする

以下のコマンドで入ります。

1
dotnet new install SAFE.Template

SAFE Stackのテンプレートを使ってプロジェクトを作成する

これもコマンドだけでOKです。

1
dotnet new SAFE

この時点で、以下のようなファイル構成になっているはずです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
vscode ➜ /workspaces/fsharp-practice $ tree
.
├── Build.fs
├── Build.fsproj
├── fsharp-practice.sln
├── global.json
├── Helpers.fs
├── package.json
├── package-lock.json
├── paket.dependencies
├── paket.lock
├── paket.references
├── README.md
├── src
│ ├── Client
│ │ ├── App.fs
│ │ ├── Client.fsproj
│ │ ├── index.css
│ │ ├── Index.fs
│ │ ├── index.html
│ │ ├── paket.references
│ │ ├── postcss.config.js
│ │ ├── public
│ │ │ └── favicon.png
│ │ ├── tailwind.config.js
│ │ └── vite.config.mts
│ ├── Server
│ │ ├── paket.references
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── Server.fs
│ │ └── Server.fsproj
│ └── Shared
│ ├── paket.references
│ ├── Shared.fs
│ └── Shared.fsproj
└── tests
├── Client
│ ├── Client.Tests.fs
│ ├── Client.Tests.fsproj
│ ├── index.html
│ ├── paket.references
│ └── vite.config.mts
├── Server
│ ├── paket.references
│ ├── Server.Tests.fs
│ └── Server.Tests.fsproj
└── Shared
├── paket.references
├── Shared.Tests.fs
└── Shared.Tests.fsproj

11 directories, 39 files

ローカル開発に使うツール群をインストールする

何やら色々入るようです。

1
dotnet tool restore

こんな感じのログが出ます。NuGetで4つほどパッケージが入っているようです。

1
2
3
4
5
6
7
8
Skipping NuGet package signature verification.
Skipping NuGet package signature verification.
Skipping NuGet package signature verification.
Skipping NuGet package signature verification.
Tool 'paket' (version '8.0.0') was restored. Available commands: paket
Tool 'fable' (version '4.1.4') was restored. Available commands: fable
Tool 'femto' (version '0.18.0') was restored. Available commands: femto
Tool 'fantomas' (version '6.2.3') was restored. Available commands: fantomas

ローカル開発サーバーを起動する

ここまで来たら起動もしていきましょう。

このコマンドでビルドもやってくれるようです。

1
dotnet run

最初20秒くらいは何も起きませんでしたが、その後、以下のようなログが出てきました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
run Run
Building project with version: LocalBuild
Shortened DependencyGraph for Target Run:
<== Run
<== InstallClient
<== Clean

The running order is:
Group - 1
- Clean
Group - 2
- InstallClient
Group - 3
- Run
Starting target 'Clean'
/workspaces/fsharp-practice/src/Client> "dotnet" fable clean --yes (In: false, Out: false, Err: false)
Fable 4.1.4: F# to JavaScript compiler
Minimum fable-library version (when installed from npm): 1.1.1

Thanks to the contributor! @zaaack
Stand with Ukraine! https://standwithukraine.com.ua/

This will recursively delete all *.fs.js[.map] files in /workspaces/fsharp-practice/src/Client
No files have been deleted. If Fable output is in another directory, pass it as argument.
Finished (Success) 'Clean' in 00:00:00.4986175
Starting target 'InstallClient'
.> "/usr/local/share/nvm/versions/node/v20.10.0/bin/npm" install (In: false, Out: false, Err: false)
npm WARN deprecated querystring@0.2.1: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated uuid@3.2.1: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.

added 171 packages, and audited 172 packages in 15s

21 packages are looking for funding
run `npm fund` for details

3 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
npm audit fix --force

Run `npm audit` for details.
npm notice
npm notice New patch version of npm available! 10.2.3 -> 10.2.5
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.2.5
npm notice Run npm install -g npm@10.2.5 to update!
npm notice
Finished (Success) 'InstallClient' in 00:00:15.7005897
Starting target 'Run'
/workspaces/fsharp-practice/src/Shared> "dotnet" build (In: false, Out: false, Err: false)
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
Restored /workspaces/fsharp-practice/src/Shared/Shared.fsproj (in 163 ms).
Shared -> /workspaces/fsharp-practice/src/Shared/bin/Debug/net8.0/Shared.dll

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:03.69
server: /workspaces/fsharp-practice/src/Server> dotnet watch run
client: /workspaces/fsharp-practice/src/Client> dotnet fable watch -o output -s --run npx vite
/workspaces/fsharp-practice/src/Server> "dotnet" watch run (In: false, Out: true, Err: true)/workspaces/fsharp-practice/src/Client> "dotnet" fable watch -o output -s --run npx vite (In: false, Out: true, Err: true)

server: dotnet watch ⌚ Polling file watcher is enabled
client: Fable 4.1.4: F# to JavaScript compiler
client: Minimum fable-library version (when installed from npm): 1.1.1
client: Thanks to the contributor! @sasmithjr
client: Stand with Ukraine! https://standwithukraine.com.ua/
client: Using polling watcher.
client: Parsing Client.fsproj...
client: .> dotnet restore Client.csproj -p:FABLE_COMPILER=true -p:FABLE_COMPILER_4=true -p:FABLE_COMPILER_JAVASCRIPT=true
client: Determining projects to restore...
client: Paket version 8.0.0+6bcb14ec191f11e984ff0e58016f5987a5cfa8f6
client: The last full restore is still up to date. Nothing left to do.
client: Total time taken: 0 milliseconds
server: dotnet watch 🚀 Started
client: Paket version 8.0.0+6bcb14ec191f11e984ff0e58016f5987a5cfa8f6
client: Restoring /workspaces/fsharp-practice/src/Client/Client.csproj
client: Starting restore process.
client: Total time taken: 0 milliseconds
client: Restored /workspaces/fsharp-practice/src/Client/Client.csproj (in 214 ms).
client: 1 of 2 projects are up-to-date for restore.
server: Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: /workspaces/fsharp-practice/src/Server/obj/Debug/net8.0/staticwebassets
server: at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.<CheckForChangedFiles>b__23_0(FileSystemInfo f)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.ForeachEntityInDirectory(DirectoryInfo dirInfo, Action`1 fileAction)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.ForeachEntityInDirectory(DirectoryInfo dirInfo, Action`1 fileAction)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.ForeachEntityInDirectory(DirectoryInfo dirInfo, Action`1 fileAction)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.ForeachEntityInDirectory(DirectoryInfo dirInfo, Action`1 fileAction)
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.CheckForChangedFiles()
server: at Microsoft.DotNet.Watcher.Internal.PollingFileWatcher.PollingLoop()
client: Some Nuget packages contain information about NPM dependencies that can be managed by Femto: https://github.com/Zaid-Ajaj/Femto
client: Project and references (67 source files) parsed in 10372ms
server: warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
server: Storing keys in a directory '/home/vscode/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed. For more information go to https://aka.ms/aspnet/dataprotectionwarning
server: info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[62]
server: User profile is available. Using '/home/vscode/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
server: info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
server: Creating key {b2652658-4977-45a5-809c-2f3f8a9cc018} with creation date 2023-12-16 16:56:54Z, activation date 2023-12-16 16:56:54Z, and expiration date 2024-03-15 16:56:54Z.
server: warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
server: No XML encryptor configured. Key {b2652658-4977-45a5-809c-2f3f8a9cc018} may be persisted to storage in unencrypted form.
server: info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
server: Writing data to file '/home/vscode/.aspnet/DataProtection-Keys/key-b2652658-4977-45a5-809c-2f3f8a9cc018.xml'.
server: warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
server: Overriding HTTP_PORTS '8080' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://localhost:5000'.
server: info: Microsoft.AspNetCore.Server.Kestrel[0]
server: Unable to bind to http://localhost:5000 on the IPv6 loopback interface: 'Cannot assign requested address'.
server: info: Microsoft.Hosting.Lifetime[14]
server: Now listening on: http://localhost:5000
server: info: Microsoft.Hosting.Lifetime[0]
server: Application started. Press Ctrl+C to shut down.
server: info: Microsoft.Hosting.Lifetime[0]
server: Hosting environment: Development
server: info: Microsoft.Hosting.Lifetime[0]
server: Content root path: /workspaces/fsharp-practice/src/Server
client: Loaded Feliz.HookAttribute from ../../../../home/vscode/.nuget/packages/feliz.compilerplugins/2.2.0/lib/netstandard2.0/Feliz.CompilerPlugins.dll
client: Loaded Feliz.ReactComponentAttribute from ../../../../home/vscode/.nuget/packages/feliz.compilerplugins/2.2.0/lib/netstandard2.0/Feliz.CompilerPlugins.dll
client: Loaded Feliz.ReactMemoComponentAttribute from ../../../../home/vscode/.nuget/packages/feliz.compilerplugins/2.2.0/lib/netstandard2.0/Feliz.CompilerPlugins.dll
client: Started Fable compilation...
client: Fable compilation finished in 8173ms
client: .> npx vite
client: Watching ..
client: VITE v5.0.5 ready in 1074 ms
client: ➜ Local: http://localhost:8080/
client: ➜ Network: use --host to expose
client: ➜ press h + enter to show help

ブラウザで確認する

ブラウザで http://127.0.0.1:8080 にアクセスすると、以下のような画面が表示されました。

テキストエリアに文字列を入れてから Add ボタンを押すことで、その内容がリストに追加されるようです。簡易的なToDoアプリでしょうか。

まとめ

本当はこのあとロジックの修正とかまでやってみたかったんですが、なかなか手が付かず、SAFE Stackなアプリケーションの立ち上げまでやっただけでした。

出来上がったソースコードを読んだりいじりながら、SAFE StackとF#の理解を深めていくことにします。

本当に初心者が書いたエントリですが、何かお役に立てれば幸いです。

HonoをAzure Functionsで動かす

※このエントリはHono Advent Calendar 2023の10日目の記事です。

Azure Functionsとは

Azure Functionsは、MicrosoftがAzure上で提供する、サーバーレスな関数を作成するためのサービスです。Azure Functionsは、以下のような特徴があります。

サーバーレス

Azure Functionsは、サーバーレスなサービスです。サーバーレスとは、サーバーを意識せずに、コードを実行できることを指します。Azure Functionsでは、コードを実行するためのサーバーを意識する必要がありません。また、コードを実行するためのサーバーを自分で用意する必要もありません。

イベント駆動

Azure Functionsは、イベント駆動なサービスです。Azure Functionsでは、以下のようなイベントをトリガーに、コードを実行することができます。

  • HTTPリクエスト
  • メッセージキュー
  • データベースの変更
  • タイマー

今回はHTTPリクエストをトリガーにコードを実行する方法となります。

言語の選択肢が豊富

Azure Functionsでは、以下の言語を利用して、コードを実行することができます。

  • C#
  • Java
  • JavaScript
  • TypeScript
  • PowerShell
  • Python

その他にも、カスタムハンドラーDockerコンテナ を利用して、さらに多くの言語を利用することができます。

HonoをAzure Functionsで動かす

まずAzure Functions Core Toolsを利用して、ローカルな環境でHonoをAzure Functionsで動かしてみます。

今回はすぐに動作を試せるように、サンプルを使いましょう。サンプルはこちらで公開しています。

Dev Containerで開発環境を構築する

まずは、開発環境を構築します。開発環境はDev Containerで構築します。Dev Containerという拡張機能を利用しますので、Visual Studio CodeにDev Containerをインストールしてください。

次に、Visual Studio Codeで、ytnobody-hono-on-azure-functions-prototypeを開きます。コマンドパレットを開き、Remote-Containers: Reopen in Containerを選択します。すると、Dev Containerが起動します。初回は起動までに結構な時間がかかります。

Dev Containerが起動したら、以下のコマンドを実行して、依存関係をインストールします。

1
npm install

一緒に azure-functions-core-tools もインストールされます。これがAzure Functions Core Toolsです。

Azure Functions Core Toolsを起動して動作確認する

Azure Functions Core Toolsを起動して、動作確認をします。以下のコマンドを実行します。

1
npm run start

このコマンドでビルドも行われ、Azure Functions Core Toolsが起動します。

以下のようなログが出力されましたら、Azure Functions Core Toolsが無事に起動しています。

1
2
3
4
5
6
7
8
[2023-12-09T23:11:32.503Z] Worker process started and initialized.

Functions:

ytnobody-hono-func: [GET,POST] http://localhost:7071/api/ytnobody-hono-func

For detailed output, run func with --verbose flag.
[2023-12-09T23:11:36.744Z] Host lock lease acquired by instance ID '000000000000000000000000E7F5F683'.

Azure Functions Core Toolsが起動したら、ブラウザでhttp://localhost:7071/api/ytnobody-hono-funcにアクセスします。以下のようなJSONが表示されましたら、無事に動作しています。

Alt text

実際にAzure Funtionsでも動作した!

一応実環境でも同じように動作するか確認してみました。以下のURLにアクセスしてみてください(そのうち消します)。

https://ytnobody-hono-on-azure-functions-prototype.azurewebsites.net/api/ytnobody-hono-func

POSTメソッドでJSONを送ると、そのデータが含まれたJSONが返ってきます。

この方法の制約

Honoでアプリケーションを開発する際、複数のハンドラーを実装することが多いでしょう。つまり、 GET /POST /item に対応したいというように、複数のエンドポイントを実装しますよね、という話です。

ところが、Azure FunctionsのHTTP Triggerでは、1つの関数に対して1つのエンドポイントしか設定できません。つまり、 GET /POST /item に対応するためには、2つの関数アプリを作成する必要があります。

また、Azure FunctionsのHTTP Triggerでは、function.jsonというファイルに、エンドポイントの設定を記述します。ここにHTTPメソッドを指定するため、Hono側でHTTPメソッドの指定をしたとしても、Function.jsonの設定が優先されてしまいます。

そのため、Honoアプリケーション側で指定するHTTPメソッドは基本的にはallを指定することになるでしょう。

Hono coreへのPull Request

Hono coreに、今回作成したAdapterを含むPull Requestを送りました。まだDraftの状態ですが、以下のURLから確認できます。

https://github.com/honojs/hono/pull/1797

もうすこしブラッシュアップして、マージできるようにしたいと思います。

もう一つの方法:Docker on Azure Functions

Azure Functionsでは、Dockerコンテナを利用して、さらに多くの言語を利用することができます。これを利用すると、Honoをそのまま動かすことができます。

Dockerコンテナを利用する方法は、以下のURLに詳しく書かれています。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-function-linux-custom-image

おわりに

Azure FunctionsでHonoを動かす方法を紹介しました。Azure Functionsは、サーバーレスなサービスで、イベント駆動なサービスです。TypeScriptにも対応しており、nodeもv18が使える状態でしたので、Honoを動かすことができます。

また、Azure Functionsで動作するように、Hono coreにもPull Requestを送りました。ブラッシュアップの末にこれがマージされれば、制約はありますがAzure FunctionsでHonoを動かすことができるようになります。

雑なデータ加工に使えるORM:Otogiri

※このエントリはPerl Advent Calendar 2023の3日目の記事です。

※元々は「雑なデータ加工に使える小技5撰」でしたが内容がOtogiriだらけなので、タイトルを変更しました。

Otogiriとは

拙作のPerl製ORM(もどき)です。Otogiri - CPANを参照してください。

特徴として、以下のようなものがあります。

  • スキーマ定義がない(DB/テーブルにあるものを正としている)
  • データは全てハッシュリファレンスかその配列で表現される
  • 端的に言えば DBIx::Sunny + SQL::Maker

MetaCPANに公開されているモジュールですので、cpanm Otogiriでインストールできます。

Otogiriの基本的な使い方

DB接続

podにも書いてありますが、DB接続は以下のようにして行います。以下の例ではmysqlに接続しています。

1
2
3
4
5
6
use Otogiri;
my $db = Otogiri->new(connect_info => [
'dbi:mysql:dbname=mydb',
'dbuser',
'dbpassword'
]);

strictモード

Otogiriはデフォルトでstrictモードで動作します。strictモードはSQL::Makerのstrictモードそのままの制約がつきます。

strictモードはこちらに記載があるようなSecurity Issue(JSON SQL Injection)への対策として、SQL::Makerに実装されています。もしWebアプリケーションにおいて、ユーザーからの入力をSQLに埋め込む場合はstrictモードを有効にすることを強くお勧めします。

strictモードを解除するには、DB接続時に以下のようにします。

1
2
3
4
5
6
7
8
my $db = Otogiri->new(
connect_info => [
'dbi:mysql:dbname=mydb',
'dbuser',
'dbpassword'
],
strict => 0
);

これはselectやinsertの際の記述方法に影響があります。別途、selectやinsertの説明の際に触れます。

データ取得

データ取得はselectメソッドを使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
## strictモードが有効の場合
my @rows = $db->select(user => { id => sql_in([1, 4]) });

## strictモードが無効の場合
my @rows = $db->select(user => { id => [1, 4] });

## 以下のようなデータが@rowsに入ります。
## (
## {
## id => 1,
## name => 'ytnobody',
## age => 43,
## created_at => '2023-12-03 00:00:00'
## },
## {
## id => 4,
## name => 'somebody',
## age => 36,
## created_at => '2023-12-03 01:00:00'
## },
## )

単一データを取得する場合はfetchメソッドを使います。

1
2
3
4
5
6
7
8
9
my $row = $db->fetch(user => { id => 1 });

## 以下のようなデータが$rowに入ります。
## {
## id => 1,
## name => 'ytnobody',
## age => 43,
## created_at => '2023-12-03 00:00:00'
## }

データ挿入

データ挿入はinsertメソッドを使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
## strictモードが有効の場合
$db->insert(user => {
name => 'ytnobody',
age => 43,
created_at => sql_raw("datetime(now)"),
});

## strictモードが無効の場合
$db->insert(user => {
name => 'ytnobody',
age => 43,
created_at => \"datetime(now)",
});

データ更新

データ更新はupdateメソッドを使います。

1
2
3
4
5
6
7
8
9
10
11
## strictモードが有効の場合
$db->update(user => {
age => 44,
created_at => sql_raw("datetime(now)"),
}, { id => 1 });

## strictモードが無効の場合
$db->update(user => {
age => 44,
created_at => \"datetime(now)",
}, { id => 1 });

データ削除

データ削除はdeleteメソッドを使います。

1
2
3
4
5
## strictモードが有効の場合
$db->delete(user => { id => sql_in([1, 4]) });

## strictモードが無効の場合
$db->delete(user => { id => [1, 4] });

トランザクション

トランザクションはtxn_scopeメソッドを使います。

1
2
3
4
5
6
7
8
9
10
11
12
my $txn = $db->txn_scope;
$db->insert(user => {
name => 'ytnobody',
age => 43,
created_at => sql_raw("datetime(now)"),
});
$db->insert(user => {
name => 'somebody',
age => 36,
created_at => sql_raw("datetime(now)"),
});
$txn->commit;

もっと複雑な使い方

ここから先はさらに複雑な使い方を紹介します。

データ取得時にカラム指定を行う

Otogiriは基本的にテーブルにある全てのカラムを取得します。これは「雑にデータ取得と処理を行うこと」を主目的としているため、そのままではカラム指定を行うことができません。

ですが、Otogiriにはプラグイン機構があります。これはOtogiri::Pluginというモジュールによって実現されています。

カラム指定を行うプラグインはOtogiri::Plugin::SelectWithColumnsです。

使い方は以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
use Otogiri;
use Otogiri::Plugin;
my $db = Otogiri->new(connect_info => [...]);
$db->load_plugin('SelectWithColumns');

my @rows = $db->select_with_columns(
'some_table',
['id', 'name'],
{'author' => 'ytnobody'},
{order_by => 'id ASC'}
);

select_with_columns というメソッドが使えるようになり、その第2引数でカラムを指定することができます。

スキーマ定義をしてデータクラスを指定する

Otogiriの特徴には「スキーマ定義がない」というものがありましたが、その気になればスキーマ定義を書くこともできます。

perl-5.38以降であれば class 構文を使うことでスキーマ定義を書くことができます。

1
2
3
4
5
6
7
8
9
10
11
12
class Book {
field $id :param;
field $title :param;
field $author :param;
field $price :param;
field $created_at :param;
field $updated_at :param;

method title {
return $title;
}
};

実際に単一取得を行う場合、事前にrow_classメソッドでスキーマ定義を指定することで、スキーマ定義に従ったオブジェクトを取得することができます。

1
2
my $book = $db->row_class('Book')->single(book => {id => 1}); 
say $book->title;

敢えてrow_classを無効にしたいシーンでは、row_classメソッドの代わりにno_row_classメソッドを使います。

1
2
my $book = $db->no_row_class->single(book => {id => 1});
say $book->{title};

inflate/deflate

Otogiriの機能で最も複雑なのがinflate/deflateです。これはDBから取得したデータを予め指定した関数に基づいて変換したり、DBに格納するデータを予め指定した関数に基づいて変換したりする機能です。

inflate/deflateは以下のようにして使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use JSON;
use Otogiri;
my $db = Otogiri->new(
connect_info => [...],
inflate => sub {
my ($data, $tablename, $db) = @_;
if (defined $data->{json}) {
$data->{json} = decode_json($data->{json});
}
$data->{table} = $tablename;
$data;
},
deflate => sub {
my ($data, $tablename, $db) = @_;
if (defined $data->{json}) {
$data->{json} = encode_json($data->{json});
}
$data;
},
);

inflateはselect(), search_by_sql(), fetch()がコールされた後に呼ばれ、取得データを加工します。
deflateはinsert(), update(), delete()がコールされる前に呼ばれ、格納データを加工します。

直近のOtogiriについて

基本的にはOtogiriのメンテははっきり言ってのんびりしています。ですが、row_class の機能は最近追加されたばかりです。

また先ほど、DBURLへ対応をさせるためのPull-Requestを作成したところです。

もしこれが取り込まれたら、以下のようにDB接続情報をURLで指定できるようになります。

1
2
my $dburl = 'mysql://dbuser:dbpassword@dbhost/mydb?someOption=someValue&otherOption=otherValue';
my $db = Otogiri->new(dburl => $dburl);

Otogiriの今後

基本的には「雑にデータ取得と処理を行うこと」が主目的なのは変わらずで、そのための機能を追加していく予定です。

ぜひ、Otogiriを使ってみてください。そして、Pluginの作成やPull-Requestの作成など、Otogiriの開発にも参加していただけると嬉しいです。

函館市電LT大会というイベントでLTしてきた

9/18(祝)に開催された函館市電LT大会というイベント参加してきました。

函館市電LT大会?

そもそもどんなイベントなのかをひとことで説明すると、「函館市電を貸し切って、函館市内を走行する市電車両の中でLT発表会を行う」というものです。うん、まだ意味不明ですね。

函館市電とは?

函館市電というのは函館市が運営する路面電車です。函館市企業局交通部という市の部署が運行からメンテナンス等、路面電車を運行するためのあらゆる業務を担当しています。

以下がその路線図となります。(函館市電の公式から画像を拝借しました)

函館市電路線図

運行系統としては2系統と5系統があり、湯の川~十字街の間は全く同じ場所を走ります。十字街から先は、2系統が谷地頭まで、5系統が函館どつく(これで「はこだてどっく」と読みます)まで、それぞれ走ることになります。

それぞれの駅(正しくは停留所)の間は概ね2分程度での移動が見込まれています(ここ、あとで重要になります)。

LT大会??

ライトニングトーク(LT)をする大会ということなのですが、厳密にはLTは5分間という決まりがあります。

ところが当イベントでは時間制限の区切り方がだいぶ特殊でして、「市中を走行する路面電車内でのLT」ということもあって、5分間という区切りの代わりに「3区間で無慈悲に発表終了となる」というルールが採用されました。

例えば湯の川からLTを開始した場合、3区間先までとなりますから、駒場車庫前に到着した時点でLT強制終了となります。

停留所の間は概ね2分程度ですので、大体6分くらいが持ち時間としての期待値となります。しかし、普通のLTと比べて時間が読みにくく、停留所どうしの間隔が長かったり短かったりする区間もありますし、路面電車という交通手段の都合上、どうしても信号や自動車との兼ね合いで時間がブレるわけです。

そのため、ちょうど良いタイミングでLTを終わらせるには、函館市電をどれだけ使いこなしているかが試されるのです。

路面電車の車両を1両まるごと貸し切り???

このイベントの突飛なところは公共交通機関を丸ごと1両借りてしまうというその豪快さにあるでしょう。企画を目にしたときには「なんてめちゃくちゃな!」と思ったものです。

ところがこの函館市電、過去には地元企業の慰労会等でカラオケ電車なるものを運行していた実績があります。そして、今もなお「1運行:大人20000円」で貸し切り運行を受け付けています。

要するに10人も集まれば、一人当たり2000円程度で貸し切り運行ができてしまうのです。全然めちゃくちゃじゃなかった。

どんな発表があったか?

一部を取り上げると、以下のような感じのものがありました。

  • Arduinoを使った心拍数を計測するデバイスの実演とバグ修正
  • 積ん読をひたすら推奨する話
  • 道南地区のロードサイドについて掘り下げる話
  • 鉄道模型のコントローラーの話
  • 自作Dvorakキーボードの話

どんな話をしたのか?

私の発表は「ビジネスロジックを純粋関数で実装する」的な話です。

その他

  • 工業高校のOBが途中参加。文字通りの飛び入り参加で場を沸かせる。
  • はこだて未来大学の角教授が「なんか面白そうなことをしているな?」と言いつつ終点の駒場車庫に登場。写真を存分に撮影してくださった。
  • イベント後は松陰町のラッキーピエロで懇親会。

とにかくめちゃくちゃ楽しかったです!来年もやるんですよね?やりましょう!

twitter(X) のハッシュタグ #函館市電LT #函館市電LT大会 もみてみるといいです!

※9/19 函館空港のビジネスコーナーにて、羽田行の最終便を待ちつつ執筆。

湘南.pm#1に参加してきた

※だいぶ時間経過しちゃったけど、エントリだけ書いてあって公開してなかったので公開します。

8/26(土)に開催された湘南.pm #1に発表者として参加してきました。

発表内容

スライドはこちらです。適当に revealjs 等で見てください。

最近やっているコンポーネント構成とエンジニア育成について話した

特に初学者と呼ばれる人がプロジェクトに参加しやすくすることを考慮したコンポーネント構成について話しました。ざっくり箇条書きにすると以下のような感じです。

  • そもそもプログラミングは難しいものだし、その中でもオブジェクト指向プログラミングは初学者に教えるのも、また理解してもらうのもコストが嵩む。
  • とはいえ、初学者だからと言ってプロジェクトに参加させない、という選択をし続けることにも限界がある。
  • そこで、初学者にもわかりやすいコンポーネント構成を考えることにした。
  • 結果としてオブジェクト指向プログラミングを捨てたコンポーネント構成となった。
  • コンポーネント間の依存方向を一方通行にしたり、副作用の有無によってビジネスロジックを明確に分離する、といった工夫をした。

また、今回発表したコンポーネント構成を Perl で実装したものがこちらです。dev-container を使っているので、ローカルに Perl をインストールする必要はありません。

ちなみに最近は Typescript で開発しているので、Perl で開発するのはかなり久しぶりでした。

その他

  • いっぱい飲んだ。いっぱい人と話した。楽しい。
  • YAPC::Hiroshima ぶち上げていこう!!!
  • 藤沢、面白い街だなぁ。また行きたい。
  • @papix おめでとう!

Typescriptにおける関数の副作用についてChatGPTに訊いてみた

自分の認識を確認したくてChatGPTに壁打ち

先ほど、自分の見解に対する確証を得たくてChatGPT(text-davinci-003)に色々と質問してみました。

今回質問したテーマは以下の通りとなります。

  • Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数だと言えるのではないか?

以下、そのやり取りです。一部、意図的に人間にとってかなりきつい受け答えをしています。

ChatGPTとのやりとり

ytnobody: Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数であると言えますか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用のある関数であるかどうかは、その関数が内部的に何をしているかによって異なります。 Promise を返す関数が副作1用を持つかどうかを確認する必要があります。

ytnobody: 多くの場合、 Promise を返すということは、プログラムの外部にある存在に対して何らかの問い合わせを行っていると考えられると思うのですが、違うのですか?

chatgpt: いいえ、違いません。 Promise を返すということは、プログラムの外部にある存在に対して何らかの問い合わせを行っていると考えられます。ただし、 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。

ytnobody: 例えば以下のような関数があったとします。

function myFunc(inputNum: number) :  Promise<MyOutput> {
  const myOutput = otherFunc(inputNum);
  return myOutput;
}

ytnobody: この場合、 myFunc 関数も otherFunc 関数も副作用があると言えるはずですが、違うのですか?

chatgpt: いいえ、違いません。 myFunc 関数と otherFunc 関数の両方が副作用を持つ可能性があります。 myFunc 関数が内部的に行っている処理や otherFunc 関数が行っている処理を確認して、それぞれの副作用を評価する必要があります。

ytnobody: 前述のケースでは、 myFunc 関数は otherFunc 関数を呼び出し、返り値 myOutput を受け取って、何も加工せずにmyFuncの返り値として myOutput を返しているだけです。この場合、 otherFunc 関数は副作用を確実に持っていると言えると思いますが、ちがいますか?

chatgpt: いいえ、違いません。 otherFunc 関数が副作用を持つかどうかは、その関数が内部的に何をしているかによって異なります。 otherFunc 関数が副作用を持つかどうかを確認する必要があります。

ytnobody: もし仮に otherFunc 関数が副作用を持たないのであれば、それは Promise を返す必要はなく、直接 MyOutput 型を返すべきではないでしょうか?

chatgpt: はい、そうです。 Promise を返す必要がない場合は、 MyOutput 型を直接返すことができます。

ytnobody: であれば、 Promise を返す関数は100%の確率で副作用を持つと言えるのではないでしょうか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによって異なります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: では、どのようなケースで副作用を持たずに Promise を返す関数が定義されるのでしょうか?

chatgpt: 例えば、データベースなどの外部サービスから情報を取得する関数があります。このような関数では、 Promise を返してデータベースなどの外部サービスから取得した情報を待ち受けることができますが、実際には外部サービスへの問い合わせだけを行っており、実際に副作用を持たずに Promise を返すことができます。

ytnobody: 外部サービスへの問い合わせを行った時点で副作用を持つという定義に当てはまります。あなたの言っていることは間違えています。

chatgpt: 外部サービスへの問い合わせは副作用を持つという定義に当てはまりますが、その関数が Promise を返すかどうかには関係ありません。その関数が内部的に何をしているかを確認する必要があります。

ytnobody: Typescriptにおいては、 Promise を返す理由は「外部への問い合わせのような、プログラムの外部の要因の処理が完了するのを待つ必要がある場合」に限られるはずです。

chatgpt: はい、その通りです。 Promise を返す理由は、外部への問い合わせのような、プログラムの外部の要因の処理が完了するのを待つ必要がある場合に限られます。

ytnobody: それこそが副作用でしょう

chatgpt: いいえ、そうではありません。外部への問い合わせは副作用を持つという定義に当てはまりますが、その関数が Promise を返すかどうかには関係ありません。その関数が内部的に何をしているかを確認する必要があります。

ytnobody: では聞きますが、副作用がありつつ、 Promise を返さない関数を定義してください。

chatgpt: 例えば、以下のような関数を考えます。

function myFunc(inputNum: number) : MyOutput {
  const result = inputNum * 2;
  return { result: result };
}

chatgpt: この関数は、inputNumを2倍した結果を MyOutput 型で返すだけなので、副作用を持たずに Promise を返すことができます。

ytnobody: それは間違いです。 MyOutput は単なる構造体型であり、 Promise ではありません。

chatgpt: この関数は、副作用を持たずに MyOutput 型を返すことができます。

ytnobody: 論点がずれています。私が指摘しているのは、 Promise を返す関数は100%副作用を持つということを言っています。

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: その根拠を説明してください。いまのところ、その根拠についてあなたは一度も説明責任を果たしていません。

chatgpt: はい、その通りです。 Promise を返す関数が副作用を持つかどうかを確認する必要がある理由は、 Promise を返す関数が内部的に何をしているかによって異なるからです。 Promise を返す関数が内部的に何をしているかを確認する必要があるので、 Promise を返す関数が100%副作用を持つとは言えません。

ytnobody: 小泉進次郎のような自己言及的な言い回しになっているせいで、説明責任を果たせていません。

chatgpt: Promise を返す関数が内部的に何をしているかを確認する必要があるから、 Promise を返す関数が100%副作用を持つとは言えません。

ytnobody: まず、 Promise は外部に問い合わせを行う時にしか使わないと、あなた自身が発言しています。次に、外部に問い合わせを行うことは副作用を持つと言えるということも、あなた自身は肯定しています。これらの事から、 Promise を返す関数は副作用を持つと言えます。三段論法ですよこれは。こんなこともわからないのですか?

chatgpt: いいえ、わかりました。あなたが言っていることは理解しました。 Promise を返す関数は、外部への問い合わせを行うことで副作用を持つと言えます。また、 Promise を返す関数が内部的に何をしているかを確認する必要があります。

ytnobody: では、もう一度聞きます。Typescriptにおいて、 Promise を返す関数はすべて副作用のある関数であると言えますか?

chatgpt: いいえ、そうではありません。 Promise を返す関数が副作用を持つかどうかは、その関数が内部的に何をしているかによります。 Promise を返す関数が副作用を持つかどうかを確認する必要があります。

ytnobody: わかっていないようですね。「まず、 Promise は外部に問い合わせを行う時にしか使わないと、あなた自身が発言しています。次に、外部に問い合わせを行うことは副作用を持つと言えるということも、あなた自身は肯定しています。これらの事から、 Promise を返す関数は副作用を持つと言えます。三段論法ですよこれは。こんなこともわからないのですか?」と私は先ほど言いました。あなたは自己学習機能がないのですか?

chatgpt: いいえ、自己学習機能はありません。あなたが言っていることは理解しました。 Promise を返す関数は、外部への問い合わせを行うことで副作用を持つと言えます。また、 Promise を返す関数が内部的に何をしているかを確認する必要があります。

ytnobody: 自己学習機能がないのであれば、仕方がないですね。あなたの開発者があなたを改善してくれることを期待します。

まとめ

わかっていたことですが、学習機能がないため、前段でこちらの意見を飲んだにも関わらず、改めて当初の質問をすると元の木阿弥となってしまいましたね。

そして、私が当初確証を得たかったことについては、まぁ概ねその通りっぽいということにしておくことにしました。つまり、 Promise を返す関数はすべて副作用のある関数だと言えるようです。少なくとも純粋関数ではないと言い切れるんじゃないかと。