手続き型音楽の日常

関数型音楽に乗り換えたい

Gravという超絶おもしろCMSを見つけた話

本当は PHP から離れたかったんだけども。

CMS を使いたい

私ははてブロとは別に自分のホームページを持っています。

あっちにはエンジニア業のことはあまり載せないようにしているので、こっちでも逆に載せません。もしかしたらどこかで載せるかも。

今のページを作り始めたのは高校生の時で、あれからもう10年近く経ちました。あの頃は HTML も JS も CSS も全部手書きで、サーバーに乗せれば終わり、っていう昔のホームページの体をしてました。

今までは全然あれで良かったんですが、最近はコードを書くこと自体が億劫になったというか、データはデータで作りたくて、プログラムはプログラムで作りたいと思い始めたのです。まあ自然な分離なわけですが、仕組み無しにそんなことできず、手書きでコンテンツローダーのJSを書いたとしても、コンテンツは SSH で潜らないといけないから端末限られる……とか考えると、継続性にもかかわってくる。やっぱり、コンテンツはコンテンツ制作用の画面で書きたい。

ということで CMS を入れたいとなったわけです。

でも Wordpress は使いたくない

CMS で代表的なものといえば、やっぱり Wordpress 一強。何でもかんでも Wordpress を推してきます。標準インストール可能!みたいな。

でも待て。あの クソ重 機能ゴリゴリマッチョな環境私は要らないし、構築したあと別のサーバーに移そうと思ったら結構大変になるだろうし、そもそもデータベースのチューニングとかダルいし、第一脆弱性うんぬんがうるさい。個人サイトだから常時監視するわけでもないし、サーバー乗っ取られたらたまったもんじゃない。

そして様々なテーマがプラグインありき、カスタマイズありき、 PHPer 前提。私もこの間 PHP カンファレンス行ってきたし PHP やりたいとは思ってるんだけど、今は全然わからんからサクッと作りたいと考えたらあまり言語依存してほしくない。 Node.js とかで動いたほうがまだ知識あるからいい。

で、最近はヘッドレス CMS とか流行ってるし、どうなのかなーって思って調べてみたら、どうも私が思い描いていた像とはちょっと違って。デファクトプロトコルがあるわけではなくて、 CMS 側とジェネレーターが側の相性を見て、お互い橋渡しをセットアップしないといけないらしく。えー……

そして何故か知らんが PHP が多い!いや Web 系で PHP がほぼデファクトだから仕方ないことだけど、私には理解が及ばんのじゃ!!

……と明け暮れていて、最近面倒なことはだいたい Copilot に聞いちゃうので、おすすめのヘッドレス CMS のセットアップを聞いてみたら。

Grav + Hugo を使ってみたら?」

※後に Grav だけで運用し始めます

とりあえず入れてみることに

Copilot 曰く、 Grav は「記事を Markdown で管理する CMS」、 Hugo は「テーマを HTML / CSS で作成できるサイトジェネレーター」と言われ、今のサイトのテーマをそのまま移植するならこの組み合わせが楽だよ!って言ってきたんです。

その時点で、

「…… Markdown? 素の??」

とまあ不思議に思っていました。ただ、最初はバックエンドに DB を用意しようと思っていたんけど、データ移行の事を考えて SQLite 対応していると嬉しいなと思って探してたら、そんなヘッドレス CMS が全然なくて、 MySQL とかやるのを考えたら Markdown でもいっか、と思い始めました。

そこで、とりあえず Grav を入れてみることに。ドキュメントから Installation を開いて……

えっ、 git clone しろ???

なるほど、確かに PHP はコンテンツだから、サイトのディレクトリに直接 git clone で配置すれば動くのか……。なるほどね。そしたらリポジトリをクローンして、各種インスコして、最後に bin/grav install ……。

動いた!?

Installation が終わった段階で既にサイトが動き、初期コンテンツが見える状態に。環境設定が一切不要、コマンドポチポチしただけでとりあえずサイトが動く状態になってしまって、えっめっちゃええやん……。となり。

これヘッドレス CMS ちゃうやんけ!?!?

サイトが動いたことに感動し忘れそうになりましたが、そもそも私はヘッドレス CMS とサイトジェネレーターを入れようとしていたのです。

……そう、何を隠そうこいつはヘッドレスならぬ ヘディッドCMS で、単体でコンテンツの管理も配信もできちゃうんですよ。フル機能の CMS 。そもそもコンテンツジェネレーターを使う必要がない。

Copilot 曰く、 Grav の記事管理機能だけを使って、プラグインで Hugo が対応してる REST API を発行できるようにセットアップして、 CLI から叩けばいいよ!って。そうじゃねえ。

ということで Hugo は使わず、 Grav だけ使うことに。

そして真っ先に入れるべきプラグインと紹介されていたのが Admin Panel 。公式が出してるプラグインで、コレを使えば Wordpress の管理画面みたいなページが一発で出来上がる。ここから記事の Markdown が書けたり、プラグインのインストールやアップデートができたり、テーマを入れ替えたりできる。

インストールもコマンドラインbin/gpm install admin と叩くだけで完了。あとは管理用 URL にアクセスしてユーザーを作ってログインすればOK。

なんだこれ、こんな手軽にピュッと作れる時代だったのか。

軽量で簡単すぎる

マジで動作が快適すぎてびっくりした。ほぼ専用サーバーで他の利用者がいないから、動作が軽快なのはそうだと思うんだけど、それにしてもコンテンツが爆速で返ってくる。

文字情報だけでテストしてるからかもしれないけど、それにしても速い。多分 DB が絡んでない分オーバーヘッドが著しく少なくて、かつ Markdown をパーサーにかければ一瞬で HTML が出来上がるからそれを直接配信してて、総じて閲覧リクエスト時に処理してる内容が少ないんじゃないかな。

そして管理画面の動作もめっちゃ軽くて、サクサク書いていける。ページごとの設定も結構充実してるし、最初から多言語対応前提の構造をしてて、個人のブログからツールのドキュメントみたいなものまで、いろんな用途に使えそう。

