テストコードがコードの冗長化であることについて

テストコードがコードの冗長化であるという話に、腑に落ちないという指摘がちらほらあるので、どういうことかを解説してみます。
このエントリについてです。
テストというのは、ソースコードの冗長化だと思う - きしだのHatena

※ ここでの冗長化は、英語版Wikipediaでいう「Information redundancy, such as error detection and correction methods」のことですね。

まず、「これは冗長化だな」と納得してもらいやすそうな例として、間違いが絶対に許されないような計算をするシステムの実装を考えてみます。
このような場合に、同じ仕様を3つの組織に渡して独立に実装をしてもらい、計算が必要になったとき3つのモジュールを呼び出して多数決で結果を返すようにします。
このようにして正確さを担保するのは、冗長化と呼べると思います。

そして、これ多数決というけど、1つでも異なる結果を出してくるなら他の2つも怪しいのでは?その時点でエラーにするべきでは、そもそも3つも組織を確保できん、みたいな感じで、2つのモジュールを作って異なる結果がかえってきたらエラーにしよう、というふうになったとします。
これでも冗長化よね?

で、ちょっと実行時にやってるとパフォーマンスが悪いし同じ入力には同じ出力になるので、納品前に全入力について比較確認して、片方の実装だけ納品すればいいのでは、となったとします。
この時点でも冗長化ですね?

さて、もうひとつ同じように正確性が求められる計算を実装することになったけど、別実装つくるのは難しいし、確認用の実装はすでに動作検証済み実装がのった別のコンピュータでの値を使えばいいってなったとします。けど、そのコンピュータを直接使えないので、全入力に対する出力を用意しておいて、テーブルを作って比較するとします。テーブルをひく実装というのはよくあるので、これでも実装の冗長化といえますね。

そして、これそこまでは正確性もとめられなかったので、実際に使う範囲で確認とればいいんでは、とテーブルをすこし狭くします。
これでも実装の冗長化ですね。

こうやっていくと、じゃあどこまでどのように入力・出力値の対応をせばめていくと冗長化でなくなるかという閾値は恐らくみつけることができません。

で、実装の比較を納品前にやるのであれば、それはテストと呼ばれます。

単一の入力に対して単一の出力を定義するというのは、適用範囲の狭い最小の実装ということになります。
そしてこれが、ユニットテストで多く書かれているコードです。 効率的なテストというのは、冗長化をいかに最低限にして最大限の品質を担保できるかというものになるんじゃないでしょうか。

ところで、2つの実装を比較するテストというのも、アルゴリズムの最適化のときに行ったりしますね。最適化して処理が難しくバグの入り込みやすくなった実装に対して、自明でバグの入りにくい実装との処理を比較するというのは、それなりにあるんじゃないかと。

2023/1/30 追記 自動改札の例を教えてもらいました。改札機は時間の制約やメモリの制約からテーブル化をおこなっていて、ルール通りの計算を実装した検証版と比較を行うという事例。
https://www.publickey1.jp/blog/12/_1040_2.html

Javaのインストール2023年版

ちょっとJavaのインストールについて調べてみました2023年版。
Javaにはディストリビューションがたくさんあるので、目につくインストーラーをWindowsで全部ためしてみました。
初心者が勉強するためにJavaをインストールするというときにどれを使うのが手軽か確認するというのが主な目的です。

いろいろあるので、結論を先に書いておくと次のようになります。

いまPATHの設定が必要なJDKインストーラはない。Oracle以外ではJAVA_HOMEも設定できる。
一番楽なJavaインストール手順は、「Adoptium」で検索して最初のサイトを開いてダウンロードボタンを押して取得したインストーラを起動、JAVA_HOMEの設定を指定してインストール継続、という感じになります

2023年のJavaの勉強は「プロになるJava」で!

※検索ワードを示してますが、時期や検索エンジンによっては違う結果が出る可能性があります。
※ バージョン取得がきまぐれに-versionだったり--versionだったりしますが深い意味はありません。

Oracle JDK

まずはOracleJDK
いまはOracle以外にもJDKディストリビューションを出すようになったので、Oracle社のサイトからダウンロードできるJDKを「Oracle JDK」と呼ぶようになったというレトロニムで、Oracle自身がOracle JDKと呼んでるわけではないですね。
有償化が騒がれたりもしましたが、とりあえずデスクトップ用途では無償で使えることになって、騒ぎもおさまりました。
ただし、サーバーとして外部に公開して不特定が使うという場合には、個人でも有償ライセンスが必要なので注意してください。
サーバーとして外部公開する場合にはTemurinなどのディストリビューションを使うほうがいいです。ということで、学習の際も最初からTemurinなど別のディストリビューションを使うのが無難です。

JDK Download」で検索するとトップに出ます。「Java Download」だとJava 8 JREダウンロードサイトにたどりついてしまいます。

JRE」は元々はアプレットプラグインなどブラウザ連携を含んだものだったので、そういったブラウザ連携機能の廃止と同時に公式にはリリースされなくなっています。いまはJVM+APIだけのパッケージをJREとして配布するディストリビューションがあるという感じ。
自分のプログラムを配布するときは、jlinkコマンドで必要なモジュールだけをパッケージしたカスタムランタイムを一緒に配布したり、jpackageコマンドでそういうラインタイムを含んだ実行イメージやインストーラを作って配布という方針になっていますね。

Oracle JDKダウンロードサイトはこちら。
https://www.oracle.com/jp/java/technologies/downloads/

最新版と最新LTSが選択できます。OSやアーキテクチャは自分で選ぶ必要がありますね。

