ウイルス感染シミュレーターを作ったので、オーバーシュートを完全に理解した

新型コロナウイルスの問題で、専門家が使う「オーバーシュート」という言葉の意味が問題になっていた。

togetter.com

興味を持ったので、自分でも感染症数理モデルを作り(グラフは参考にしたが、計算するための具体的な数式は見ていない。集団免疫率の定義を見たぐらいだ。)、シミュレーションを行ってみたところ、同様のグラフを得ることができた。ここに、簡単に説明をしておこうと思う。

留意点として、私は疫学の専門家ではないので、以下で使われている用語は疫学で使っているものと異なっているかもしれない。

docs.google.com

f:id:mandel59:20200325093514p:plain
再生産数=2のときのシミュレーション

再生産数は感染者1人がウイルスを伝染させうる人数のこと。ウイルスの性質で決まる、本来的な再生産数を基本再生産数といい、コロナウイルスではだいたい2~3ぐらいであると考えられているらしい。

1度感染した人間は免疫を獲得するとすれば、ワクチンが開発されていない限り、延べ感染者数=免疫獲得者数となる。免疫を獲得した人間にはもうウイルスが感染しないことから、次のような漸化式を立てて、感染者数の変化を考えることができる。(直接、感染者数や延べ感染者数=免疫獲得者数を扱うのは面倒があるので、それぞれ人口で割って、感染者率・延べ感染者数率=免疫獲得者率とする。)

  感染者率' = 感染者率 \times 基本再生産数 \times (1 - 免疫獲得者率)

  免疫獲得者率' = 免疫獲得者率 + 感染者率'

この式を眺めれば分かるが、感染者率は基本再生産数 \times (1 - 免疫獲得者率)が1を超えていればどんどん増えるし、1を下回ればどんどん減っていく。その境界となる免疫獲得者率の値を、集団免疫率という。

基本再生産数 \times (1 - 集団免疫率) = 1

式変形すれば

集団免疫率 = 1 - \frac{1}{基本再生産数}

これらの式から、免疫獲得者率=延べ感染者率が集団免疫率を下回っている間は、感染者は増え続け、延べ感染者率が集団免疫率を上回ったところで、ようやく感染者数が減少に転じるということがわかる。(わからなければ、シミュレーション結果のグラフを見て確認してほしい。上掲のグラフは基本再生産数が2の場合のシミュレーションで、集団免疫率は50%だ。免疫獲得率が50%のところで、感染者数が減少に転じている。)何もしなければ、社会の人口のうち集団免疫率の分の人間がウイルスに感染するまで、ウイルスの勢力は強まり続けるという、避けがたい運命が示されている。ウイルスの封じ込めができなくなった以上、どんな政策を行おうと、いずれ延べ感染者数は、人口の50%~66%に至るのだ。たとえ、新型コロナウイルスの致死率が数%程度だったとしても、大量の人間が死ぬことになる。

もはや、延べ感染者数の人口比が集団免疫率に至ることが避けられないのであれば、私たちは速やかに集団免疫率を目指して、感染するようにすべきなのだろうか?いや、違う。何もしなければ、集団免疫率を大幅に超えて感染者が発生してしまうことになるのだ。

集団が集団免疫を獲得した時点、すなわち、延べ感染者率が集団免疫率に達し、感染者数が減少に転じた時点で、自然と流行に終息に向かうというのは正しい。しかし、流行が終息に向かうというのは、直ちに終息するわけではなく、感染者数が0になるまでは、あらたな感染者が出続ける。シミュレーションでは延べ感染者率は約87%に至っており、集団免疫率とは大きな差が生じている。これこそが、「オーバーシュート」と呼ぶべき現象だ。

しかし、オーバーシュートを回避する方法はある。実効再生産数(感染者が実際に感染を広げる人数)を下げれば良い。つまり、衛生管理を徹底する、外出を避ける等の施策によって、人為的に実効再生産数を下げることができれば、延べ感染者率はより低い値に収束し、その時点で感染者数がほぼ0になる。延べ感染者率が集団免疫率に至った時、感染者がほぼ0である状況であれば、ここから元の生活に戻り、実効再生産数が上昇したとしても、免疫獲得者の割合が多く実効再生産数は1を下回るのでウイルスの流行はもはや拡大しない。(延べ感染者率が集団免疫率を下回った状態ではいけない。延べ感染者率が集団免疫率より少ない状況で、実効再生産数が基本再生産数に戻れば延べ感染者率が集団免疫率より少ない状況では、実効再生産数が1以上に戻りうるので、そこからまた流行が拡大することになる。)

実効再生産数を下げれば、感染者数の最大値も下がる。医療のキャパシティが有限である以上、このことも施策上重要な要素ではあるのだが、感染者数が医療のキャパシティを超えることをオーバーシュートと呼ぶわけではない。オーバーシュートは、延べ感染者数の割合が集団免疫率を超えて増えすぎてしまうことを指すのだ。延べ感染者数×死亡率=総死者数なのだから、延べ感染者数を最小化することは非常に重要だ。

以上が、是非とも実効再生産数を下げる政策を積極的に行わなければならない理屈だ。しかし、このような理屈をまともに書いている記事はほとんどない。医療従事者さえ、この理屈を正しく理解できていないから、オーバーシュートを誤用しているのだろうと思う。これは問題だ。自分でシミュレーションをやってみれば、上の理屈は容易に理解できるはずだ。

ウイルス感染シミュレーターといっても、Google Sheetsで簡単な数式を並べてグラフを作成するだけの、やり方を知っていれば誰でも作れるものだ。実際に自分でも、シミュレーションをして、パラメーターを変えて感染状況の変化を確認してみてほしい。

追記:

追記2 (2020-03-27): 上の記述で、「実効再生産数」が適切に使われていなかったので、訂正し、より正確な表現にした。

オープンソース概念の無意識な風化への抵抗

このツイートが目に触れた。これは問題だ。

リプライにぶら下がっているリンクから、<利用ガイドライン>に飛ぶことができた。

本日より放送開始30周年となる2028年7月6日までの間において、以下の「利用ガイドライン」と「利用規約」の両方に同意いただくことを条件に、日本国内に居住されている個人の方に限り、アニメーション作品「serial experiments lain」(以下「本作品」といいます)の二次創作の利用を商用・非商用にかかわらず無償で許諾します。監修を受ける必要もありません。

また、個人の集合体となるファン・コミュニティによるOpen Source Projectであれば法人格を有しない限り、同様に許諾します。

www.nbcuni.co.jp

Open Source という言葉は、1998年に生まれた。パーソナル・コンピュータが大衆化し、人々の相互接続性が次第に高まっていく、そういう時代だ。奇しくも、あるいは必然か、serial experiments lainが発生した年でもある。(私はそれが存在することを知っているが、触れたことはない。)

Open Source は、概念だ。概念はとらえどころのない存在だが、言葉で定義されることで、社会全体に客観的に理解できる、確固としたものとなる。Open Sourceは、Open Source Initiative によって定義されていて、次のページで定義を見ることができる。

opensource.org

日本語で読みたい人は、次の八田真行訳を読んでもいい。

opensource.jp