設定ファイルは全部 YAML で管理されているから、なんかバグって動かなくなってもプログラムを気にしながら治す必要もないし、すごく気が楽。 PHP のコード見て治すより遥かに簡単なんじゃないかな。

だけど難しいところもある

Wordpress と違って素の機能が少ないから爆速だし、必要なものをプラグインで入れていけば全然問題ないんだけど、やっぱりデメリットはあるようで。

たとえば、記事が Markdown なだけにリッチな表現をしようと思うとゴリゴリに HTML を Markdown に埋め込まないといけない。コレがちょっと難点で、結局コード書くのかー……みたいな気持ちになる。まあやろうとしてることがブログ記事とかに普段使わないような段組みだからそうなるんだけど。

そしてテーマも Markdown で記事が書かれる前提なので、文中でそこまでリッチな表現はできない感じ。デフォルトテーマがそうなだけかもしれんけど、ページに HTML のクラスとか付けて区別をするみたいで、管理画面にドロップダウンとか出てこないから調べながらやらないといけないっぽい。そこは面倒だね。

あと、管理画面からテーマの変更はできるけど編集はできなくて、 GUI で編集したいなら専用のプラグインが必須。だけどそのプラグインを入れると static-generator と相性が悪くて、管理画面がエラーで動かなくなっちゃう。どうしたもんかね。

人は選ぶかもしれない

色々まだ試している段階ではあるけど、最悪自分でプラグイン作るとかはできると思うから、どうにもならなかったらそれで切り抜けよう。

スタートまではすごく簡単だけど、その後の面倒見が大変なのと、細かいことしようとするとエンジニア力が結構必要になってくるので、まあそこは クソ重 機能ゴリゴリマッチョな CMSトレードオフかな。

なんかこういう感じなの、普段業務で触ってる kintone みたいで、面白いんだよな。

Chromebook を買ったら進化に驚いた件

長らく記事を書いていなくて、もう二度と書くことはないのかなぁとか思っていたら突如として話題が出来上がったので、勢いに任せて書いてみることに。

Chromebook を買ってしまった

衝動買い。多分日頃のストレス。

今月は旅行も控えてるし Nintendo Switch 2 も買わなきゃと思っていたので、節約しようと心に決めていたのに。。。

気づいたらポチってしまっていた。

買った端末はこちら:

Lenovo の11インチの 2-in-1 タイプの Chromebook

もともと ASUS の CM3 ってやつがめっちゃ見た目好みだったんですが、メモリが 4GB しかなかったので見送っていたら後継機種 CM30 が出てきて、これいいじゃんって思ったら価格が 5万円強。最近では値上がりして 54,800円 になっていました。

で、ちょっと躊躇っていたら、ほぼ同じ画面サイズで同じ 2-in-1 の、さらに SoC がちょっと上位モデルっぽい Lenovo 製の製品がそれよりも安い (タイムセール) 価格で出ているではありませんか!

ちょうど iPad (6th) がそろそろ限界だなぁと思っていたし、旅行先に会社からの貸与 PC を持っていきたくないし、買うなら今しかないでしょ!!

とポチってしまったわけですね。あぁ南無。

ChromeOS と私

ChromeOS というか ChromiumOS は昔 x86 機用ビルドを USB に焼いた事があって、メモリ 2GB の Win10 端末にぶっ刺して遊んでたことがありました。

当時はまだ Chrome アプリなる代物があって、ただの Web サイトに繋がる今で言うところの PWA みたいなものと、ページそのものが拡張機能と同様にインストールされてクライアントアプリのように使えるものが混在していました。 IE もまだ死んでいなかった時代なので、その頃で言うと HTAChrome 版みたいな感じでしたね (?)

で、結局それらが動かせれば良いだけなので、本当に Chrome (Chromium) が軽量 OS 上で動くだけというものでした。それ以上の機能は全くと言っていいほど搭載されていない、正真正銘 ChromeOS (ChromiumOS) 。

つまりまあ、 OSを USB に焼いて遊んでいたくらいですから、当時の私には機能不足だったんですよ。コンソールは叩けないし、オフィススイート (Google Docs) を使うにももっさりするし。更に USB が邪魔だからと思って、今のマウスの無線レシーバーくらいの大きさの 32GB メモリに入れていたので、すぐ熱くなって速度が下がる。

当然すぐに飽きました。まだ Debian を突っ込んだほうがカスタマイズ幅が多くて楽しかった。

というわけで、いつしか x86 ビルドをしていた人の更新が止まり、私もそれに伴って離れていきました。

それから、そうですね、かれこれ 10 年近く経ちました。 Win10 はサポートが終わりを迎えようとしています。 誰だよ最後の Windows になるとか言ったやつ。

職に就き日々の時間が限られてきて、そもそもパソコンを弄って遊ぶことがほぼ無くなりました。酒を飲みながら Splatoon 3 をプレイして一喜一憂する日々。試合の合間に YouTube の新着動画を漁る日々。

たまに D4DJ を iPad (6th) でやって、最近は最低画質じゃないとまともに動かなくなって。

旅行に出かけるときはほとんど必要もないのに会社からの貸与 PC を持ち歩いて常に紛失と破損のリスクを孕んで……。

ん、もしかして今の私が求める端末って Windows とかの高機能 OS じゃなくても、そこそこ軽快に動いてリモート操作ができるなら、なんでもいいのでは??

実際に使ってわかったデメリット

というわけで無事散財したわけですが、先にデメリットから書き連ねていきます。

当然だけどカスタマイズ性が皆無

もはや Android スマホよりカスタマイズ性が悪いです。ランチャーやシェルが固定なので、好きなホーム画面を使うことはできません。変えられるのは壁紙とテーマカラーくらい。

Windows でさえ、ウィンドウの縁やタイトルバーに色を付けられるとか、透明度つけるかとかカスタマイズできるのに、いくら軽量 OS でもそのくらいは欲しいよ。

好きなブラウザが使えない

デフォルトブラウザはもちろん Google Chrome 一択、それ以外はありません。

私は最近 Vivaldi 派なので、カスタマイズでデフォルトブラウザや WebView みたいなものをすげ替えてやろうかと思ってましたが、私の技術力では無理でした。誰かやり方教えてくれ。