jdk-19_windows-x64_bin.msiをダウンロード。
今回はインストーラでやっていくのですが、msiとexeがある場合にはインストーラ専用形式であるmsiを選ぶほうがいいです。インストールとして許された操作以外を行わないことがある程度保証されるし、ダウンロードサイズも少し小さいので。

開くとインストールが始まります。特に設定が必要な項目はないのでNextを押していくと大丈夫です。

PATHは通ってるので、そのままjava -versionJavaのバージョンが出ます。JAVA_HOMEは設定されません。

Oracle JDKインストーラではPATHJava 15から設定されるようになっているので、いまだにPATHが必要と説明しているサイトは情報が古いです。

いまどれだけJAVA_HOMEの設定が必要なのか?っていう疑問はあるものの、インストーラJAVA_HOMEを設定できないのはOracle JDKだけなので、設定が必要なら自分で環境変数を設定するか、Oracle JDK以外のインストーラを使うかになりますね。

ちなみにjava -versionとやってjava versionが表示されるのはOracle JDKのみです。ほかはopenjdk versionなどJavaという用語を使わない表示になります。

あと、ほかのディストリビューションでも同じですが、macOSではArmかx64も正しく選ぶ必要があるので気をつけてください。Liberica JDKMicrosoft Build of OpenJDK 以外はarm版Windows用バイナリが用意されていません。

Temurin by Adoptium

特になにも条件がなくて単にJavaを動かしたいというときにおすすめしているのがAdoptiumのTemurinです。 「Adoptium」で検索すると出ます。「Temurin」でも大丈夫。

ただしくはEclipse Temurinですが、Eclipse IDE専用みたいな感じがしてしまうので、AdoptiumとかTemurinとか呼んでます。 そのためか、トップページにはEclipseと大きく書いてないですね。
https://adoptium.net/

アクセスした環境を判定して自動的に最適なLTSバイナリを選んでくれるので、目につくダウンロードボタンを押すだけでいいのも推しポイントです。

Java 19のようにLTSじゃないバージョンをインストールしたい場合には「Other platforms and versions」から飛んでバージョンなどを選択する必要がありますが、ここではOSなども設定する必要があります。デフォルトを設定してくれてればいいのに。

まあ、ここではLTSでいいという方針で行くので、トップページのダウンロードボタンを押してOpenJDK17U-jdk_x64_windows_hotspot_17.0.5_8.msiをダウンロード。

インストーラが起動します。

Nextボタンを押すとセットアップ画面が表示されて、ここでJAVA_HOMEなどの設定を指定することができます。「Will be installed...」をインストール時にJAVA_HOMEが設定されます。

PATHJAVA_HOMEも設定されてますね。

なので、いま一番楽なJavaインストール手順は、「Adoptium」で検索して最初のサイトを開いてダウンロードボタンを押して取得したインストーラを起動、JAVA_HOMEの設定を指定してインストール継続、という感じになりますね。

Amazon Corretto

Temurinの説明で「特になにも条件がなくて」と書きましたが、代表的な条件に「AWSAmazon Linux上で動かす」のようにクラウドサービスとそのサービス提供のLinuxで動かすというものがあります。その場合に、そのクラウド提供者がJDKディストリビューションを出しているのであれば、そのJDKを使うのが無難です。Oracle Cloudを使う場合にはOracle JDKのライセンスもついてたりしますね。
Amazonの場合はCorrettoというディストリビューションを出しているので、これを使うことも多いと思います。もちろん、他のディストリビューションでも動きますが、Corettoなどを使うとクラウドサービス用に動作検証や最適化がされている可能性がありますね。

「Corretto」で検索すると出てくるのだけど、rをふたつ書くのと最後のoも忘れて「corettt」としたり最後をeにして「corette」にするとぜんぜんひっかからなくなるので「Amazon Corretto」で検索しましょう。
https://aws.amazon.com/jp/corretto/

バージョンを選ぶとパッケージいちらんが表示されるので、その中から自力でみつける必要があります。
まあ、Corretto使う人はコマンドラインでインストールするやろみたいな考えなのだと思います。

ここからamazon-corretto-19.0.1.10.1-windows-x64.msiをダウンロード。

インストーラが起動します。

「Setup Environment」の中の「Setup Environment Variables」を選択しておくとPATHJAVA_HOMEが設定されます。デフォルトで設定されるので特にいじる必要もないです。

パスがとおってJAVA_HOMEも設定されています。

Azul Zulu

以前はOracle JDK以外のJDKといえばAzulという感じだったのですが他がどんどんディストリビューションを出してきたので影が薄くなりましたね・・・
「Zulu Download」で検索すると出てきます。
独自のJITを持っていたりするので面白いのですが、ここではそういった特別な機能を含まず無償で使えるZuluを使います。
https://www.azul.com/downloads/

スクロールすると「Download Now」というボタンがあるので、左端のZuluの列のものを押します。
バージョンやOSなどは自分で選ぶ必要があります。

JDKだけではなくJREJavaFX付のパッケージを選ぶこともできます。JREに実際に何が含まれているかは要確認

zulu19.30.11-ca-jdk19.0.1-win_x64.msiをダウンロード

インストーラが起動します。

JAVA_HOMEの設定を指定する画面がでます。デフォルトでは設定されないのでTemurinと同様に指定する必要があります。

インストールが終わるとPATHJAVA_HOMEも設定されました。

Liberica JDK

Liberica JDKはBellSoftの提供するJDKです。
「Liberica」で検索すると出てきますが、「Download for Free」を押して「Download」を押すと次の画面が出てきます。
https://bell-sw.com/pages/downloads/#/java-19-current