第5条には、こういう定義がある。

  1. No Discrimination Against Persons or Groups The license must not discriminate against any person or group of persons.

  2. 個人やグループに対する差別の禁止 ライセンスは特定の個人やグループを差別してはなりません。

ライセンスに「法人格を有しない」という条件を課すことは、当然、差別にあたる。これは、私には(おそらく、他のオープンソースソフトウェア・コミュニティーの人間にとっても)明らかにOpen Sourceとは認められない。

オープンソースとは、個人の集合体という意味ではない。個人であろうと、法人であろうと、差別されず参加できる、そういう仕組みを実現するための言葉なのだ。

だいたい、「法人格を有しない」という条件があるのであれば、法人格を有するいちから株式会社の運営するバーチャル配信者グループ「にじさんじ」に所属する、月ノ美兎は、どうなるのだ。月ノ美兎は、個人なのか、法人なのか?

考えれば、法人という概念自体が、組織をバーチャルな人間とみた概念とも言えるだろう。とらえどころのない組織に対してインターフェースとして与えられた、法的にバーチャルな人間、それが法人だ。月ノ美兎自体は、いちからではなく、いちからの運営するにじさんじに所属するバーチャルライバーではあるし、月ノ美兎は、個人であるように見える。しかし、月ノ美兎の活動は © Ichikara Inc. の付く、いちからの知的財産でもあり、月ノ美兎の、月ノ美兎としての活動は、いちからの活動ということにもなるはずだ。

いったい、この状況で、月ノ美兎は岩倉玲音の凸を受けることができるだろうか? いや、そもそも、Open Source の考えは、そんなことで悩ませたりせず、自由な参加を促すためのものだったんじゃないのか?(これはただの私見だし、私は悩むのが好きだけれども)

Open Source 文化は、日本の同人文化やネット文化と似た面もあるが、本質的に異文化だ。おそらく、日本の同人文化が個人と法人の区別による黙認で二次創作を正当化してきた一方、Open Source 文化は明示的なライセンスによる許諾で二次創作を正当化してきたという文化的背景が、利用ガイドラインに無意識に反映されてしまっているのだろう。しかし、自分が Open Source だと言葉にするなら、Open Source 文化を正しく理解するとまでは言わずとも、少しはOpen Source 文化に触れてみてほしい。社会では色々な属性の人間やグループが、それぞれの意思と目的をもって活動しているが、それにも関わらず、Open Source文化では協同することができる。たとえ競合関係にある企業同士でさえ、その経済的原理から Open Source License のもとで協同することができる。(わたしはこういう、ドライで感情を伴わない状態から、原理に従って協力構造が生まれるという構図が、とても好きだ。)Open Source は、そうしてできあがった営みを指している。

lain は真に Open Source となることを願うだろうか。

概念をフォローする

概念をフォローする

本質的に、わたしたちは概念をフォローしたいのである。

あなたがツイッターで、その人をフォローする理由はなんだろうか。その人自身が本当に好きなのだろうか。単に、好みの記事や、写真や、動画をRTしてくれるから、ではないだろうか。実際に、おもしろ動画botのようなものがあり、それをフォローする人間はたくさんいる。

人間ではなく、概念をフォローするには、どうすればいいだろうか。たとえば、ハッシュタグを追うこともできる。ハッシュタグは、すなわち直に概念に結びついた記号だ。しかし、今のツイッターは、話題を検索することはできても、ハッシュタグを直接フォローすることはできない。

ハッシュタグは、書き手が恣意的に設定しなければならないという点で、ウェブページに設定されたキーワードと同じような、簡易な仕組みだ。それは、機械が直接文章から概念を抽出することができない、あるいは抽出するコストが高いということが前提にある。しかし、わたしたちは現に文章から概念を抽出している。それは、全文検索 Full Text Search という技術がやっていることだ。

全文検索システムは、その名からは想像が付きづらいが、直接文字列を検索するのではない。そうではなく、文章を「語」ごとに分割し、語によってデータの転置操作を行い、索引を作成する。そうすることで、語が与えられると、効率的に、語に結びついた文章を引いてくることができる。そして、語はやはり概念に結びついた記号である。つまり、全文検索を実現する段階で、わたしたちは文章から概念を抽出しているのである。

わたしたちは文章からある程度の精度で概念を抽出する技術を持っているのだが、本来は概念をフォローすることができてもおかしくないのだが、そうはなっていない。わたしたちは概念をフォローするのではなく、ボットをフォローしている。しかし、検索システムは任意のフレーズで自由に検索できるのに対し、ボットは限られた概念しかRTをしてくれない。これは非常に不自由なことだ。人間は有限だが、概念は無限だという問題がある。

分散概念検索システム

わたしたちは、分散SNS上で、概念のフォローを実装できるかもしれない。ActivityPubで、人間のかわりに、概念をフォローできる仕組みが作れるはずだ。インスタンスは、各個人のoutboxの公開アクティビティーから概念を抽出し、概念のoutboxに入れる。この概念のoutboxを、フォローできるようにすればよい。また、概念のoutboxそれ自体が転置インデックスの役割を果たし、インスタンスに対する全文検索機能を提供する。概念のoutboxは仮想的に無限に存在するが、その無限のoutboxを一括でフォローする仕組みがあれば、作成された転置インデックスを他のインスタンスに転送することもできる。もちろん、興味がある概念だけをフォローしてもよい。

個人は特定のインスタンスにしか存在しないが、概念はあらゆるインスタンスに存在する。そこで、上の考えの発展として、概念のフォローは、インスタンスを特定しない形式によるフォローができることが望まれる。インスタンスを特定せず、すべてのインスタンスの特定の概念をフォローできるようにする、あるいは、概念のフォローに関する情報を、連合インスタンス全体に伝播させる仕組みがあれば、わたしたちは、透過的に、純粋な概念をフォローすることができる。

人間やボットを介せず、純粋な概念によって構成されたタイムラインを、わたしは見てみたい。

TopShell: シェル再考

GitHubのExplore repositoriesにたまたま表示されていた TopShell が気になったので、ここで紹介する。

github.com

TopShell開発の動機は TopShell: Reimagined Terminal and Shell · topshell-language/topshell Wiki · GitHub に書いてあるが、要点をまとめると「古典的なUnixシェルを使うのはつらい。いいところだけを抜き出して、全くシェルを考えたら、どうなるだろうか?」ということらしい。

  • Unixシェルのだめなところ
    • 未定義の変数を使ってもエラーにならない【デフォルトで。set -u を使えばエラーになる。】
    • コマンドがエラーになってもスルーされる【デフォルトで。set -e とか set -opipefail を使えばエラー時に中断される。】
    • 全部のデータが文字列
      • sedawk の不思議なコードを覚えないといけない
      • 不完全なパーサーを書いて処理することになる
  • Unixシェルのいいところ
    • システムへの実用的でインタラクティブなインターフェースを提供している
    • パイプを使ってコマンドを合成することで、素早く問題を解決できる
  • TopShell
    • 純粋計算と作用(副作用)を分離する純粋関数型プログラミング
    • モダンな型システム
    • 細かいコード断片を書けば、直ちに評価され、結果を見ることができる
      • 結果はテキストのみならず、画像でもアニメーションでもよい
    • 非同期タスクとリアクティブ ストリームによるプログラムの合成
    • これらを努力なしに使うことができる環境