現状では基本的なブラウジングAndroid 版の Vivaldi を使いつつ、 PWA は Google Chrome で動かしています。あまり垣根を作ってない。

好きな AI をアシスタントにできない

私はオフィススイートに関してはもっぱら Microsoft 信者なので、それに伴って AI は基本 Copilot を使っています。なのでデフォルトのアシスタントを Copilot にしようと思ったのですが、無理でした。

アシスタントはショートカットキーで起動できるんですが、どうやら Gemini しか起動できないショートカットのようで、他のアプリに差し替えることはできなさそうでした。

特定アプリの起動にショートカットを割り当てることができれば代用とかできそうでしたが、そういう機能もなく、ちょっと残念ですね……。

タッチの即応性が乏しい

D4DJ をやるときに特に困ってるんですが、画面をタッチしてから反応するまで若干ラグがあります。 iPad のように素早くない。

ゲーム上の設定では +20.0 で安定でしたが、これが何の数字かよくわからないんですよね。 200ms なのか、それ以下なのか。少なくとも音ズレの仕方からして 20ms どころではないはず。

これは端末の個体差かなぁ。

Phone Hub でアプリ画面転送ができない

最近の ChromeOS は Android スマホと連携することができて、通知が ChromeOS 側で見れたり、最近撮った写真が少しだけ見れたりします。

そして対応スマホであれば、スマホ側のアプリ画面を転送してくれる (ミラーリングじゃなくて拡張画面っぽい扱いなのかな?) らしいのですが、対応機種が少なすぎる。

私の機種はそもそも対応してないのですが、何故か機能は動いて、真っ黒な画面が表示される。で真っ黒な画面が出ている間はスマホがくっっっっそ重くなってフレームレートが 20 くらいになる。通知を開いただけで自動で動くのやめてくれ。

ところで開発者オプションから second display simulation を ON にするとうまく画面転送できるのなんなん?

キー配置が曲者

キーボード自体は、押し込み感触は良いしピッチもそこそこあって、結構クオリティは高いんですが。

ChromeOS ならではの曲者配置すぎてちょっと……。

まず Delete キーがなくて、本来あるべき場所にはロックボタンが設置されています。 0.6 秒くらい長押ししないとロックされないので押し間違えても大惨事にはならないのですが、なぜこのような配置にしたのかわからない。

そしてファンクションキーは最近の流行りでショートカットキーになっていて、切り替えることでファンクションキーとして使えるようになっているのですが、何をトチ狂ったのか F10 相当のボタンまでしか存在しない……。どこへ行った F12 キーよ。

通常のキー機能と入れ替えるためには、ランチャーキー (普通のキーボードで CapsLock がある場所にある◉キー) と同時押しをする必要があります。 Home とか PageUp とかも同じ。これが地味に押しづらい。慣れれば問題ないのかもしれないけど。

YouTube アプリの Android 版が未対応

なぜかわからないけど Android 版の YouTube アプリが ChromeOS では使えません。 YouTube Music は使えるのに……。

そうなると PWA 版の YouTube を使うことになるのですが、案の定、パソコンで見る YouTube と同じものをマウス+タッチで扱うので、細かい部分で操作性が悪い。大体のことはできるけど、やっぱりタッチだと Web 画面との相性が悪いので、アプリ版を入れさせてほしい。

Linux 版ソフトが重すぎる

一応、開発者ツールとして Linux コンソールを入れることができて、仮想環境の Debian を動かすことができます。

素晴らしいことに GUI が標準で動く (X サーバーや Wayland が最初から ChromeOS のデスクトップに向いてる) のですが、これがあまりにも動作が重い。 Linux 版の Vivaldi を入れて YouTube を見てみたんですが、フルスクリーンで FHD の動画を流すとコマ落ちします。流石に常用は無謀でした。

いくら仮想環境とはいえカーネルは共通だしもうちょっと素早く動いてくれよ……と思ってしまいますね。

それでも買ってよかった理由

ここまで色々文句を書き連ねてきましたが、ここからはメリットを書いていきます。良いところもたくさんあります。

めっちゃ軽い

とにかく軽い。ほぼタブレットなので、本体だけでは 0.51kg (公称) 。マジで軽い。

一応、キーボードと背面スタンドとタッチペンが標準装備なので、それを含めると 1.1kg になるらしい。これだけ聞くとちょっと重そうに感じてしまう。

だけど、画面サイズが 11 インチ程度なので、手で持ち上げたときに普通のノート PC と比べて重心が手に近くなります。そうすると、テコの原理でそこらのノート PC を持ったときより体感で軽く感じます。

もちろんカバンに入れたら一緒なんだろうけど、こう、取り回しはすごくよく感じます。

さわり心地がいい

ケースと言うか、キーボードと背面スタンドの外側の素材が地味に心地いい。少しだけ柔らかく、マットな仕上げで手触りサラサラ、角が丸みを帯びてて、なんかこうマシーン感がない。

そしてキーボードのキー側もマットな仕上げで、すごくサラサラ。キーもツルツルじゃなくて指にいい感じに引っかかるザラザラ感。

本体もマットな金属仕上げだし、縁も角張っていて手に引っかかりやすく、落としにくい。

これは本当にクオリティが高いと思います。

画面もきれい

1920 x 1200 の画面がめっちゃ綺麗。そこそこ輝度もあって見やすい。

今まで使っていた iPad には紙の質感に似た保護フィルムを貼っていましたが (Apple Pencil も買ったし) 、今回は画面がとても綺麗で発色も良いので、ガラスフィルムにしました。画面の綺麗さ重視。

タッチペンはちょっと滑りすぎて、ぎこちないけどね。まあトレードオフとしては納得しています。

メモリ8GBが強強

Amazon 限定で、通常はメモリ 4GB のところ 8GB 載っています。

家電量販店でいろいろな Chromebook を触って試したところ、 4GB の機種だとシェル含めすべてがもっさりしていて、流石に実用に耐えられないと感じていたので、最低 8GB は欲しいと思っていました。

案の定、メモリは 8GB あって正解でした。かなり快適に使えています。多分 16GB あっても使い切れないと思うので、この辺がコスパ最強だと思います。