最初はJDK 8 LTSが選択されているので、所望のバージョンを改めて選択する必要があります。 そうするとあとは「Windows」のダウンロードボタンを押すとダウンロードが始まります。
bellsoft-jdk19.0.1+11-windows-amd64.msiをダウンロードします。

起動するとインストールが始まります。

環境変数などを選ぶ画面が出ますが、デフォルトですべて選択されているので特に変更する必要はありません。

PATHJAVA_HOMEも設定されています。

Microsoft Build of OpenJDK

名前のとおり、MicrosoftがビルドしたOpenJDKです。
Azureを使うと最初から入っているかも。
Microsoft JDK」で検索すると出てきます。
https://www.microsoft.com/openjdk

独自にエスケープ解析が実装されたりもしています。
https://devblogs.microsoft.com/java/microsoft-build-of-openjdk-october-2022-psu-release/

「ダウンロード」ボタンを押すとパッケージ一覧が出ます。LTSバージョンしかリリースしないようですね。
フィルターなどはないので、自分でファイルを選びます。

microsoft-jdk-17.0.5-windows-x64.msiをダウンロードします。

インストールが始まります。

JAVA_HOMEはデフォルトでは設定されないので、指定する必要があります。

PATHJAVA_HOMEも設定されました。

SapMachine

さて、インストーラー最後はSapMachineです。名前のとおりSAPがビルドしたOpenJDKです。
「sap jdk」などでも出ると思いますが「sapmachine」で検索するのが確実
https://sap.github.io/SapMachine/

スクロールすると環境が指定された状態になっているので「Download」ボタンを押すだけです。

sapmachine-jdk-19.0.1_windows-x64_bin.msiをダウンロード、なのですが、なにやら危険なファイル認定されてしまう。署名がされてないのかな。

「・・・」を押してメニューから「保存」を選びます。

そして「詳細表示」をクリック

「保持する」を押せば、ようやくファイルが使えるようになります。

これで「ファイルを開く」とインストーラが起動するのだけど

やっぱり保護されてしまうので、「詳細情報」を押す

で、「実行」を押すとようやくインストーラが起動します。

インストーラが起動

ライセンス確認のあと環境選択が出ますが、環境変数まわりは全部デフォルトでオフです。

「Optional Features」で「Entire feature will be installed on local hard drive」を選ぶと全部が設定されます。

インストールすると、PATHJAVA_HOMEも設定されます。

しかし、さすがにこれを人にはオススメできないですね。

OpenJDK

ちなみに、LTSではない最新版を試すのであれば、OpenJDKサイトからzipなどのアーカイブをダウンロードして展開するのが楽ですね。
最新版を試したい人ならパスの設定もわかるだろうし、そもそも試すだけならjavaコマンドのフルパス指定で問題ないので。開発もIDE使うだろうから、IDEにパスの設定を追加すればいいですからね。
https://jdk.java.net/19/

SDKMAN!

さて、いろんなバージョンを使うからインストーラなんか使ってられないという人もいるんじゃないかと思います。まあだいたいはzipダウンロードで展開しておけばどうにかなると思いますが、コマンドライン作業が多いのでパスもちゃんと切り替えたいということもあるでしょう。
そういうときにはSDKMAN!でインストールするのがいいですね。
https://sdkman.io/

MacLinuxでは、サイトに書いてあるとおりcurl -s "https://get.sdkman.io" | bashを実行すればインストールされます。

Windowsの場合、コマンドプロンプトでは使えないので、CygwinなどのUNIX系ターミナルが必要です。
https://www.cygwin.com/install.html

setup-x86_64.exeをダウンロードして起動します。このexeはあとでcygwinにパッケージをインストールするときにも必要になるので、どっかに置いておきましょう。

ダウンロードサイトを選べと出ます。どこでもいいと思うのだけど、jaistとかを選んでます。

パッケージを選ぶ画面が出ますが、zipとunzipがないとSDKMAN!のインストールに失敗し、curlがないと実行に失敗します。

特にcurlを忘れるとそれっぽく動くのに怒られるのでハマります。
https://nowokay.hatenablog.com/entry/2019/06/12/035324

まあ、Cygwinがインストールできたり、LinuxMacの人はターミナル開いてSDKMAN!のサイトに書いてあるスクリプトを動かしましょう。

ちゃんとインストールできると「Enjoy!」って出るのでエンジョイしましょう。

ターミナル再起動するか、メッセージにあるsource "/home/WDAGUtilityAccount/.sdkman/bin/sdkman-init.sh"を実行するとsdkコマンドが使えるようになります。WDAGUtilityAccount部分はログインアカウントになります。

sdk list javaとするとインストール可能なJava一覧が出てきます。

sdk install java 19.0.1-openとするとOpenJDKがインストールされます。この19.0.1-openの部分には先ほどの一覧のIdentifierの欄の文字列を指定します。

この時点でPATHは通ってますが、JAVA_HOMEは設定されていません。

sdk use java 19.0.1-openとするか、ターミナルを起動しなおすとJAVA_HOMEも設定されます。

LinuxMacはともかく、Windows環境でCygwinいれてまでSDKMAN!を使う必要があるかという話になりますが、結局SDKMAN!でJDKを切り替えたいのはコマンドラインでいろいろやる場合なので、JDK切り替えるツールが欲しいくらいコマンドラインでいろいろやるならCygwin使ってもいいんではないかなという気がします。

winget

ぶこめにあって素敵そうなので試そうと思ったんだけどサンドボックスで動かせなかったので保留

OpenJ9 / Semeru Runtime