理屈はともかく、TopShellはブラウザから使うことができる。次のリンクからプレイグラウンドを開いて、試してみよう。

http://show.ahnfelt.net/topshell/

https://github.com/topshell-language/topshell#http-example のサンプルコードを画面左側のエディタに入力すると、直ちに評価結果が右側に表示される。といっても、副作用が発生するタスクやストリーム(バインド文 x <- e で宣言されているもの)は、行にカーソルをあわせて Ctrl + Enter で1文ずつ実行するか、実行ボタンを押すまでは実行されない。感覚的には、シェルというより Jupyter Notebook に近い気もする。

json <- Http.fetchJson {url: "https://reqres.in/api/users?page=2"}

people : List {id: Int, "first_name": String, "last_name": String, avatar: String} = 
    Json.toAny json.data

htmlImage = url -> Html.tag "img" [Html.attributes ["src" ~> url]]

peopleWithImages = people |> List.map (
    p -> {image: htmlImage p.avatar, name: p."first_name" + " " + p."last_name"}
)

peopleWithImages |> View.table

f:id:mandel59:20190902225844p:plain
HTTP example

f:id:mandel59:20190902230537p:plain
HTTP exampleの実行結果

ストリームのサンプルとして https://github.com/topshell-language/topshell#stream-example も試してみよう。この例では、時計のアニメーションが動く。

言語仕様についてはまた今度書く。

『新記号論』メモ 記号の正逆ピラミッドとOSI参照モデル

記号の正逆ピラミッドのうち、少なくとも逆ピラミッドは、OSI参照モデルのレイヤー構成と対応すると考えられる。逆ピラミッドは基底部から頂点へ向けて順にアナログ信号/デジタル信号/プログラムとなっているが、これがOSI参照モデルの第1層 物理層が逆ピラミッドのアナログ信号、第2層 データリンク層~第6層 プレゼンテーションまでがデジタル信号、第7層 アプリケーション層がプログラムと、対応している。

OSI参照モデルの規格書 ISO/IEC 7498-1:1994 は、次のリンクからダウンロードすることができる。

Information technology – Open Systems Interconnection – Basic Reference Model: The basic model

OSI参照モデルにおいて、各システムは物理層によって相互接続されている。この層は物理的な信号を流すことができるが、その信号は、減衰するしノイズも混じる。また、数えきれない機器が接続されているから、どのようにして目的の機器に情報を伝達するかという問題もある。OSI参照モデルはその問題を解決し、遠隔地の任意のプロセスどうしが相互に通信できる仕組みのモデルとなっている。

3階層の記号の(逆)ピラミッドと比べて、OSI参照モデルは7階層もあるのは、OSI参照モデルでは各レイヤーごとに通信を実現する上で求められる機能を、各層ごとに細く定義しているからだが、現実にインターネットで使われている通信技術はOSI参照モデルのように行儀よく積み上がっているわけではなく、複雑に組み合わさっているので、その点はOSI参照モデルに縛られず考えてもよい。

とにかく、機械間の通信階層を考える重要なのは、下層で取り扱うノイズが混じったり欠損したりする信号が、各通信階層の提供するエラー訂正やパケットへの分割・再送、輻輳制御といった機能によって、上層では文字列の理想的の転送として見えるようになることが大事だ。すなわち、プレゼンテーション層のレベルでは、ある機器で入力した文字列表現が、遠隔地の好きな機器に、そのままの形で、あたかもテレポートしたかのように出力される。

このようにして確立した文字列の転送の上に構成されているのがアプリケーション層の諸プロトコルで、SMTPやHTTP/1.1のように、このレイヤーの表現は、テキストを伝送する限りにおいては、人間が読んでも容易に理解できるプロトコルも多い。(マルチメディアが伝送されるようになると、話は変わってくる。)

ひとつ問題として、OSI参照モデルが機械間の通信のモデルであり、人間・機械間の通信を図示していない。人間を図に加えるとするならば、どうなるべきだろうか。人間も機器と同様に、物理層で他のシステムと接続されていることは確かだ。機器どうしは電線や無線で接続されている一方、人間は各種入出力デバイスを介して接続されている。そして人間の認知システムを通して、人間はその記号を認識する。この部分の構造は、機械とそれほど変わるようには思えない。記号の正逆ピラミッドは両ピラミッドを逆向きに置いているが、底を揃えて、並置する形で図示してもよいはずだと思う。(下のツイートで言及した、ISO/IEC 7498-1:1994の図のような感じ。)

上述したとおり、プレゼンテーション層の文字列表現は人間が読むことができる。(おそらく、そういった理由でプレゼンテーション層と呼ばれているのではないかと思う。)それは、この層の表現は、人間の思う文字列表象を、文字コード表を相互変換表として使うことで、表現できるように作られているからだ。(プレゼンテーションが表現している文字列表現は、抽象文字に対応するものであって、字形に対応するものではない。原則として、ローマン体とイタリック体の差異といった文字の形の差異は、このレイヤーでは捨象されている。)人間の思う文字列の情報が保存されているから、プレゼンテーション層の表現を(文字コード表を介して)人間がそのまま読むことができるし、その文字列の上に人間や機械が処理するアプリケーション層の言語を実装することができる。(もちろん、現在よく使われているUnicode文字列の処理を安易に容易だというのは憚られるが、Unicodeの処理が難しいというのは技術上の問題以前に、世界中の文字の多様性と複雑さが反映されているからという側面もある。)

マルチメディアを伝達する場合はどうか。例えば音声を伝達する場合は、録音装置が、環境の音をディジタル信号に変換する。それは、文字列(厳密にはオクテット列)だが、文字コード表のかわりに、サンプリング定理を介して、連続的な音声信号と結び付けられている。サンプリング定理は、標本化周波数が、元の信号の最大周波数の2倍より大きい場合に、元の信号が復元できるというものだ。実際のところ、アナログ信号をディジタル信号に変換するには、標本化の他に量子化も行うが、いずれにしろ数値に変換できてしまえば、それは通信で送ることができる。人間も、見た画像や聞いた音声を言語化して伝えることは可能だが、機械が行うそれと比べると、画像や音声の再現度は大きく落ちるだろう。(ただ、たとえば証言から似顔絵を描いて犯人を探すというようなことを考えると、犯人を特定するのに十分な情報が含まれていて、それを他人が認識できればいいわけで、機械の符号化した表現が実用上は過剰だったり、ノイズとして有害に作用する場合も考えられる。)

追記:機械の場合は、文字列表現を正確に転送するという目的から設計された層が基盤にあり、その層を活用して、その上に諸プロトコルが実装されることが多い。(絶対的に文字列表現である必要性はなく、データを細かい塊に分割したデータグラムを基礎としたプロトコルなどもある。ただ、文字列上で、人間が読めるように作られたプロトコルは、流れてくる情報を人間が見て意味が理解しやすいという利点がある。)一方で、人間の表象は機械ほど正確に転送することはできない(たとえば伝言ゲームでも徐々に変わってしまう)