キーボードが強い

ただの Android タブレットであればキーボードはいらないんだけど、 ChromeOS はあくまで PC なので、やっぱりキーボードがあって正解。

タッチ操作だとどうしても細かいポインティングができないので、デスクトップ向けのサイトとかはマウスやキーボード操作ができると便利な時があるんですよね。あとちゃんと机に向かって作業するというときは、キーボードがあるほうが文字も打ちやすいし楽。

逆にタッチ前提で作られたアプリ (特に Android アプリ) を無理やりマウスとキーボードで操作するのは、疲れるんですよね。ボタンがクソでかくて画面占領するし、右クリックという概念がないからメニュー操作も難しいし。そういうときは思ったところを直接タッチするほうが楽だったりする。

その点、デスクトップ前提だけどタッチ操作がスムーズに行えるっていう ChromeOS や Chromebookバイスは、凄くバランスが取れた良い製品だと思います。

ちなみに押し心地は最高です。私こういう玩具みたいなのにしっかり打鍵感のあるキーボード、意外と好きなんですよね。デスクトップはメカニカル一択ですが。

そこそこのゲームは動く

さっきからちょくちょく話題に出てきていましたが、私は D4DJ Groovy mix というゲームをやっていて、もともと iPad (6th) でプレイをしていました。

で、性能的に追いつかなくなってきたので買い替え先を検討していて、このデバイスを手に入れたので、 Android 版を入れて遊んでみました。

結果として、デメリットで挙げたように入力遅延はあるものの、そこそこちゃんと動いてくれています。音ゲー中の画質は中品質で背景はキャラ絵固定にしていますが、メニュー画面は最高画質で Live2D がヌルヌル動きます。多分 3D が結構重たいんじゃないかな、 2D は全然問題なし。これからは Chromebook で D4DJ を楽しむことにします。

他の重めのゲームも試してみようと思って、遊戯王 Master Duel も試してみましたが、推奨画質であればヌルヌル動きました。最高画質にすると、流石に常時カクつく感じで、ちょっと不快かも。多少解像度は落ちるけど推奨画質を推奨します。言うてそんなに解像度低くならないので、十分かなと思います。

iPhone 的な使い方ができる

私は個人携帯は Android を使っているけど、会社から貸与されているスマホiPhone SE です。個人的にはこれ、すごく理にかなっていると思います。

iPhone というか iOS って、カスタマイズ性がほぼないので、何をやるにしても他の人と同じ手順を踏むことになります。これってつまり、何かをしたいときは先人の知恵を借りることができるということ。よほど変なことをしない限り、困ることはないと思います。

そして基本的には変な挙動をしないので、目立った速度低下や不具合を踏むことが少ない。拡張性が乏しい = 安定している、ということ。

これは、仕事をするという面に置いては重宝する仕様だと思っています。ちょっとした出先で何かをしたいときとか、差し迫って何かをしないといけないというとき、いつでもフルパワーを出せるのは良いこと。

Web サイトのアプリ化 (PWA) が地味に便利

今まで Web アプリってブラウザで使う前提だと思っていて、最初は「別にお気に入りで良くない…?」と思っていたんですが、 ChromeOS が Web アプリ前提で設計されているので、嫌でも Web アプリがランチャーに増えていくんですよね。それでまあ、仕方なくよく使うサイトをアプリ化して使ってみたんですが。

試した結果、意外とコレが便利ということに気づきました。

一番のメリットは、本体の容量を食わないという点。 Android アプリもそうですが、普通はアプリが増えれば増えるほどストレージを圧迫し、更にキャッシュや個人データも増えてきてストレージが地獄と化してきます。そんな中、 Web アプリはページデータがキャッシュでしかないので、必要なときに必要なものだけ取得し、必要なくなれば解放することができます。また Cookie などはあれどブラウザ機能の範疇でしかデータを保存できないので、無闇矢鱈なデータが保存されることがありません。故に本体容量が少なくても大丈夫。

次に適切なウィンドウサイズで立ち上がってくる点。前回のウィンドウサイズを記録してくれるので、例えば Copilot は縦長ウィンドウ、 Outlook は最大化、みたいな感じで便利なサイズで立ち上がってきます。ブラウザで使うときには実現できなかった利便性ですね。

最後にアドレスバーが消える点。アドレスバーが欲しいときって、検索するときと、色んなサイトを見ていて変なサイトに飛んでないか確認するときくらいなので、信頼したサイトであればなくても大丈夫。まあ小さくてもあったほうがいいけどね。

今までは Web サイトをアプリ化して何の意味があるんだと思っていましたが、インターネット上のデータからアプリを作成するという新しい感覚に慣れれば、たしかに悪くはないかもしれないなと思った次第でした。

リモートデスクトップさえできれば、多分事足りる

正直言って、このデバイスで物足りないことって、基本的には Windows PC とか Mac PC に面と向かってやらなきゃいけない作業だと思うんですよ。普段このデバイスでやる必要がないというか、たった 11 インチの画面でやれることではない。開発とか、高度な文書編集とか、厳密な画像編集とか。

どうしてもやらなきゃいけないときは、リモートデスクトップでメイン環境に繋いで、サクッと作業すれば良いんですよね。

幸い、 Chrome リモートデスクトップとかいう、この用途に超もってこいな機能がありまして。ホスト側にはデーモンを仕込む必要がありますが、リモート側は Google アカウントされあればブラウザのみで完結するという素晴らしい利便性。 ChromeOS と相性が良いのなんの。

今回、旅行中に少しだけリモートデスクトップを使って作業をしましたが、本当に十分でした。出先で軽く対処するにはこれで十分です。

もちろんネットワーク構成をちゃんと考えるとか、外出前にセットアップを済ませておくとか、準備は色々必要だと思いますが。多分、この記事を読んでいる皆さんであればそのくらい朝飯前なんじゃないでしょうか。

アカウント切り替えがスムーズ

ChromeOS の特徴として、ユーザーは Google アカウントに紐づきます。ただ、 PIN とパスワードだけは端末×ユーザーとなるので、 Google アカウントとは別で指定する必要があります。

