この記事は クソアプリAdvent Calendar 2025 の23日目の記事です。
はじめに
モンティ・ホール問題という有名な確率論の問題があります。
プレーヤーの前に閉じた3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会のモンティが残りのドアのうちヤギがいるドアを開けてヤギを見せる。
ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。
ここでプレーヤーはドアを変更すべきだろうか?
一見、ドアを変更してもあたりの確率は変わらないように感じますが、実はドアを変更することであたる確率が1/3から2/3に変わる、という問題です。
正しい確率を計算するための方法はいくつかありますが、直感的な理解のための説明として 「扉を増やして考えてみる」 という解説をよく聞きます。
つまり、問題を以下のように考え直します。
プレーヤーの前に閉じた 100個 のドアがあって、1つのドアの後ろには景品の新車が、 99個 のドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会のモンティが残りのドアのうちヤギがいるドアを 98個 開けてヤギを見せる。
ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。
ここでプレーヤーはドアを変更すべきだろうか?
最初の段階で100個のドアの中からあたりを選んでいない限り、ドアを変更することであたりのドアを選べます。
つまり、ドアを変更することで99%の確率であたりのドアを選ぶことができます。
クソゲーであります。
というわけで今回は扉を100個に増やしたモンティ・ホール問題実装してみました。
作ったもの
扉が100個あります。
選び放題です。
実装について
普通に実装しても面白くないので、AltJSを使って実装してみました。
AltJSとはJavaScriptの代替となるプログラミング言語群の総称で、JavaScriptにコンパイルされることで実行されます。JavaScriptの弱点を補ったり、簡潔な構文を採用したりと様々な言語が存在しますが、わざわざ1つのプロジェクトに複数の言語を用いて無駄に複雑化させるためのものではありません。
というわけで今回はJavaScriptに加えて以下の言語を使いました。
- TypeScript
- CoffeeScript
- PureScript
- Dart
これらの言語でクラスや関数を実装してコンパイルし、JavaScript側でimportして使用しました。今回のプログラム全体を説明すると長くなってしまうので、Hello Worldのプログラムで、どんな感じで組み合わせたかご紹介します。
※ 筆者はこれら4つの言語に関しては全くの初心者であり、今回ご紹介する方法が正攻法なのかすらわかりません。その点ご留意ください。
AltJSをてんこ盛りにするための構成
ディレクトリ構成は以下の通りです。
.
├── build
│ ├── coffeescript
│ ├── dart
│ ├── purescript
│ └── typescript
├── index.html
├── src
│ ├── coffeescript
│ │ └── script_cs.coffee
│ ├── dart
│ │ └── script_dart.dart
│ ├── javascript
│ │ └── script_js.js
│ ├── purescript
│ │ └── script_ps.purs
│ └── typescript
│ └── script_ts.ts
└── tsconfig.json
環境構築
- TypeScript
npm install -g typescript
- CoffeeScript
npm install -g coffeescript
- npmプロジェクトの初期化
npm init
- PureScript
npm install -D esbuild purescript spago@nextnpx spago init
- Dart
- Flutterをインストールしたときに入っていたものを使いました。
実装
基本的にはES Modulesを使い、AltJS側でexportしてJavaScriptでimportします。
Dartだけはやり方がわからなかったため、Dartで定義した関数をJavaScriptからグローバルスコープで呼び出しました。
TypeScript
export function hello_ts(arg: string): void {
console.log(`Hello from TypeScript! ${arg}!`);
}
CoffeeScript
export hello_coffee = (arg='anonymous') ->
console.log "Hello from CoffeeScript! #{arg}!"
PureScript
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
hello_ps :: String -> Effect Unit
hello_ps arg = do
log ("Hello from PureScript! " <> arg <> "!")
Dart
import 'dart:js';
void hello_dart(String arg) {
print('Hello from Dart! $arg!');
}
void main() {
context['hello_dart'] = allowInterop(hello_dart);
}
JavaScript
import { hello_ts } from '../../build/typescript/script_ts.js';
import { hello_coffee } from "../../build/coffeescript/script_cs.js";
import { hello_ps } from '../../build/purescript/Main/index.js';
console.log('Hello from JavaScript! Call by js');
hello_ts('Call by js');
hello_coffee('Call by js');
hello_ps('Call by js')();
hello_dart('Call by js');
HTML
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="./build/dart/script_dart.js"></script>
<script src="./src/javascript/script_js.js" type="module"></script>
</body>
</html>
ビルド
TypeScript用のtsconfig.jsonを作っておきます
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"outDir": "./build/typescript",
"rootDir": "./src/typescript",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": ["src/typescript"]
}
PureScript用にspago.yamlを編集します
package:
# 省略
bundle:
minify: true
module: Main
outfile: "build/purescript/script_ps.js"
platform: browser
type: "module"
workspace:
# 省略
buildOpts:
output: "build/purescript"
ビルドのコマンド
- TypeScript
tsc
- CoffeeScript
coffee -c -o ./build/coffeescript ./src/coffeescript
- PureScript
npx spago build
- Dart
dart compile js ./src/dart/script_dart.dart -o build/dart/script_dart.js
実行
プロジェクトのルートディレクトリでローカルサーバーを立ててアクセスし、開発者ツールを開くとconsoleに以下の出力が表示されます!
それぞれの言語で実装した関数を呼び出すことができました。
Hello from JavaScript! Call by js!
Hello from TypeScript! Call by js!
Hello from CoffeeScript! Call by js!
Hello from PureScript! Call by js!
Hello from Dart! Call by js!
※ 単にindex.htmlをブラウザで開くだけではCORSエラーが発生してうまくいかないので注意してください。
pythonを使うと簡単にローカルサーバーを立てられます。
参考: pythonでローカルwebサーバを立ち上げる #Python - Qiita
まとめ
今回は複数のAltJSを強引に使って扉100個のモンティ・ホール問題を実装しました。
ゲームに関しては開発途中のテストプレイで何度かプレイしましたが1度目であたりを選べた回数は0でした。なかなかクソゲーかと思います。
実装については不安しかない構成でしたが、生成AIサービスのおかげでなんとか環境構築やコードを書き進めることができました。便利な時代です。
関数型言語の経験がなくPureScriptを活かしきれなかったことは少々心残りです。
これを機に関数型言語も勉強してみようかと思います。