IBMがJ9として開発していたJVMEclipseに寄贈されてオープンソースになっていました。愛してる人もいるだろうと思って追記
Adoptiumの前身、AdoptOpenJDKではJ9版もリリースしてたなぁと思ってAdoptium見てもHotSpot版しかなく、SDKMAN!にあったなぁと思って確認してもなく、そして、公式サイトをみてもダウンロードリンクがない、どこからもダウンロードできなさそうなプロダクトになっていました・・・
https://www.eclipse.org/openj9/

ということで、Semeru Runtimeという名前になっているようです。
「Semeru」だけだと火山が出てきたりするので「IBM Semeru」や「Semeru Download」で検索するのがいいですね。 OSは自分で選ぶ必要があります。Java 18はあるけどJava 19はない模様
https://developer.ibm.com/languages/java/semeru-runtimes/downloads/

ibm-semeru-open-jdk_x64_windows_17.0.5_8_openj9-0.35.0.msiをダウンロードします。

インストーラが起動しますね。

そして、JAVA_HOMEはデフォルトでは設定されないので指定が必要です。

PATHJAVA_HOMEも設定されていますね。そして、他のJDKとは違う表示が。

OpenJ9はJVMの名前。ふつうのJDKJVMはHotSpotと呼ばれます。
OMRはGraalVMのTruffleみたいな、スクリプト言語の開発のためのランタイムだと思います。 https://www.infoq.com/jp/news/2016/04/ibm-creates-omr/

JCLはなんだろう?Job Control Language?

Red Hat Build of OpenJDK

Red HatはOpenJDKの開発においてOracleについで多くのソースをコミットしている企業なので、当然ディストリビューションをもってます。
Red Hat Java」とかでぐぐると出てきますね。
しかし、ダウンロードにアカウントが必要なことや、Mac版がないことから、今回は見送り。
サーバーにRed HatLinuxを使ってたりアプリケーションサーバーにJBoss EAPを使ってたり、Red Hatにお世話になっている場合には使うことになると思います。

GraalVM

Oracleが開発しているもうひとつのJava、GraalVMもとりあげておきます。JITにGraalを使っていて、それを事前コンパイルに使うことでネイティブイメージを作ることで話題。 「GraalVM」で検索すれば出てきます。
https://www.graalvm.org/

ダウンロードはOSとバージョンの表になっててわかりやすいですが、インストーラはありません。

Scoop

これもWindowsのパッケージ管理として便利そうなのだけど、やっぱサンドボックスで動かせなかった・・・
https://scoop.sh/

ChatGPTのヤバさは、論理処理が必要と思ったことが確率処理でできるとわかったこと

ChatGPTのヤバいところは、論理処理が必要だと思っていたことが、じつは多数のデータを学習させた確率処理で解決可能だと示したことだと思います。

たとえば、このように正規表現にマッチする文字列を生成するには、特別に専用の論理処理が必要だと思っていました。

前のブログのときには特殊処理が必要だと考えてましたね。
ウソはウソと見抜ける人じゃないとChatGPTを使うのは難しい - きしだのHatena

けど、123_45678world.mdはマッチするのにマッチしないと言っているので、そのような誤りが入ることを考えると、どうも確率処理だけでやっているようです。

考えてみると、3層以上のニューラルネットであれば論理素子を再現できるので、ディープラーニングで論理処理を模倣することは可能なんですよね。
バックプロパゲーションでニューラルネットの学習 - きしだのHatena

そもそも論理は、多数のデータに見られる傾向を一般化してデータの意味にかかわらず適用できるようにした法則なので、もともとが確率処理でできるというのも無理はない。論理というのは「データがなくても導ける」というものなので、データがあるなら当然に導けるという話でもある。

ただ、この場合「1100円でバットとボールを買いました。バットはボールより1000円高かったのですが、それぞれいくらでしょう」に対してバットを1000円、ボールを100円と答えてしまうような間違いをするのと同じような間違いをします。

と書いたあとで試してみると、ほんとにそのような間違いをして笑いました。

この、バットとボールの問題は、次のGigazineの記事にあったものです。
直感的に「正しい」と判断した問題は実は間違っているかもしれない、認知機能を暴く「バットとボール問題」と対処法とは? - GIGAZINE

この記事では、思考のプロセスには「意識的に熟考することはほとんどなく、素早く実行する」と「熟考し、ゆっくりと実行する」の2過程があるとしています。前者の段階でバットを1000円のように間違うのは自然なので、そのあとでちゃんと考えろという話です。

前者はカーネマンのいうシステム1での処理で、後者がシステム2での処理なんだと思います。そして、ChatGPTをはじめディープラーニングでの処理はシステム1での処理を模したもので、だいたい正しそうでよくみると間違っているというような答えを出してくるのだと思います。

画像認識だとこういうのがありますね。これ、人間にも1頭の長い牛に見えてしまうので面白いのだと思います。しかし人間は、このような牛はいないとか頭としっぽが隠れることでこのように見えるということを考えて、「1頭ではない」という結論をえるわけです。このような常識を用意して前後関係を考えて結び付けていくというのを特別な処理ではなく汎用にやるのが難しい。

思えば、たとえば次のようなコードは構文の意味やメソッドの意味を考えることなく何をするかがわかります。

for (int i = 0; i < 10; ++i) {
  System.out.println("Hello!");
}

入門のとき以外で「初期処理でiに0を入れて繰り返しで++で1増やしてiが10より小さいときに繰り返すから・・」のように考えることはありませんね。
まあ、プログラミング言語というのは論理処理を感覚的に処理できるように工夫するものでもあるのでRubyなんかでは次のように書けるようになってたりしますね。

10.times do |i|
  p i
end