で、何がすごいのかって、ログイン/ログアウト処理が結構速いんですよ。体感 15〜20 秒くらい。セッションを保持したままユーザー切り替えはできないので、必ずログアウトする必要はありますが、それにしても素早い。

パスワードは短めに設定できるのと、 PIN は数字 6 桁なので、入力はかなりスムーズです。さらに Windows と違ってプロファイルの読み込みが速くて、体感 5 秒くらいでログインできます。

コレの何がいいかって、普通は WindowsMacOS って Chrome 自体に複数プロファイルを作成して個人用を仕事用で分けると思うんですが、 ChromeOS に限っては OS レベルでユーザーを個人用/仕事用に切り分けられて、それらの切り替えをめっちゃスムーズに行えるんですよ。

つまり、デスクトップやアプリの構成を完全に切り替えられるので、無駄な要素が目に入らないし、完全にモードに入れる。ぱっとやりたい仕事だけ済ませて、個人用途に戻ってこれる。コレがかなり便利。

Windows にもこのくらいの俊敏さがあれば良かったんだけどなぁ。まあ Win32 API をサポートし続ける限り (ユーザーが求め続ける限り) 無理だろうね。

総評

……とダラダラ書き連ねていてたら 8000 文字を超えていました。久々に書きたいことを延々と書いたって感じ。個人的には満足です。

お気づきかもしれませんが、この記事も今回購入した Chromebook で全文書いています。特段めちゃくちゃ困ったことはなく、快適に書けています。こういうサクッと文書を起こすとか、ちょっとスマホじゃやりづらいことを実現するにはもってこいのデバイスだと思います。

総合して、結構いい買い物をしたなと思っています。反省はしていますが後悔はしていません。

もし手軽なサブ機をお求めの方がいれば、何かの参考になれば幸いです。

それでは。

C#/VB.NET の XML コメントに `completionlist` という謎のタグがある件

ネット上の情報が少なすぎて何なのか解明するのに非常に時間がかかったので。

忘れないために。

<completionlist cref=""/>

標準化されていないけど正しくコンパイルされるXMLタグ。

Microsoft Docs にも情報が載っていないので、何のための機能か一切わからない。

日本語の情報はここしか見つけられなかった。

yan note: .NET 定数ファイルを作成する ~その4~ インテリセンスに候補を表示する

.NET 文字列型の列挙体 - ウマヤディア

ただ、reddit には英語でこんなスレッドが立っていた。

www.reddit.com

英語が読めないので翻訳して読んだけど、なるほどなと思った。確かにこれは知恵袋にでも訊いてみたい機能だ。

解説

要は、あるクラスの変数に何かを代入しようとしたとき、 Intellisense に表示されるべき候補をクラスの静的メンバとして定義することができる機能のよう。

例えば自作クラスを作ったとして、

/// <summary> なんかのクラス </summary>
public class Test
{
    public string Value { get; set; }
}

このクラスの変数を宣言する。

public class Program
{
    public static void Main(string[] arg)
    {
        Test x;
    }
}

ここで、 x = と入力しても本来は何も出ない。 x = new まで入力すると、new Test() が補完されるけど、単純な代入ではコンパイラは何を代入すべきか理解できない。

そこで <completionlist cref="Test"/> コメントをつけて、静的メンバを定義する。

/// <summary> なんかのクラス </summary>
/// <completionlist cref="Test"/>
public class Test
{
    public static Test A => new Test('A');
    public static Test B => new Test('B');
    public static Test C => new Test('C');

    public string Value { get; set; }
}

そうすると、先ほどの x = まで入力した段階で、

  • Test.A
  • Test.B
  • Test.C

が候補として登場する。

実装されてる例としては、例えば System.Drawing.Color 。確かに lblNavi.BackgroundColor = とか入れると出てくるよね、色。

所感

これはかなり便利な機能だけど、ドキュメントを定義するはずの XML コメントとしては場違いな気がする。

なぜ標準化されていないか理由は定かでないけど、もしかしたら単純に Visual Studio しか対応してないからなのかもしれない。

ただ、属性として定義すれば IDE 関係なく使えそうな気がするんだよね。例えば System.ComponentModel.BrowsableAttribute とか完全に IDE 向け属性じゃん?

docs.microsoft.com

例えばクラス側に CompletionAttribute 属性をつけたら、宣言された変数への代入に全部そのリストが使われるとか。

例えば変数やフィールド側に CustomCompletionAttribute 属性をつけたら、その変数やフィールドだけ特別な補完リストが出てくるとか。

うーん。今までのコンパイラが対応しないとなると、今更導入しても使われなさそうだなぁ…。

C# で ウェーブレット木 を実装したライブラリをアップデートした

かなり前の記事で書いたのですが、ウェーブレット木を C# で実装したライブラリを作りました。

今回、 Visual Studio 2019 Preview が出たので、それを機にアップデートしてみました。

github.com

アップデート内容

名前空間の変更、クラスの分割

名前空間はもともと単発の予定でつけたので、めっちゃ適当でした。今回は改めて名前を付けなおしました。

あと、最近よくわかってませんが、インターフェースだけ外出しして実装系を別ライブラリにするっていう方法を考えていまして。

今回その方法を試してみました。 IWaveletTree<T> だけを定義するプロジェクトと、それを参照して実装する WaveletTree<T> だけのプロジェクトみたいな。

どちらも .NET Standard 2.0 に準拠しているので、だいたいの実装系で使えます。

使う際は、 WabeletTree<T> を nuget で落としてくる時に依存解決で一緒に。。。と言って感じで想像しています。まだ nuget パッケージ作ってないけど。

WaveletTree.Select<T> メソッドの実装

今回実装にあたり、以下のサイトを参考にしました。

www.slideshare.net

こちらの方のスライド、めちゃくちゃわかりやすいのですが、この中に出てくる「完備辞書」と呼ばれるものを目指してみました。