位置の外延的表現・内包的表現の区別と考察ノート

オブジェクトの位置の表現方法には、大きく分けて2種類ある。ここではそれを、外延的表現と内包的表現と呼びわけ、ボードゲームの駒の位置の表現を具体例にして、どのような違いがあるかを考えてみる。

今、将棋盤上においてある、王将の位置を表したい。どのような表現が考えられるか。(王将なので、手駒や成りについては考えないこととする。)

ひとつは、将棋盤を2次元配列として用意し、駒の位置に該当する要素として、駒の識別子を代入するものが考えられる。

// コード1
let 将棋盤 = Array.from({ length: 9 }, () => Array.from({ length: 9 }, () => null))
将棋盤[8][4] = "王将"

コード1を実行した結果、 将棋盤 は次のような配列になる。駒の位置は、将棋盤配列上の特定の要素として駒を表す識別子 "王将" を格納することで表現している。このような表現方法を、位置の外延的表現と呼ぶことにする。

[ [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, null, null, null, null, null ],
  [ null, null, null, null, "王将", null, null, null, null ] ]

これは 将棋盤[8][4] == "王将" である点で次の表現と大差ないので、簡潔にこちらを使って考えてもよい。

{ "8": { "4": "王将" } }

もうひとつの方法では、将棋盤を連想配列として用意し、駒に該当する要素として、駒の座標を代入する。

// コード2
let 将棋盤 = {}
将棋盤["王将"] = [4, 8]

コード2を実行した結果、 将棋盤 は次のようなオブジェクトになる。駒の位置を座標で表現するこの方法を、位置の内包的表現と呼ぶことにする。

{ "王将": [4, 8] }

位置の外延的表現と内包的表現の違いですぐ分かるのは、配列の添字と要素の関係が入れ替わっていることだ。外延的表現では、座標が添字、識別子が要素となっている。一方、内包的表現では識別子が添字、座標が要素だ。

このことは、情報へのアクセスの容易さと関わってくる。外延的表現では、位置からオブジェクトを得ることは簡単だが、オブジェクトの位置を得るには配列をスキャンしなければならない。内包的表現ではその逆で、オブジェクトの位置はすぐ分かるが、どの位置に何があるかは、オブジェクト全体をスキャンする必要がある。両方向での参照を高速に行うために、場合によっては、索引を作る必要が出てくる。

アクセスの容易さの他に、添字の空間の違いもある。外延的表現では添字が座標なので、2次元配列を使っている。一方、内包的表現では添字が識別子であるため、連想配列を使っている。つまり、添字の空間構造次第で、使うべきコンテナデータ構造が異なってくる。配列や連想配列を言語機能として備えている言語は多いが、添字空間が広大だったり連続的であるならば、R木のような構造が必要な場合もあるだろう。

オブジェクト指向のデータ表現では、オブジェクトを基準にデータが凝集されるため、素朴に設計すると内包的表現を選びがちではないかという気がする。しかし、それは空間上の現象を上手く表現することが難しいという問題がある。空間上で隣り合ったオブジェクトに作用するといった処理を書くには、将棋盤というオブジェクトを意識し、外延的表現を使う必要が出てくる。

昔作ったリポジトリがフォークされていた話

昔、SQLiteをWebAssembly向けにビルドする例をGitHubに置いていたんだけど、 github.com

先月ごろフォークされて Uno.sqlite-wasm というリポジトリができていた。 github.com

Unoは、UWPアプリをiOSAndroid, WebAssembly上で動かすプラットフォームらしい。

github.com

デモも公開されている。

https://github.com/nventive/Uno#live-webassembly-apps

練習で作ったリポジトリだったので、ドキュメントやコメントはほぼ無かったのに、よく拾い上げてハックしたな、と思った。まあ、シンプルな構成だからドキュメントがなくても理解できるとも思う。

(当のsqlite-wasm, TypeScriptで書いた部分には微妙なこだわりを出していて、普通に型を付けたらポインタが全部number型になるのを避けるために、前回紹介したPhantom property patternを使っていたりする。)

Phantom property pattern

TypeScriptは、JavaScriptエンジンの動的セマンティクス上に、静的な型システムのセマンティクスが重なっているものです。ここで、JavaScriptとしては正しく実行できても、TypeScriptの型システム上ではちゃんと型が付かないという場合もあります。ときには、TypeScriptで型がつくように大幅に書き換えないといけない場合も出てきたりして、そうなると大変です。それでも、TypeScriptの型システムは高度な機能を持っているので、大体の場合は、うまく表現してやると、JavaScriptらしい書き方のままで型をつけることができてしまいます。今回は、JavaScriptでよくある、プリミティブ値をそのまま取り回すパターンに型を付けたいと思います。

プリミティブ型を扱うプログラミングは、素朴でわかりやすいですが、型の考えからするとかなり悪いものです。こういうプログラミングを行うと、あらゆるデータを文字列で表す様子から、ときに”stringly typed”と揶揄されます。文字列は、表したいものはとりあえず何でも表現できるのでとても便利ですが、いろいろな種類のデータが一つの型で表されてしまうのなら、型が役立たずになってしまいます。すべてがstring型のプログラムで、どうやってその文字列の中身がテキストなのか、URLなのか、メールアドレスなのかを判断するのでしょうか? 文脈を読み込んで察するか、ハンガリアン記法に回帰するか、さもなくばエスパーで当てるしかないでしょう。

この問題に対処するひとつの「まっとうな」方法は、プリミティブ値を直接使わず、適宜ラッパーオブジェクトを作り、その上でプログラミングを行うことでしょう。プリミティブ値ではなくオブジェクトを使えば、メソッドを追加することも容易です。しかし、実際にJavaScriptでそういうことが行われづらい原因として、ひとつはプリミティブ値をコンストラクタでラップするが手間だという点、ひとつは===による同一性比較がオブジェクト型に対しては工夫を加えなければ意味上の同値性比較にならない点、ひとつは性能上の問題点、ひとつはシリアライズ・デシリアライズの自明さが失われる点が挙げられます。要するに、プリミティブ値をオブジェクトで包むのはわりとデメリットもあるわけです。

今ここに、この問題へのもうひとつの対処方法、幽霊プロパティパターン phantom property pattern を提唱します。このパターンを使うと、プリミティブ値の型を好きなだけ定義できる。つまり、言語組み込みのプリミティブ値を取り回すプログラムでありながら、各プリミティブ値を別々の型として取り扱うことができるようになります。

幽霊プロパティパターンでは、まず、次のような型を定義します。

export type Tag<X, R extends keyof any, Y> = X & {
    [rel in R]: Y
}

x: Tag<X, R, Y>は「xはXで、xのRはYである」というような意味になります。

次に、幽霊シンボルを定義します。

// This is a phantom symbol, which does not exist at run time
declare const _unit: unique symbol
export type unit = typeof _unit

_unitは幽霊シンボルで、宣言しますが、実際には定義しません。_unitの型はユニークシンボル型ですが、このままでは型に名前がなく使いづらいので、unitというエイリアス名を付けます。