あと、ぼくの感覚処理にはバグがあるので、数字で始まる行は行番号に見えてしまう。

直観だけでは間違ってしまうことがあるし、たとえばCスタイルのfor文では、ちょっと条件や再計算処理が変化すると感覚的には追えずにひとつづつ確認する必要もあったり、最終的に論理処理が必要になります。
ChatGPTを見た人が「あとは論理的な確認を行うようにすれば完璧」などということがありますが、このような汎用の論理処理を論理コンピュータで実用的に計算することは計算量の爆発の問題があって不可能なので、量子コンピュータが実用化して普及するまでは正しい処理には人間が必要という状況は変わらないと思います。

2023/1/11 「予想どおりに不合理」のダン・アリエリーは「statistics is a nuiscance(統計は邪魔だ)」というような発言をする人だと思い出したので、少なくとも自身の実験は妥当とされているカーネマンの「ファスト&スロー」に差し替え。あと、大脳xxと書いてたところをシステム1, 2に変更

陽性と陰性の確率がそれぞれ50%の場合に最も検査の価値が高い

検査の結果には陽性と陰性しかないんだからそれぞれ50%、のような発言をみかけました。どうも文脈としては、検査をうけなければ100%陰性なので検査をうけないという話のようです。
けれども、もし本当に陽性と陰性がそれぞれ50%なのであれば、それは一番検査の価値が高いので、検査を受けるべきだと思います。

たとえば、極端な例として100%陽性になる検査があるとします。たとえば「血液があるかどうか」という検査は、生きている人間なら100%陽性になります。そういう検査は受けなくても結果がわかるので必要がないですね。逆に100%陰性になる検査も受ける必要がありません。

少し確率をいじって、10%が陽性で90%が陰性の場合はどうでしょうか。この場合、陽性がでれば価値が高いですが、多くの場合は陰性で、検査の結果で陰性だとしても「やっぱりね」となるので検査全体の価値はそこまで高くないです。
お年玉くじ付き年賀状をもらって「どうせはずれだから結果をみない」となるのも似たようなものです。この場合は当たる額も影響するのだけど。

で、確率をいじっていくと、50%が陽性で50%が陰性という場合が一番検査の価値が高くなります。どちらが出てもおかしくない、という場合ですね。
サッカーの試合でも、一方が圧倒的に強ければ(弱ければ)見なくても結果がわかるので試合を見ないですが、勝つか負けるかわからないとなると試合を見たくなります。

こういった場合に、その情報の価値を計算する 情報量 という考え方があります。
情報量というのは、簡単にいうと「びっくり度合い」です。その結果を見たときにどのくらいびっくりするかというものです。10%が陽性で90%が陰性の場合、陰性がでてもびっくりしないですが、陽性がでるとびっくりします。その検査の結果の陰性というのは「やっぱりね」なのでびっくりせず情報量が低いです。一方で陽性だと「なんと!」となって情報量が高いです。

計算としては、その結果がでる確率をpとすると情報量は log \frac{1}{p}となります。簡単にいえば、確率が低いほど情報量が高くなって0に近いとかなり大きい値になり1に近いと0になる計算です。

そして、結果すべての情報量の平均を平均情報量とかエントロピーといいます。
陽性の確率をp1、陰性の確率をp2にしたとき、平均情報量は p_1 log \frac{1}{p_1} + p_2 log \frac{1}{p_2}になります。
10%が陽性で90%が陰性であればp1=0.1, p2=0.9で、logの底を10としたとき、 \frac{1}{10} log 10 +  \frac{9}{10} log \frac{10}{9} \risingdotseq 0.14になります。50%が陽性で50%が陰性であれば \frac{1}{2} log 2 +  \frac{1}{2} log 2 = log 2 \risingdotseq 0.3となります。結果が半々のほうが平均情報量が高い、エントロピーが高いということになります。つまり検査の価値が高いということですね。

実際に検査体制がととのってないときには、検査の価値を高めるために、肺に影があるなどcoivd19の可能性が高い場合にはPCR検査をすることなく陽性とみなしたり、症状がない場合には検査をしないのような対策をして、検査をする場合の平均情報量が高くなるような工夫をしていましたね。

ところで、エントロピーが高いというのは全体的にびっくり度合いが高いということです。そして片付いた部屋はエントロピーが低く、散らかった部屋はエントロピーが高いということになっています。
これは、片付いた部屋で何かを手にとっても予測の範囲であることが多いのでびっくりしないですが、ちらかった部屋で何かを手にとったとき「おー、こんなものがある!」のようにびっくりすることが多いことからもわかりますね。
検査のエントロピーは高くするべきですが、部屋のエントロピーは低くしましょう。

Jakarta NoSQLを試す(動かない)

なんかJakarta NoSQLというのが出ていて素敵そうだったので試してみた。
サポートしているデータベースがこんな感じで、このあたりを統一的に扱えると便利そう。
http://www.jnosql.org/docs/supported_dbs.html

NoSQLといっても色々あって単一APIでは難しいんでは、というのはそのとおりで、Key Value、Column、Document、Graphの4分類で扱う感じっぽい。

このGetting Startedに従ってやってみる。
http://www.jnosql.org/docs/document.html

結果としては、動かなかった。
けど、準備としては必要なことをやってるはずなので、メモとして残しておいて、今後正式リリースされてちゃんと動きそうになったらまた試してみる。

MongoDBを起動する

今回はMongoDBを試すけど、Dockerで動かします。

> docker run --name testmongo -d -p 27017:27017 mongo

試しに普通にJavaから接続する

まずは普通にJavaコードで。
dependencyにこういうの追加します。

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>4.8.1</version>
</dependency>