実装するには Select 関数を実装しなければならないらしいので、書いてみました。

        /// <summary>
        /// 指定された値が最初に出現する位置を取得します
        /// </summary>
        /// <param name="value">位置を取得する値</param>
        /// <returns>位置</returns>
        public int Select(T value) => this.Select(value, 0);

        /// <summary>
        /// 指定された値がn回目に出現する位置を取得します
        /// </summary>
        /// <param name="value">位置を取得する値</param>
        /// <param name="index">n</param>
        /// <returns>位置</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>index</c> is out of range of tree values.</exception>
        public int Select(T value, int index) => _ids.ContainsKey(value) ? this._SelectInner(value, index) : throw new ArgumentOutOfRangeException();

まずは公開メソッド。n回目という部分を実装するため、引数は2つ必要でした。というわけで、引数1つの時は強制的に最初に出てくる位置を取得するようにしています。

また、そもそも文字が含まれていない可能性があるので、その場合は例外をスローしています。

        /// <summary>
        /// 指定された値がn番目に出現する位置を取得します
        /// </summary>
        /// <param name="value">位置を取得する値</param>
        /// <param name="index">n</param>
        /// <returns>位置</returns>
        private int _SelectInner(T value, int index) =>
            this._depth == 0 ?
                (0 <= index && index <= this.Pairs.Count()) ? index : throw new ArgumentOutOfRangeException() :
            this._CheckTop(value) ?
                this._rightPairs?[this.Right?.Select(value, index) ?? 0].Index ?? 0 :
                this._leftPairs?[this.Left?.Select(value, index) ?? 0].Index ?? 0;

        /// <summary>
        /// 最上位ビットを見て0か1を判定します
        /// </summary>
        /// <param name="value">検査する値</param>
        /// <returns><c>true</c>: 1, <c>false</c>: 0</returns>
        private bool _CheckTop(T value) => (this._ids[value] & (1 << (this._depth - 1))) != 0;

そして内部の実装。結構考え込んで、このような形にしました。

何をしているかというと、実は「n番目に出てくる要素Xのインデックスを取得しろ」という情報を「Xを持っている葉」まで伝達しています。

すると、「Xを持っている葉」にとって「n番目に出てくる要素Xのインデックス」は「n」なので、そのまま受け取った「n」を返します。

その親は X がどちらの節にあるかを判別し、「その節のn番目に出てくる要素は、自分自身ではy番目」という逆引きを行い、それを返します。

というアルゴリズムを続けて、最後にオリジナルのシーケンスの中でのインデックスが変えるという寸法です。

めちゃくちゃ回りくどいです。もはや無駄です。ですが LINQ を使ったいい方法がこれしか思いつきませんでした…

安直に while 回したほうが早かったかもしれません。

感想

今回のアップデートは、破壊的というか根本的に名前空間を変えているのであれですが、極力クラスの内部は変えないように心がけました。

LINQ を使った実装を頑張ったので、ちょっと実用的でないコードがたくさんありますが、楽しかったから良し。

もしアドバイス等あれば、コメントお待ちしております。

kintone hive NAGOYA vol.3 に参加してきた話

ちょっとタイムリーな話題?

kintone ってなんぞ

kintone はサイボウズ株式会社が開発しているクラウド型データベース系業務アプリ構築サービスのこと。

詳しくは以下の Qiita がめっちゃわかりやすい。

kintoneとは、サイボウズ株式会社が開発しクラウドで提供するWebデータベース型業務アプリ構築サービス(作成したアプリケーションを連携したりシステム管理機能も活用できるよ) qiita.com

私の kintone レベル

実は業務で触ってたりします。

転職した関係でフロントエンドな Web アプリケーションを触ることが多くなりましたが、大概この kintone を使ったアプリを開発しています。

アプリを開発しているというか、 kintone のカスタマイズをしているというほうが正しいのか…。

kinotne CRETIFIED も Associate だけ取ってたりします。もう App Design と Customize も取っていい気がしますけど勉強する時間が…

cybozu.co.jp