ここに定義したTag型コンストラクタと幽霊シンボルを組み合わせると、プリミティブ値の性質を幽霊プロパティとして表現できます。

例として、角度の単位を幽霊プロパティとして付加した型を定義してみましょう。

export type Scale<X, U> = Tag<X, unit, U>
export type Radian = Scale<number, "radian">
export type Degree = Scale<number, "degree">

ここで定義したRadian, Degreeは両方ともプリミティブ値の数値を元とする型ですが、実際には存在していない[_unit]プロパティの型が異なるため、相互に代入不可能となっています。

{
    const x: Degree = 30 as Degree
    const y: Radian = x // type error!
    console.log(x, y)
}

いいですね。これを使って、たとえば三角関数双曲線関数に単位をつけてあげるといい感じになります。

export const sin = Math.sin as (x: Radian) => number
export const tanh = Math.tanh as (x: Radian) => number
export const asin = Math.asin as (x: number) => Radian
export const atanh = Math.atanh as (x: number) => Radian

{
    const x = 30 as Degree
    const y = sin(x) // type error!
    console.log(y)
}

弧度法で渡すべきところをうっかり度数法で渡すミスを、これで防げますね。 相互に変換する関数も用意してあげましょう。

// Ref: https://tauday.com/tau-manifesto
const τ = 2 * Math.PI

/** converts radian to degree */
export function toDegree(x: Radian): Degree {
    return (x / τ * 360) as Degree
}

/** converts degree to radian */
export function toRadian(x: Degree): Radian {
    return (x / 360 * τ) as Radian
}

{
    const x = 30 as Degree
    const y = sin(toRadian(x)) // ok
    console.log(y)
}

例をもう一つ、今度はUnix時間とISO8601を扱ってみます。 今度は単位ではなくデータフォーマットの話なので、新しくdataformat型を定義しています。

declare const _dataformat: unique symbol
export type dataformat = typeof _dataformat

export type UnixTime = Tag<Unit<number, "millisecond">, dataformat, "unixtime">
export type ISODateTime = Tag<string, dataformat, "iso8601">
export const now = Date.now as () => UnixTime
export const toISODateTime = (t: UnixTime): ISODateTime => new Date(t).toISOString() as ISODateTime
export const toUnixTime = Date.parse as (d: ISODateTime) => UnixTime
export function isISODateTime(s: string): s is ISODateTime {
    return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(s)
}

{
    const t = 1514810096000 as UnixTime
    console.log(toISODateTime(t))
    const d = "2018-01-01T12:34:56Z"
    if (isISODateTime(d)) {
        console.log(toUnixTime(d))
    }
}

UnixTime型はデータフォーマットだけでなく、単位も幽霊プロパティで付加していますね。一口にUnix時間と言っても、実際にはミリ秒単位のものと秒単位のものが混ざっていることがよくあるのですが、こういう工夫が事故を防いでくれるはずです。

ISODateTime型の方は、型ガード関数を用意しました。こういう関数を用意して使うほうが、直接as ISODateTimeでダウンキャストするよりも安全です。

欠点

  • 本当ならinterface Tag<X, R, Y> extends X { [rel in R]: Y }とでも定義したいところですが、現状のTypeScriptではプリミティブ型を拡張できないため、やむなく交叉型 intersection type で表現しています。
  • 幽霊シンボルそれ自体はexportしないほうがいいでしょう。実在しない識別子なので、アクセスしても型エラーになりませんが実行時エラーになる可能性があります。型レベルで使うだけでシンボル自体はなくてもいいので、シンボルの型に名前を付けて、そちらをexportするといいです。
  • interfaceではないため、スニペットコンパイラのエラーメッセージ等で、型エイリアスが展開されて表示されてしまいます。unique symbolと表示されるだけなのは非常にまずいです。
    • f:id:mandel59:20180901034739p:plain
    • ワークアラウンドとして、幽霊シンボル自体にタグをつけるように書き換えると、なんとか分かるようになります。
      • f:id:mandel59:20180901035321p:plain
declare const _unit: unique symbol
export type unit = Tag<typeof _unit, "name", "unit">
export type Unit<X, U> = Tag<X, unit, U>

declare const _dataformat: unique symbol
export type dataformat = Tag<typeof _dataformat, "name", "dateformat">

まとめ

幽霊プロパティパターンを使うと、実行時コストに影響を与えることなく(ゼロコストで)、プリミティブ値にユーザー定義の型をつけることができるようになります。