データつっこむやつ。

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.bson.Document;

public class TryInsert {

    public static void main(String[] args) {
        
        var uri = "mongodb://localhost";
        try (MongoClient client = MongoClients.create(uri)) {
            var db = client.getDatabase("mydb");
            var coll = db.getCollection("mycol");
            var doc = Document.parse("""
                           {
                             id: 123,
                             name: "kishida",
                             phone: ["234", "123"],
                           }
                           """);
            coll.insertOne(doc);
        }
    }
}

データ読み込むやつ

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;

public class TryRead {
    public static void main(String[] args){
        var uri = "mongodb://localhost";
        try (MongoClient client = MongoClients.create(uri)) {
            var db = client.getDatabase("mydb");
            var coll = db.getCollection("mycol");
            var ite = coll.find();
            for (var d : ite) {
                System.out.println(d);
            }
        }
    }
}

成功。よかった。

Document{{_id=63b68bfe6414fd518da18292, id=123, name=kishida, phone=[234, 123]}}

Jakarta NoSQLのための依存ライブラリ

ということでJNoSQLを試す。 まずはMongoDBはdocumentデータベースなので、次のようなmappingを追加

<dependency>
    <groupId>org.eclipse.jnosql.mapping</groupId>
    <artifactId>mapping-document</artifactId>
    <version>1.0.0-b4</version>
</dependency>

そしてMongoDBのためのドライバを追加

<dependency>
    <groupId>org.eclipse.jnosql.communication</groupId>
    <artifactId>mongodb-driver</artifactId>
    <version>1.0.0-b4</version>
</dependency>

CDIが必要なのでWeld SEを入れる

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-shaded</artifactId>
    <version>5.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se</artifactId>
    <version>2.4.8.Final</version>
</dependency>

MicroProfile Configが必要なので入れる

<dependency>
    <groupId>org.eclipse.microprofile.config</groupId>
    <artifactId>microprofile-config-api</artifactId>
    <version>3.0.2</version>
</dependency>

あと、標準アノテーションも入れておく

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

試してみる

CDIを使うので、resources/META-INF/beans.xmlに空のファイルを作っておく。

マッピング用のクラス。JPAっぽいけど違うアノテーションを使う。共通にしてほしい。

import jakarta.nosql.mapping.Column;
import jakarta.nosql.mapping.Entity;
import jakarta.nosql.mapping.Id;
import java.util.List;

@Entity("Person")
public class Person {
    @Id("id")
    long id;
    
    @Column
    String name;
    
    @Column("phone")
    List<String> phones;
}

こういうDocumentCollectionManagerをとってくるためのProducerを用意。getting startedではクラス名にミスがありそう。

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.nosql.Settings;
import jakarta.nosql.document.DocumentCollectionManager;
import jakarta.nosql.document.DocumentCollectionManagerFactory;
import jakarta.nosql.document.DocumentConfiguration;
import java.util.Map;
import org.eclipse.jnosql.communication.mongodb.document.MongoDBDocumentConfiguration;

@ApplicationScoped
public class DocumentCollectionManagerProducer {
    private static final String COLLECTION = "mycol";
    private DocumentConfiguration config;
    private DocumentCollectionManagerFactory manFact;
    
    @PostConstruct
    public void init() {
        config = new MongoDBDocumentConfiguration();
        Map<String, Object> settings = Map.of("mongodb-server-host-1", "localhost:27017");
        manFact = config.get(Settings.of(settings));
    }
    
    @Produces
    public DocumentCollectionManager getManager() {
        return manFact.get(COLLECTION);
    }
}

こんな感じらしい。

import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import jakarta.nosql.document.DocumentQuery;
import jakarta.nosql.mapping.document.DocumentTemplate;
import java.util.List;
import java.util.Optional;
import static jakarta.nosql.document.DocumentQuery.*;

public class App {
    public static void main(String[] args) {
        try (SeContainer cont = SeContainerInitializer.newInstance().initialize()) {
            Person p = new Person();
            p.name = "yamamoto";
            p.phones = List.of("321", "543");
            p.id = 918;
            
            var temp = cont.select(DocumentTemplate.class).get();
            temp.insert(p);
            
            DocumentQuery q = select().from("Person").where("id").eq(918).build();
            Optional<Person> result = temp.singleResult(q);
            System.out.println(result);
        }
    }
}

では動かしてみよう。

なんかDocumentTemplateがふたつあるといって怒られた・・・

Exception in thread "main" org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001335: Ambiguous dependencies for type DocumentTemplate with qualifiers 
 Possible dependencies: 
  - Managed Bean [class org.eclipse.jnosql.mapping.document.DefaultDocumentTemplate] with qualifiers [@Any @Default],
  - Managed Bean [class org.eclipse.jnosql.mapping.document.DefaultDocumentTemplateProducer$ProducerDocumentTemplate] with qualifiers [@Any @Default]

それでは、とリポジトリ使うやつを試す。 こんな感じにインタフェース作ると実装を勝手につくってくれる。

import jakarta.inject.Named;
import jakarta.nosql.mapping.Repository;
import java.util.List;
import java.util.stream.Stream;

public interface PersonRepository extends Repository<Person, Long> {
    List<Person> findByName(String name);
    Stream<Person> findByPhones(String phone);
}

そして動かす。

import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import java.util.List;
import org.eclipse.jnosql.mapping.DatabaseQualifier;

public class App {
    public static void main(String[] args) {
        try (SeContainer cont = SeContainerInitializer.newInstance().initialize()) {
            Person p = new Person();
            p.name = "yamamoto";
            p.phones = List.of("321", "543");
            p.id = 918;

            PersonRepository repo = cont.select(PersonRepository.class)
                    .select(DatabaseQualifier.ofDocument("mycol")).get();
            repo.save(p);
        }
    }
}