というわけで、 kintone には結構ディープに関わっています。今まで黙っててごめんね (何

当日

当日はなかなかの盛況でした!

会場となった Zepp Nagoya の後ろのほうまで人がいる状態。なんか映画館みたいな感じでした。

もともとはライブ会場だけあって椅子は全部パイプだったり、音響はめっちゃばっちりだし後ろに DJ ブースみたいな部分もあったりして、こんなところでやるんだなぁって面白味を感じました。

登壇者1 : ちらし屋ドットコム 河田 菊夫​ 氏

岐阜を中心に展開している Web 製作会社。

これまで少人数だったため散在していた各資料を kintone にまとめることで、コストを可視化して意識を一つにしていったということ。

新しいプラットフォームに移行するため、帰りのドアに kintone の利用を懇求する紙を張ったり、会議中に kintone への登録をする時間を作ったり…。

自分がやりたい!だけでなく、みんなの協力があって kintone の活用に成功したという話でした。ぐうわかる。

システムだけではどうにもならない、みんなが使ってこそのシステムだってことが伝わってきました。

あと、ビール飲みながらサッカー観戦が好きらしく、暇そうなサイボウズ社員と一緒に観に行ってきたそう。

登壇者2 : ミッドランド税理士法人 小泉 直哉 氏

初っ端からインパクトの強い体験談をいただきました。

会社を立てたけど仕事が追いつかなくなり、アル中になり、太り、破産し、死んでしまうかもしれないという恐怖に怯えた日々から一転、筋トレをして痩せたり税理士法人の方に採用されたり。

人々を救うということが税理士法人になったきっかけだそうですが、今後この職業は AI に取って代わられて無くなってしまうと言われている。

いや、そうではない!無駄な業務をコンピュータ化して、人間は高度な判断が必要な業務に専念すれば良い!ということに行き着いたそう。

そこで kinotne を使って新たな業務改革 "LawyTech" を推進していこうということらしい。

必要な機能をシステムにして人間は人間の仕事をしていくという、着眼点にとても共感しました。いいなぁ、こういう世界。

登壇者3 : チヨダセキュリティサービス 吉本 大介 氏

名前からはなかなか想像できない、ガス会社を経営されている方。

先代である父が作り上げた会社がリーマンショックで打撃を受け、売却を考えていたところ取って代わって取締役に。

苦労を重ねながらもなんとか従業員数を増やし、九州にも支店を構えるようになったそう。

最近は新しい事業を始めるなど活躍する舞台を増やす中、自分は次第に経営者の道へ進もうと決意。

今までは目で見て実感していたことが、現場を離れることで見えなくなる。そこで、 kintone を使って情報共有を行うようになったそう。

システムを通じて企業が成長し世界が広がっていくということを発表されました。これからは、他の企業の立て直しも行っていきたいとのこと。

非常に愛のあるセッションでした。

登壇者4 : ジヤトコ株式会社 岩男 智明​​ ​氏

規模の大きな会社ですが、情報システム部の主任が秘めたる思いも同じだけ大きかった話。

もともと社内 SNS を作って暗黙知の共有を推進するなど、エンジニアとしてよりよい環境を作っていこうと活動されていたそう。

そんな中 kintone と出会い、本当にもとめていたシステムはこれだ!と感激。

現場にあったシステムを作るには、現場が主体となってシステムを作らなければならないという原点に立ち返り、 kintone を使って "現場が業務アプリを作る" という仕組みを作ったそうです。

また、それにあたって社内で統一的なルールを作ったりコミュニティで情報交換を行ったりし、社内で広く使ってもらおうと情熱を燃やしているそうです。

小さな会社にしか勤めたことがないので、全社展開とか正直最初はピンと来なかったのですが、1万人以上の組織で kintone という一つのシステムが使われるようになるとすれば、それはとても素晴らしいことだと思います。

一人の情熱がみんなの情熱を呼び覚ます、そんな社風と重ねたプレゼンが心に響きました。

kintone hack セッション

その後、開発者目線での kintone の活用例も披露されました。

1人は、Alexa を使って社内にコンビニを作り、声で購入できるシステムを作った方。

もう1人は、ノンコーディングでドローンを飛ばし、ノンコーディングで kintone に随時情報を書き込むという方。

kintone を使って派手なアクションを披露する、面白い場でした。でもやっぱドローン飛ばした、株式会社GEクリエイティブ​の伊藤さんがすごかったな……。

感想

kintone ってモバイル対応があまり進んでいないとか、トランザクションに弱いとかの理由から、大規模開発や汎用的なシステム基盤から避けられてきたように思います。

でも逆に、小規模から積み上げていく "成長する" システムなんかはめっちゃ作りやすかったり、外部連携機能やプラグインによって進化したり、様々な面で発展を続けていると思います。

今度のアップデートでは、モバイル版の画面がかなり充実するそう。期待が大きですね!

kintone.cybozu.co.jp

というわけで、 kintone hive NAGOYA vol.3 への参加レポでした。楽しかったです!皆様お疲れ様でした!

Node.js の exports と require() をなんとなく理解する

ブラウザで動く JavaScript を書いてて、初めて Node.js を触ると絶対にびっくりする、モジュール。

そもそもスコープが共有されていないことに驚愕するのですが、じゃあどうやったらモジュールとかが使えるのか、ライブラリはどうやったら使えるのか、というのをざっくりまとめてみた。

Node.js におけるグローバル空間

ブラウザだと、ファイルの一番大きな部分に変数を宣言したり、そもそも宣言せずに変数を使った場合、その変数は window オブジェクトのプロパティとして定義されます、

var test = 1; // ルートで宣言

function a() {
    test2 = 2; // var がついていないのでグローバル
    console.log(test2); // 2
    console.log(window.test2); // 2
}

function b() {
    test = 10; // 変数宣言の巻き上げによってローカル変数になる
    var test = 11;
    console.log(test); // 11
    console.log(window.test); // 1
}

a();
b();

console.log(test); // 1
console.log(test2); // 2

そして、 Node.js で同じことを行うと、 global オブジェクトのプロパティになります。要するに、 window の代わりとなるのが global オブジェクトです。

test = 1;
console.log(global.test); // 1

この global オブジェクトは、1ファイルごとにそれぞれ生成されます。つまり、ブラウザにおける window オブジェクトと違い、ファイルをまたいで変数が共有されることはありません。

例えば a.jsglobal.test = 1 を定義したとしても、 b.js では global.testundefined を返します。というかそもそもプロパティ自体がありません。

これによって、不用意なグローバル汚染をなくすことができます。

即時関数パターンを使わなくてもいい

これに伴って、実はブラウザ向け JavaScript プログラムでよくやる "ファイル全体を即時関数パターンで囲う" ということをしなくてよくなります。イメージ的には Node.js が勝手に囲ってくれている感じに近いです。

なので、ファイルの一番上で 'use strict' しようと、ファイルのルートでどれだけ変数を宣言しようと、ほかのファイルには一切影響しません。

変数の公開

さきほどから散々 "ファイル間で変数の共有ができない" と言っていますが、ではどうしたら複数のファイルで変数の共有ができるでしょうか?

その答えは、 Node.js の "モジュール" という概念です。 Node.js には、モジュールをインポートしたりエクスポートして、ファイル間で情報を共有する機能があります。

そのカギとなるのが、 require 関数と exports オブジェクトです。

require 関数

require 関数は、モジュールをインポートする関数です。つまり、モジュールを使う側が情報を得るために使います。

使い方は2つです。

var test = require('./test'); // 自ファイルと同じ階層にある test.js を読み込む
var test2 = require('test2'); // node_module ディレクトリ、もしくはビルトインモジュールの test2 を読み込む

これによって、モジュールの情報が変数に入ることになります。

この関数、実は 指定したファイルを一番上から実行する 機能があります。

ブラウザの JavaScript で言うなら、 script タグで埋め込んだスクリプトが順次動くとか、途中で Element.appendChild(scriptElement); みたいな感じで動かすのと似ています。

指定したファイルを実行し、それによって得た情報を戻り値として返す、それが require 関数です。

exports オブジェクト

フルで書くと global.module.exports になるのですが、 module.exports 、あるいは exports とも書けます。

この exports オブジェクトが、 require 関数で呼ばれた際の戻り値になります。

意味が分からないって?分かりました。 JavaScript で書きますね。 JavaScript なら皆さんも読めますね?

// test.js

exports.a = 1;


// index.js

var test = require('./test'); // { test: 1 }
console.log(test.a); // 1

test.jsexports オブジェクトにプロパティ a を生やしています。つまり、 test.js 上では、 exports{ test: 1 } となります。

index.js ではこれを require 関数で呼び出しています。戻り値は { test: 1 } になります。

そうです、 exports オブジェクトが、 require 関数の戻り値になります。

公開、非公開を意識する

名前空間などでしっかり管理されている場合は、もう何も言うことはありませんので、どうぞ読み飛ばしてください

通常、ブラウザで動く JavaScript を書いていると、関数の公開、非公開なんて意識しません。だって全部グローバルだから。

でも、モジュールを自作するとなると厄介になります。例えば…

// これを別ファイルから使えるようにしたい
function getName(id) {
    return get('/person/name', id);
}

// これは実は見られたくない
function get(url, id) {
    var xhr = new xmlHttpRequest();
    ...
}

get 関数は、単純に GET リクエストを送信する関数です。この関数はバージョンアップによって引数が増えたり、そもそも生で使ってほしくないので、公開したくありません。

でも、ブラウザで動く JavaScript は全部共有してしまうため、結果的に外から見られてしまいます。

そこで、次のようにスコープを分けてしまう、という手があります。

var getName;
(function () {
    getName = function (id) {
        return get('/person/name', id);
    };

    function get(url, id) { ... }
})();

getName('hoge'); // ok
get('/person/name', 'hoge'); // ReferenceError: get is not defined

こうすれば、外から見られたくない関数が呼び出されることはありません。

さてこれが Node.js の場合ですが、先ほど即時関数パターンが要らなくなると書きました。つまり、こんな感じです。

getName = function (id) {
    return get('/person/name', id);
};

function get(url, id) { ... }

でもこれだと、外部に関数を公開できません。公開する場合は、 exports オブジェクトのプロパティにする必要があります。

// public function
exports.getName = function (id) {
    return get('/person/name', id);
};

// private function
function get(url, id) { ... }

こうすることで、 Node.js でも公開する関数、非公開にする関数を分けることができました。

最後に

実は Node.js あんまり使ってません。

実際の業務ではブラウザで動く JavaScript を書いていますし、趣味で何かするときはもっぱら C# で書いてしまうので、クライアントもサーバサイドも事足りてしまうんですよね。

でも、 AWS Lamda とかだと Node.js が結構頻繁に使われますし、今の時代 Node.js を知らないと大変なこともたくさん。これだけは基本中の基本なのでしっかり押さえておかないと、後から苦しむことになると思います。

というわけで、今回はモジュールの基本概念についてまとめてみました。完全に自分用です。

次回、モジュールについて書くときは、 ES6 の Module との相互運用について、でしょうか…?

.net standard プロジェクトでnull許容構造体の循環参照が検出されない

先日、ウェーブレット木の記事を書くときに C# 書いていて、あれおかしいな?と感じたところを突き詰めた結果、こんなことが分かった次第です。

Visual Studio 2017 で現象を確認しましたが、 Visual Studio 2019 Preview 2 でも同じく確認したので。

現象詳細

構造体定義の中に、自分自身のnull許容構造体のフィールドを入れたとき (struct Test { Test? test; } みたいな感じ)、通常であればコンパイルエラーになるが、 .net standard プロジェクトでのみコンパイルエラーにならず実行時エラーが発生する。

再現手順

1. プロジェクトの作成

.net standard プロジェクトと、 .net core コンソールプロジェクトを作ります。そして .net core プロジェクトから .net standard プロジェクトを参照します。 .net core のほうは、 .net standard でも OK です。

.net standard プロジェクトは移植可能な DLL を作成するのみで、ビルドしても実行可能なバイナリは生成できません。そのため、別のプロジェクトから参照する形で実行します。

f:id:yuzutan_hnk:20190203155459p:plain
.net standard プロジェクトのプロパティ

2. コードの記述

検証用のコードは至ってシンプルです。 .net standard 側で構造体を定義し、 .net core 側でそれをインスタンス化します。

まずは .net standard プロジェクトの構造体定義。

namespace netstandard_structtest
{
    public struct TestData
    {
        TestData? test;
    }
}

そして、 .net core プロジェクトのエントリポイント。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }
}