2019/08/20追記: この記事を書いた時点では、先行事例があるということをちゃんと調査していませんでしたが、実は"Branding"と呼ばれて、それなりに使われている技法であることを知りました。(TypeScriptのソースコードでも使われています。

以下の記事では、Brandingや、その変形であるFlavoringについて解説されています。 Need Flexible Nominal Typing for TypeScript? Use Flavoring, not Branding

高校の教科書に載っている「情報」の説明が変だという話

数研出版の教科書『高等学校 社会と情報』(平成24年2月27日検定済、平成27年1月10日発行)の序編第II章では、「情報の特徴」と題して、情報とは何かとその特徴についての説明が行われているのだが、そこで行われている「情報の有無」の説明にいまいち納得が行かない。

情報の有無

16本のマッチ棒をテーブルの上に投げたとき,たいていは,図1のような乱雑な並び方になる。偶然にマッチ棒が,図2のように「SOS(または505)」の文字の形になる可能性もあるが,きわめて確率が低い。私たちが,図2を見たら,人が手で並べたと思うだろう。

この2つの状態(図1の並び方と図2の並び方)のちがい(差)は,何だろうか。私たちが一目見てわかるように,この2つには大きな差がある。そのちがい(差)が,情報である。

f:id:mandel59:20170102025120j:plain

(『高等学校 社会と情報』14ページ)

並び方の乱雑な「テーブルに投げたマッチ棒」と、並び方に秩序があるように感じられる「人が並べた?マッチ棒」とを比較し、その差が情報であるとする「説明」は、理屈ではなく人間の直感に訴える説明であるため、理屈を気にしない人間ほど納得してしまうのではないかと思うけれども、しかしこの説明は変だ。

この説明は、乱雑な図1「テーブルに投げたマッチ棒」が〈情報がない方〉で、秩序だった図2「人が並べた?マッチ棒」が〈情報がある方〉だという想定で書かれているのではないかと思う。この説明をされて、何かが読み取れる方が〈情報がある方〉、何も読み取れない方が〈情報がない方〉だと受け取るのは、普通の感覚だと思う。

しかし、考え方によっては「左の方が情報が多い」と結論づけることもできる。どういうことか? こんな遊びを考えてみよう。

この遊びは2人でやる。片方の人間が、机の上のマッチ棒の状態をできるだけ少ない言葉で説明する。もう片方の人間は説明を聞いて、元の状態を見ずに、マッチ棒の状態を言葉だけから再現する。

図1と図2、それぞれでこの遊びをやるとどうなるだろうか。図1のマッチ棒の状態は乱雑で、素直に説明する言葉が見つからない。「マッチ棒が散らばっている」と言うだけでは図1の状態を精度よく再現することはできないから、マッチ棒1本1本について、その位置と向きを細かく説明する必要がある。一方、図2のマッチ棒の状態は秩序だっている。「マッチ棒が505の形に並んでいる」という言葉だけでも、ある程度再現可能だろう。マッチ棒の向きが問題だとしても、それぞれについて上下左右で指定すればいい。

結局、図1は図2よりも説明に必要な言葉が多いので、より情報が多いと考えられる。

こういう説明もできることを考えると、図2の方が〈情報がある方〉なのは一目瞭然だとするわけにはいかないはずだ。

乱雑に並んだマッチ棒は、意味のわかるパターンを含んではいない。しかし、意味のわかるパターンを含まないというのは、情報を持たないということではない。例えば、ジャングルの木々の並びにも、人間にとって直接意味のわかるパターンは含まれていないが、しかし、ジャングルの木々の並びを覚えることで、自分が今ジャングルのどこにいるのかを知ることができるようになり、ジャングルでの生活に役立てられるだろう。件の教科書は「情報」とは「意思決定の材料になるもの」(15ページ)だという考えを載せているが、その考えで情報を捉えるとしても、ジャングルの例で分かるように、人間が意図しない自然発生の差異もまた人間の意思決定の材料となる「情報」であるはずだ。

人間の作成したコンテンツを主に取り扱っていると「情報は人間が生む」という錯覚に陥りがちだが、情報を生むのは人間だけではない。宇宙のあらゆる存在が情報源となりうるのであり、それは人間がその意味を読解できるかとは関係がない。

「What Is Functional Programming? に対する反論」を読んで考えたこと

lyrical-logical.hatenablog.com

読んでいて引っかかった部分について考えました。

mutable変数は「入力とは呼べない」?

この記事で僕が伝えたいのは、君が書くあらゆる関数には二組の入力と二組の出力があるってことだ。

間違いなく、InboxQueue の状態はこの関数の本物の入力だ。

この隠れた入出力にはちゃんとした名前があって、その名を「副作用」という

InboxQueue は、その関数スコープから参照可能な変数の一つに過ぎない。たまたまその関数の環境から触れるというだけで、入力というよりは、環境の中の mutable な変数の一つ、以上のことはないし、これを入力とは呼べない。これはプログラムの状態だ。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

問題のプログラムはこれです。

public void processNext() {
    Message message = InboxQueue.popMessage();

    if (message != null) {
        process(message);
    }
}

これはJavaのコード断片ですけど、断片だと分かりづらいので、JavaScriptでプログラム全体を書いてみることにします。

let Processor = {
  inbox: null,
  console: null,
  processNext() {
    let message = this.inbox
    if (message != null) {
      this.console = message
    }
  }
}

function main() {
  Processor.inbox = "hello"
  Processor.processNext()
  console.log(Processor.console)
}

main()

このコードのprocessNextメソッドは、元のコード断片より簡略化されているけど、議論の要点はおさえているはずです。

確かに、Processor.inboxはProcessor.processNextメソッドから参照可能なmutableプロパティですが、同時にmain関数からも参照可能です。このことが、mutableプロパティを一種の「共有メモリ」として使った2関数間の通信を可能にしています。

プロパティ経由の値の受け渡しが、「プログラムの状態」としてプログラミング言語のセマンティクス内で説明できる動作だとしても、Processor.inboxはProcessor.processNextへの入力となっていると表現して問題ないはずです。

このコード例から、次のことが言えます:

  • 関数の外部から書き換え可能で、関数の内部から参照可能なmutable変数は、関数への入力として使うことができる。(このような入力を副原因という。)
  • 関数の内部から書き換え可能で、関数の外部から参照可能なmutable変数は、関数からの出力として使うことができる。(このような出力を副作用という。)

副原因と副作用は対称的で、Processor.inboxはProcessor.processNextにとって副原因の源ですが、mainにとっては副作用の対象です。つまり、副原因があるとき、対になる副作用も現れ、大域的に見れば両者は同一の現象です。

「暗黙的に環境内の変数を参照するよりも、陽に変数を取るようにしたほうがいい」?

言いたいことは分かる。暗黙的に環境内の変数を参照するよりも、陽に変数を取るようにしたほうがいい。

public void processNext(InboxQueue inboxQueue) {
    Message message = inboxQueue.popMessage();

    if (message != null) {
        process(message);
    }
}
http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

環境に依存した関数の定義には注意すべし、それは一般に言って正しいことだと思うし、反論するつもりはない。しかしそれを「関数型プログラミングって何?」という文脈で話すのは、違和感が強い。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

ここで言っている「環境」って、何を指しているんでしょうか? グローバル環境のことでしょうか? それとも、関数の引数ではなく、ブロックスコープに宣言された変数を、環境内の変数と呼んでいるのでしょうか? あるいは、次のようなクロージャーで、関数g内で参照されているxはgの引数ではないわけですけど、こういう場合も「暗黙的に環境内の変数を参照する」に入るんでしょうか?

function f(x) {
  return function g(y) {
    return x + y
  }
}

「あらゆる関数」?

しかし * あらゆる * 関数に二つの入力がある、と記事では主張している。つまり上のコードでは不足だろう。
ならもうちょっと別の書き方をしよう。コードは疑似的なもので、こんな API がある言語は知らない。

public void processNext(Frame frame) {
    Message message = ((InboxQueue)frame.getEnvironment.getVariables.searchVariable("InboxQueue")).popMessage();

    if (message != null) {
        process(message);
    }
}

あるいは、InboxQueue の状態をある種の precondition としてとってもいいかもしれない。

public void processNext(InboxQueueState state) {
    // state を使って何かしたいときもあるかもしれない。以下は一例だ。関数の意味は変わる。元記事では関数の前提条件について何も触れられていないので、これは自分が勝手に加えたものだ。popMessage は blocking API かもしれないし、そうであればこのような前提条件は妥当でない。
    if (state.isEmpty()) throw new IllegalArgumentException("InboxQueue must has some elements.");
    // これは assert を書いて precondition をコード上で陽にしているのと大した差はない。
    assert(!state.isEmpty());

    Message message = InboxQueue.popMessage();

    if (message != null) {
        process(message);
    }
}

一歩譲って、これらをもってして、あらゆる関数には二つの入力がある、という主張を受け入れたとしよう。しかしそれは「関数プログラミングにとって」大事なことなんだろうか。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

このくだりはほとんど意味が分かりません。「関数型プログラミングって何?」の酷さを示すために、書かれたとおりにやってみて酷さを示すというにしては、そもそも書いてもいないことを突然やりだしたように見えます。「上のコードでは不足だろう」と思った理由もはっきりしなければ、それで例に出した「別の書き方」をそう書いてみたワケも、書かれていません。

“I put it to you that every function you write has two sets of inputs and two sets of outputs.” という一文中の “every” という一語が気にくわないということ以上ではないのであれば、よく分からない例なんか出さずとも、記事最初の例

public int square(int x) {
    return x * x;
}

には副原因も副作用もないよね、と指摘すれば済むのでは、と思ってしまいます。

でも、ただ「 * あらゆる * 関数に」って書き方じゃなくてevery function you writeなんだし、自分はこれは記事に食い付かせる「釣り」のたぐいの表現なんじゃないでしょうか。まあ、こういう正確さに欠けた表現はちょっと気に障るってのは分かります。

「本当にそうだろうか」?

これで、この関数は入力(や出力)を隠し持たなくなった。

本当にそうだろうか。上の主張は関数内で読んでいる関数が純粋であるという仮定が成り立つときに限る。"getSchedule" "programAt" が純粋でないなら、そこには状態が潜んでいる。関数内で呼び出している関数のスコープにある状態に触れている、依存している可能性がある。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

もう少し前に、こう書いてあります。

この関数には現在時刻 (new Date()) という入力が隠れている。

http://okapies.hateblo.jp/entry/2016/12/15/021550

"getSchedule" "programAt" が純粋でないなら、「この関数には現在時刻 (new Date()) という入力が隠れている」という部分で一緒に副作用(あるいは副原因)のある関数としてリストアップされなければ、話の構造上不自然です。ここでリストアップされていない以上、 "getSchedule" "programAt" は純粋だということが暗に示されてます。それを、陽に示されていないからといって「"getSchedule" "programAt" が純粋でないなら」と言い出すのは、揚げ足取りに見えます。

「一般的な説明でよいだろう」?

引数にのみ依存し値を返す、同じ引数に対して常に同じ値を返す関数であるという一般的な説明でよいだろう。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

「引数にのみ依存し値を返す、同じ引数に対して常に同じ値を返す関数」という定義には問題があります。この定義では、副原因を持たないが副作用を持つ、次のような関数を除外できないのでダメです。*1

let a = null

function out(x) {
  a = x
  return x
}

「読めば訳された記事がイマイチなのは分かる」?

結論。住井先生がわざわざ記事書いてくれてるんだから、まずそれを読もう。読めば訳された記事がイマイチなのは分かると思う。

http://lyrical-logical.hatenablog.com/entry/2016/12/15/135831

“A functional language actively helps you eliminate side-effects wherever possible, and tightly control them wherever it's not.”って、住井先生の「関数型言語とは、……関数型プログラミングを推奨・支援するプログラミング言語」「(関数型プログラミングとは)副作用をできるだけ(あるいは全く)用いず、式や関数で計算を表すプログラミングスタイル」ってのとほぼ同じこと言ってますよね。まあ、住井先生の記事の方はリファレンスがちゃんと張ってあるのがいいと思います。

*1:こういう説明が簡単にできるようになるので、「副作用」と「副原因」を区別するのは有意義だと思います。

游ゴシックは何故Windowsでかすれて見えるのか

この記事は2016年当時の状況を書いています。その後のバージョンアップで、Windowsでのフォントレンダリングは改善され、ガンマ補正周りのエラーはなくなっているようです。当時の状況を資料として残しておきますが、記事を参照する場合はご注意ください。

TL;DR

游ゴシック体は単に細いから薄いのではなく、ガンマ補正が2重、3重に掛かっているために、グレーが本来よりも明るくなりすぎている。ガンマ補正を逆に掛けると、正常な表示になる。

かすれた游ゴシック

Windowsでは游ゴシックがかすれて見える。細字だと薄くて読みづらいから、より太いウェイトを指定しろという話もある。(Windowsで游ゴシックが汚いのは、結局誰が悪いのか? | Cherry Pie Webなど)だが、かすれて見える原因は、ウェイトが細すぎるからではない。

例えば、本文に游ゴシックを使っているWIREDの記事(「癌」という名のドラゴン──父はゲームをつくった。死にゆく息子のために | WIRED.jp)のキャプチャ画像を見てほしい。



WIREDの記事をFirefoxで表示したもののキャプチャ

注目してほしいのは、ただ文字が薄いというわけではなく、線によって濃さが違うということだ。曲線の多い仮名に比べて、ピクセルに沿った直線の多い漢字の方が黒くみえる。

また、アプリケーションによって字のかすれ具合が違う。たとえば、次の画像は游ゴシックを同じウェイト、同じポイント数で、それぞれFirefoxとメモ帳を使って表示したものだ。



Firefoxで表示したもの



メモ帳で表示したもの

どちらも薄くかすれて見えるが、メモ帳の方がより薄く見える。

ガンマ補正

Windowsのフォントレンダリングは、ただ薄いのではなく、線によって濃さが違う。その原因は、ガンマ補正が適切に行われていないことだ。

ガンマ補正とは何かを説明するために、色の仕組みから説明する。

多くの人が知っているとおり、コンピューターでは色を数値で表したものを処理する。色を数値で表す方法には色々あるけれども、いちばん有名なのは、RGBカラーコードだろう。たとえばCSSでは、カラーコードを使って、黒はrgb(0, 0, 0)、白はrgb(255, 255, 255)とも表せる。これは、色を構成するR(赤), G(緑), B(青)の三原色それぞれの輝度を、0から255までの数値で表現したものだ。

しかし、ここから先の話はあまり知られていないことだと思う。多くの人は、rgb(255, 255, 255)の半分の明るさのグレーは、rgb(128, 128, 128)だと思っている。だけど、それは間違いだ。実は、rgb(128, 128, 128)はrgb(255, 255, 255)の2割ほどの明るさしかない暗いグレーであり、本当に半分の明るさになっているグレーはrgb(188, 188, 188)なのである。*1

これには、ブラウン管モニターの特性に関する歴史的事情が関わっているのだけれども、深くは立ち入らない。大事なのは、rgb(128, 128, 128)が半分の明るさだと思ってプログラムを書くと、グレーが暗くなってしまうということだ。だから、暗くなる分を考慮して、明るめの画像を表示しないといけない。そのような補正処理を、「ガンマ補正」と言う。

Windowsのフォントレンダリングは、ガンマ補正が2重、3重に掛かっている

おそらく、Windowsのフォントレンダリングにおいてもガンマ補正が行われているのだが、どういうわけかガンマ補正が多重に掛かっているようだ。つまり、グレーを明るくする処理を重ねたためにグレーが明るくなりすぎている。(rgb(128, 128, 128)を補正してrgb(188, 188, 188)になるはずが、rgb(223, 223, 223)やrgb(240, 240, 240)になっている。)

参考として、GIMPのレベル補正機能を使ってガンマ補正を逆向きに(グレーが暗くなるように)行った画像を載せる。指定した0.46, 0.21というのは、ディスプレイのガンマ値2.2の逆数と、その2乗だ。


20161111005506
Firefoxで表示したものにガンマ補正(0.46)を掛けたもの


20161111005650
メモ帳で表示したものにガンマ補正(0.21)を掛けたもの

字のかすれが解消され、全体が同じ濃さの文字になっているのが分かるだろうか。この結果から、ブラウザーに関してはガンマ補正が2重に、メモ帳に関しては3重に行われていると考えられる。(※このことに関して追記あり

游ゴシック細字は本来この程度の黒さで表示されるべきなのであり、ただ細すぎるから薄くて読みづらいという話ではない。太くすると読めるようになるのは、グレーの領域が少なく、ガンマ補正が適切でないのが誤魔化されるだけであり、Windowsのフォントレンダリングが適切でないことには違いない。将来的にはガンマ補正を修正して、細い字もムラなくちゃんと表示できるようになるように改善してほしいと思う。

追記

追記2

ブックマークコメントでも意見を頂きましたが、メモ帳のようなGDIでClearTypeを使ってレンダリングしているケースに関しては、ClearTypeの設定によって表示が変わるようです。自分もメイリオ初期設定から変更していたように思います。なので、3重に補正が掛かっているというのは誤解で、たまたまそう見えるガンマ値が設定されていただけであるようです。

*1:色空間がsRGBの場合

プログラムを哲学する 2. 「概念記法」

以前、言語の完全性について言及した。今回は引き続き、言語の完全性について考える。

mandel59.hateblo.jp

フレーゲの「概念記法」

フレーゲは未定義の式の存在を「言語の不完全性」(einer Unvollkommenheit der Sprache)とみなしていた。論理学者のフレーゲにとって、表現の「意味」(Bedeutung; その記号があらわしている事物。表記対象 denotation や言及対象 referent に相当すると考えられる)が確定していることは重要なことであった。表現の「意味」は、ちょうど1つでなければならず、それより多くても、少なくてもいけない。*1そのような多義的ないし無意味な表現は論理的誤謬のもととなるので、学問から排除するべきだと考えた。

フレーゲの提案した「約定」(Festsetzung)とは、そのような「言語の不完全性」を排し、言語を完全化する方法である。ある表現に「意味」が複数考えられる場合や「意味」が考えられない場合には「約定」を行うことで、あらゆる文法的に正しい表現の「意味」をただひとつに定める。簡単に言えば、「意味」がないなら(「意味」が多いなら)、「意味」を決めてやればいいというようなことだ。

数学では「√」という記号は正の平方根を表すと決めることによって、「 \sqrt{4}」が 2だけを表すとし、 {-2}をも同時に表すことを退けているが、これも一種の「約定」だと言える。「言語の不完全性」を「約定」により避け、あらゆる表現の「意味」をただひとつに確定した「論理的に完全な言語」(einer logisch vollkommenen Sprache)をフレーゲは構想し、それを「概念記法」(Begriffsschrift)と呼んだ。フレーゲは、「言語の完全性」を達成するために、「概念記法」に次の要求を行った。

論理的に完全な言語(概念記法(Begriffsschrift))に対しては、すでに導入された記号から文法的に正しい方法によって固有名として構成された表現はすべて、実際上ある対象を表示することと、いかなる記号もそれに対する意味が保証されることなしに新たに固有名として導入されることはないという二つのことが求められている。[1]

ここで挙げられている要求は、おおむね〈構成性〉と〈原子確定性〉に相当する。(これらの用語は以前 Referential Transparencyの代わりに使える概念案 - Ryusei’s Notes (a.k.a. M59のブログ) で導入したものだ。)「概念記法」を規定する「約定」すべてに〈構成性〉と〈原子確定性〉を要求することで、「概念記法」全体の「論理的な完全性」=〈確定性〉を実現できる。*2

フレーゲの「約定」は表現の「意味」を唯一に定めることに主眼があるため、個々の「約定」に論理上の根拠は存在しない。むしろ、論理上の根拠が与えられないからこそ、約定によって表現の意味を決めるというのだろう。「約定」に論理上の根拠がなくとも、その「約定」が〈構成性〉と〈原子確定性〉を満たす形で行われるのであれば、どう「約定」しようとも「概念記法」全体の〈確定性〉は保証され、矛盾が生じることはなく、論理上は問題がない。

例えば、フレーゲ解析学の記号の「不完全性」を示す例として発散級数(収束値を持たない級数)を挙げ、発散級数はは数0を表すと「約定」することによって、この「不完全性」を排除することに言及している。この数0による「約定」はあくまでも例で、根拠があると言えないが、〈構成性〉と〈原子確定性〉を満たすどのような「約定」を行っても、矛盾が生じることはない。

出典

[1] フレーゲ, ゴットロープ (1986) 「意義と意味について Über Sinn und Bedeutung」(土屋 俊 訳), 坂本百大 編『現代哲学基本論文集』 1, p. 26, 勁草書房.
ドイツ語の表現はGottlob Frege. Über Sinn und Bedeutungから適宜引用

*1:フレーゲは命題文の「意味」とは真理値だと考えたから、命題文の「意味」は必ず真か偽かのどちらか一方だということでもある。

*2:追記:厳密には、原子式の意味が決定されるためには原子式の文脈が決定される必要があるため、〈文脈確定性〉も要る。

Windows 10 導入作業

Windows 10を導入する上でやったこと

ディスプレイの色調整

液晶の白が青かぶりしている感じだったので、ディスプレイの詳細設定から色設定を行う。

開発者向け機能

更新とセキュリティから、開発者向け機能に関する設定を行う

PackageManagement

ソフトウェア開発に使う各種ツール群をコマンドラインからインストール

Git for Windows

PackageManagement+chocolateyで入れるとデフォルトで改行コードの変換が行われる設定になるのでgit config --global core.autocrlf=falseしておく。

Visual Studio Code

{
  "editor.tabSize": 2,
  "editor.renderWhitespace": true,
  "files.eol": "\n",
  "terminal.integrated.shell.windows": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
}

Suica専用自販機に見る形骸化した記号の話



この話を見た時、これはまさに「形骸化した記号」のよい例だろうと思った。ナンセンスな「電子署名」の話よりも、それが形骸であることがずっと分かりやすい。

普段から自販機を使っている大多数の日本人にとって、それがコイン投入口だということを認知するためにコインの絵や点字は必要ではなく、無意識に見過ごされた記号であったのだろう。だから、実際に困る人が現れるまでは、誰も気づくはずがなかった。

それで、教訓として得るべきなのはなんだろうか。マイノリティーへの配慮を充実させること? まあ、それはそうだろう。多様性を持つ利用者全般に対して不都合のないデザインであるか、よくテストを行うべきだと言える。アクセシビリティガイドラインを守り、マイノリティーを尊重し、適切なテストを行った後にプロダクトを出すのが理想だ。問いたいのは、理想を実現させるにはどうすればよいのかということだ。

あらゆるマイノリティーに対してアクセシビリティ上の困難がないかテストをするには多大なコストが掛かる。それは必要なコストだ、と頭で分かるとしても、感情が付いてこない。マジョリティーはマイノリティーに対する意識を高めなければならないのだけれども、それはアクセシビリティガイドラインを頭に叩き込むだけでは成しようがない。必要なのは、ルールの遵守というよりも、記号を多様な観点から見ることができる柔軟さだ。

記号の不完全さへの意識を高めるのに、笑いは重要な役割を果たしうる。私たちは記号を信用し、時に記号に裏切られる。記号の解釈には文化による差異があり、それによって起こる勘違いは、記号が引き起こす悲喜劇だ。記号はコミュニケーションのなかだちだけど、それが勘違いの種であり、常に不完全さを孕むことを認めよう。未知の存在との交流は常に勘違いの連続だ。私たちは記号と、記号が引き起こす勘違いを互いに笑いながら、少しずつ乗り越えていくしかない。異文化交流とは、寛容性を前提としなければ成し得ないと思う。