@DatabaseはQualifierではないと怒られた。

Exception in thread "main" org.jboss.weld.exceptions.IllegalArgumentException: WELD-000814: Annotation @jakarta.nosql.mapping.Database(value=DOCUMENT, provider="mycol") is not a qualifier

なんか設定でどうにかなる問題じゃなさそうなので、今日はあきらめ。
ちゃんと使えるなら使いやすそうなAPIなので、正式化は楽しみ。
しかしJava / Jakarta EEは難しい・・・

今年よかったマンガ 2022

今年も最後なので、よかったマンガをまとめておきます。基本的には今年よみはじめたものです。
基本的には、ストーリーがしっかりしているものが好み。異世界もの多め。安易な裏切りとかバカな行為で話がすすむということがあまりないやつ。細かい設定の荒は、物語の中でツジツマがあっていれば気にしない方針です。
あと、マンガを面白く紹介する能力はないので、そこは期待しないで。

1月2月にまとめ買いした本が多くて、そのころ調子が悪かったんだなーという思い出し。

一覧

アオアシ28巻~30巻

サッカーマンガ、アオアシは、まあ普通に面白いんだけど、28巻から登場の司馬さんとの話がすごくよかった。
ユース所属中の主人公・葦人がトップチームの練習に参加することになって、意識の違いに面食らう。そこでチームを引っ張っぱり育ててきて引退を考えてる司馬選手に出会って目標にしていくという、この二人のやりとりがめちゃ面白かった。世代交代育成物語。
28巻から30巻まで、キリよく始まって他の登場人物はあまり気にしなくていいので、ここまで見てなくても割と読めるので、追ってない人にもおすすめ。

転生した大聖女は、聖女であることをひた隠す

森で瀕死の黒龍に回復薬をかけたら、回復時の激痛を攻撃と勘違いされて反撃をうけて逆に瀕死になって、そのときに前世が大聖女であることを思い出して、黒龍を隷属させる契約をしていろいろ活躍する話。
けれども前世で魔人に殺され、そのとき生まれ変わっても殺すといわれてたので大聖女であることをひた隠していくのだけど、その隠し方が隙がありすぎてまわりを巻き込んでドタバタしていく話。
人を助けていく いい話とドタバタがいい感じに混じっていて面白いです。

異世界黙示録マイノグーラ

完全に表紙買い
病弱で死んだら国家運営シミュレーションゲームの世界に転生したので、英雄ユニット「アトゥ」と共に最も邪悪な国家マイノグーラを運営するという話。
しかし、主人公はあまりしゃべらないし動かないので、ほぼ画像左のアトゥさんが主人公のようになっている。

というか、アトゥさんを見るために読んでいる。

貼らないけど、2巻第7話の このコマのあとが最高です。

7話、8話、完全にアトゥさん顔芸回

ちゃんと話もおもしろいです。3巻なんか不穏なところで終わっている。4巻はよ。
マイノグーラを讃えよ!

最強の黒騎士、戦闘メイドに転職しました

不慮の死をとげた黒騎士部隊隊長が、女の子に転生して、しかし両親は強盗団に殺され孤児になり、貴族に拾われて戦闘メイドになる話。
そして黒騎士部隊の仲間と再会して、転生がバレたりバレなかったりしながら、昔の雰囲気を取り戻していきつつ、自分がなぜ殺されたのか探っていくという話です。
この、昔の仲間との再会のしかた、馴染んでいきかたがとてもよいです。

3巻までunlimitedで読めます

ウォルテニア戦記

異世界召喚されて、そこで召喚者を殺して逃亡、ふたりの女の子奴隷を助けたのを皮切りにどんどん仲間が増えてなんやかやと恐らく国になっていくという軍記ものです。
異世界ものといえば出だしでタイトル回収して3巻くらいでネタ切れ惰性で続くというようなものが多い中、タイトル回収は5巻の最後です。 という感じで、シナリオがしっかりしていて、そして元の世界ともつながる伏線があるという、

信じていた仲間達にダンジョン奥地で殺されかけたがギフト『無限ガチャ』でレベル9999の仲間達を手に入れて元パーティーメンバーと世界に復讐&『ざまぁ!』します!

9種族いて人間が劣等種扱いされている世界で、「無限ガチャ」という珍しいギフトを与えられた主人公が9種族パーティーに混ぜてもらってたのだけど、やっぱそのギフトはカビパンとかしか出ないハズレだってことでダンジョン奥底で殺されかけ、転移トラップにはまって逃げることができたものの魔物に襲われ、藁にもすがる思いでガチャをまわすとなぜかSURとかのカードばかり出て裏切ったパーティメンバーに復讐の「ざまぁ」していくという話。
絵がきれい。あと、やっつけられる人たちは高慢でイヤな人として描かれているので、ボロクソやられていくところがスッキリしてしまう。
あと、絵がきれい。

処刑された賢者はリッチに転生して侵略戦争を始める

勇者と共に魔王を倒した賢者が、魔王がいなくなった世界では脅威とみなされて処刑され、リッチとしてよみがえって魔王になって世界の悪役になっていく話。
基本的には戦争して領土を広げていく軍記ものかな。
人間がまとまるには共通の悪が必要という信念の元、魔王をやっていくのがよいです。

1巻はunlimitedで読めます。

世界でただ一人の魔物使い