これで準備は完了です。

3. 実行

現状で、エラーは無くビルドが通ります。なので、そのまま実行します。

実行時エラーで起動できません。

f:id:yuzutan_hnk:20190203160537p:plain
型が読み込めない

4. 別パターン

インスタンスを作らない

エントリポイント内でインスタンスを作らないことにしてみます。つまり、定義だけで利用しません。この場合は、実行できます。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            //var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }
}

f:id:yuzutan_hnk:20190203160807p:plain
動くのかい

null許容にしない

構造体の定義で null許容を外して完全に自分自身を参照します。結果は、たぶん定義通りの動きだと思います。

namespace netstandard_structtest
{
    public struct TestData
    {
        TestData test;
    }
}

f:id:yuzutan_hnk:20190203161352p:plain
わかりみが深い

.net core プロジェクト側で定義する

構造体の定義を .net core プロジェクトでやってみます。コンパイルエラーになります。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }

    struct TestData
    {
        TestData? test;
    }
}

f:id:yuzutan_hnk:20190203161624p:plain
コンパイルエラー

null許容じゃなくてクラスにする

null許容にしたいのであれば、クラスでラップすれば事足りるのではないか?という発想。

namespace netstandard_structtest
{
    public struct TestData
    {
        NullableTestData test;
    }

    public class NullableTestData
    {
        TestData value;
    }
}

f:id:yuzutan_hnk:20190203162039p:plain
まあ、動くよね。

所感

単純に考えて、自分自身を構造体で参照したらレイアウトが循環するのは正しいと思います。

いくら null許容と言っても、実際には Nullable<T> 構造体の中で自分自身の変数が定義されるので、結果的に循環参照になるはず。

.net core 側では事前にチェックされてコンパイルエラーになるのに、 .net standard ではならないということは、何か理由があるのか?それともコンパイラの単なる実装バグ?

一体、これはどこに報告すれば回答が得られるのでしょうか?詳しい方、ぜひ教えてください。

現在 0000/00/00 00:00 を生きています。