勇者がイヤになって「転職の書」を探して転職したら魔物使いになり、魔物を仲間にしながら旅をしてたら、エルフの隠れ里にたどりついて、そこを拠点に魔物の国を作ることになったという話。
戦闘多めの領地運営ものという感じ。
このメンバーが仲間になっていく過程が面白いです。
戦闘多めだけど、基本はほのぼのやっててよい。

1巻はunlimitedで読めます。

魔王軍最強の魔術師は人間だった

とりあえずタイトル通り。魔族を偽った人間を幹部にした魔王軍が人間の国に攻めていく軍記もの。
だんだん人間であることは気にされなくなっていくし、魔王軍も魔族人間混成軍になっていくしで、だいたい普通に戦争していく感じになっていくのだけど、その変化が面白い。

というか、書き込みがすごい。

俺はまだ、本気を出していない

転移でも転生でも召喚でもなく、ふつうにオレツエーな貴族4男主人公が、4男という立場にあぐらをかいてダラダラ生きていこうとしたら当主と1-3男が隕石で死亡したことにより当主になってしまったので、ダメ当主を演じて当主交代を狙おうとしたら、本気だしてないのにいろいろ解決してしまうという話。
一応領地経営ものではあるけど、本気だしたくない主人公が なんやかやうまくやりすぎてしまうドタバタが楽しい。

unlimitedで1巻読めます

アカネノネ

有名ミュージシャンを父にもつけど音楽性を認められていないボカロPな主人公が、歌い手をみつけてやっていく話。
最初は「茜色のコンポーザー」だったのが、月刊スピリッツから週刊スピリッツに移籍してタイトルが変わったぽい。
ライブをやらないミュージシャンの話は珍しいし、もちろんユニットつくって順当に賞を取ったりするんだけど、そのあとの展開がそうくるかーってなって面白いところ。

即死チートが最強すぎて、異世界のやつらがまるで相手にならないんですが。

修学旅行のバスごと異世界転移して、チート能力を与えられて賢者を目指せと言われるのだけど、主人公と女の子は能力が発現しなくて荒野に置いていかれ、でも主人公は「死ね」と思っただけで相手を殺せる最強チート持ちだし、女の子もなんだかんだ強くて旅を続けるのでした、という話。
そんな最強チートでどうやって話を進めていくんだろうと思ったら、ちゃんと面白く話がすすんで面白い。

1巻2巻、unlimitedで読めます。

異世界転移したのでチートを生かして魔法剣士やることにする

異世界でチート能力持ちが謎解きアドベンチャーやる話。原作の進行諸島先生の話はだいたいそんな感じで、人物の心情描写とかはほぼナシで魔物討伐や謎ときをやっていく感じ。なので異世界賢者の転生無双にしろ失格紋の最強賢者にしろ同行者の女の子は基本的にファンネル扱い。転生賢者の異世界ライフは同行者がスライムで、そのスライムごしに魔法を打つのでまさにファンネルですね。
で、「チートを生かして」の場合は生成した魔法を同行の不思議な女の子に操作してもらうという感じで、役割がすっきりしていて いいです。
楽しみどころとしては、黒幕やら事件やらの仕掛けが凝っているので、そういうのの解決を楽しむ感じ。
まあ、基本的にどの作品も平和に話が進んでいくので、絵柄とかで選ぶのがいいと思う。

神さまSHOPでチートの香り

全5巻完結で4巻までUnlimitedで読めるので読んでみたら結構おもしろかった。
内容としては異世界現代日本の商品を売る商売系をベースにドワーフとエルフの仲間や商会の女の子と仲良くなっていく話。
細マッチョ エルフとのやりとりがよかった。
ただ、異世界に送り込んだ神様のシンボルを現地3神の神殿に奉納していくという設定がひとつもこなされないまま「俺たちの旅はこれからだ」で終わったのは残念。女の子との関係も中途半端だったけど、オマケ小説で回収したのでまあよし。

4巻までunlimitedで読めます

APIといえばWeb APIになった現在、ローカルAPIは専らライブラリと呼ばれる説

APIというとWeb APIのことを指すようになってしばらくたちますが、こういう場合WebじゃないほうのAPIを指すレトロニムができるはずなんですよね。

例えばこのエントリのタイトルではローカルAPIという言葉を使ったけど、埋め込みAPI、組み込みAPIという言い方も可能な気はして、そしてどれもしっくり来ない。シェアドライブラリを考えると埋め込みAPI / 組み込みAPIというのは不適切でローカルAPIが適切な気がするけど、違和感が大きい。

元々でいうと、アプリケーションプログラマがなんらかミドルウェアなどを使うための入り口というのはAPIで、SQLAPIのひとつだったりした。
C.J.DateとCodd博士の「The relational and network approaches: Comparison of the application programming interfaces」ではSQLの前身であるDSLとネットワークデータベースでのデータ操作をAPIとして比較している。 https://dl.acm.org/doi/10.1145/800297.811532

Joshua Blochはこの「APIの歴史」というプレゼンで、「CLIAPIか?」という話をしていて面白い。
https://www.infoq.com/presentations/history-api/

まあもちろん、いまはエントリポイントと引数が定義されて呼び出し可能なソフトウェアパーツをAPIとよぶわけだけども。(しかしそうするとCLIAPIになるな?)

で、考えてみると、ローカルなAPIは「ライブラリ」ということが多くなっていて、Web APIを指すようになった「API」とすみ分けがされた結果(追い出されたとも言う)、ローカルAPIを指すための特別なprefixが定着していないという説。

12/31追記 あと、当然Web APIと縁がなく今までAPIと呼んでたものをそのままAPIと呼んでるところではネームスペース衝突が起きないので、レトロニムが発生しないということか。