Unicode Character Database www.unicode.org
Ideographic Variation Database unicode.org
Adobe-Japan1 github.com
MJ文字情報一覧表 mojikiban.ipa.go.jp
漢字データベース kanji-database.sourceforge.net
GlyphWiki glyphwiki.org
Unicode Character Database www.unicode.org
Ideographic Variation Database unicode.org
Adobe-Japan1 github.com
MJ文字情報一覧表 mojikiban.ipa.go.jp
漢字データベース kanji-database.sourceforge.net
GlyphWiki glyphwiki.org
コンピューターのキーボードで音符を入力するとき、よくある方式だと
キー | ノート |
---|---|
Z | C3 |
S | C♯3 |
X | D3 |
D | D♯3 |
C | E3 |
V | F3 |
G | F♯3 |
B | G3 |
H | G♯3 |
N | A3 |
J | A♯3 |
M | B3 |
K | B♯3 |
, | C4 |
のように対応させていて、要はピアノの鍵盤と対応させているけど、ふつうに横は半音階で並べて
キー | ノート |
---|---|
Z | C3 |
X | C♯3 |
C | D3 |
V | D♯3 |
B | E3 |
A | F3 |
S | F♯3 |
D | G3 |
F | G♯3 |
G | A3 |
H | A♯3 |
J | B3 |
K | B♯3 |
E | C4 |
と割り当ててもいいのではないか。C4がEキーに当てられているのは、ベースの弦と同様に1段上が完全4度(5半音)になるように配置した結果で、実際にはQ, WキーにもB3, B♯3を重複して割り当てる。 こういうふうに、縦に完全4度、横に半音間隔で配列すると、左下のZキーから右上の0キーまでの音程は、3×5+9=24半音、ちょうど2オクターブになるので、割り当てに重複があっても、音域としては十分ではないかと思う。
どうやら、竈門禰豆子の禰の字について、しめすへんは正式には「ネ」の形という指定が存在しているようで、公式サイトでもわざわざフォントを変えて1、禰のしめすへんを「ネ」に変えています。中国語のフォントでは、しめすへんは常用漢字かどうかに関わらず、いつでも「ネ」の形をしているからですね。2
日本語フォント 禰
中国語フォント 禰
この、フォントを変える手法での字形変更は昔から行われていますが、中国語のフォントを使うわけなので、日本語のフォントのしめすへんとは形が少し違う問題があります。
他の方法としては、異体字セレクタと呼ばれる仕組みを使うと禰󠄁(しめすへんが「示」)と禰󠄀(しめすへんが「ネ」)は区別して出せます。この方法で変えられる字形は、日本語フォントの中で用意されている、他の字になじんだ字形を使うことができるメリットがありますし、異体字がUnicodeで表現されているので、コピー&ペーストしても、意図通りの異体字を出すことができます。3一方で、テキストに異体字セレクタという特殊な文字が含まれることになるので、それを考慮しないソフトウェアが誤った処理をする可能性もあります。
まあ、異体字セレクタまで使って字形に拘るのは個人的にはちょっとこだわりすぎに感じてしまいますが、常用漢字外の難字であればなおさら、細かい字形の差が混乱を引き起こすということもあるかもしれず、まあ難しいですね……
(ここまで書き上げたあとでスラドの WebサイトやWebサービスで異体字セレクタは使われているか | スラド IT で取り上げられていることに気づきましたが、はてなブログで異体字セレクタを使って表示する例として載せることにします。)
安倍総理の動画はただくつろいでいるだけで、歌っても踊ってもいない。コンテンツの趣旨、コンテキストを無視しているように見えるし、それが憤りに繋がっている。
しかし、いったいそんなコンテキストが本当にあったのだろうか。
星野源さんのインスタグラムには、バナナマンと大泉洋さんの動画も投稿されている。
www.instagram.com www.instagram.com
これらの動画は、何を伝えているだろうか。参加の形態は、必ずしも楽器の伴奏やコーラスやダンスに限らない、ということではないのか。俳優やお笑い芸人であり、音楽として関わることができなくとも、連帯してほしいということではないのか。これらの動画はニュースショーでも取り上げられていたし、これを見て、音楽でなくても大丈夫、というふうに思った人がいてもおかしくはない。
もちろん、個性的な大泉さんや、お笑い芸人のバナナマンだから、コンテキストを逸脱することが許されるのかもしれないし、星野源自身が投稿した動画であるから、彼らの動画が炎上するはずもない。一方で、一国の総理大臣が(追記:現状あまり職務を全うしていないと見なされている状況で)、彼らのように歌でも踊りでもない逸脱的メッセージを発することは、やはり問題があったのだとは思う。端的に言えば、安倍総理の動画はスベっていて、ムーブメントに水を差す結果になってしまった。
インターネットの時代、私たちは皆が皆異なるものを見ていて、コンテキストはほぼ共有されていない。この世界では、コンテキストを置き去りにして、メッセージだけが転送されてゆく。メッセージは原理的にコンテキストから乖離していく。この時代、正気でコミュニケーションをとろうとするのであれば、異文化への原理的無理解を理解し、ディスリスペクトという現象をリスペクトするべきだ。不快感に正直でありつつ、自分と異なる存在を理性的に認めるべきだ。そうでなければ、排外主義に堕してしまう。文化摩擦は、絶望というよりもチャンスであり、そういう時こそ感性を研ぎ澄まし、失われたコンテキストを集め、現象を見極めようとするべきだ。創造的な、あるいは破壊的な他者へのリスペクトが行われなければ、私たちは永遠にリスペクトされないだろう。
新型コロナウイルスの問題で、専門家が使う「オーバーシュート」という言葉の意味が問題になっていた。
疫学でのオーバーシュートという言葉がどういう風に使われるのだろうと思ってさっと検索したが、https://t.co/LZMpKsJuCL の絵がでてきた。これは語感と一致している。論文もいくつかみたが、この絵でいいように思った。言葉には、色々な「文化」(じゃーごん)があるのは僕らも経験しているが。。
— 佐々真一 (@sasa3341) 2020年3月20日
興味を持ったので、自分でも感染症の数理モデルを作り(グラフは参考にしたが、計算するための具体的な数式は見ていない。集団免疫率の定義を見たぐらいだ。)、シミュレーションを行ってみたところ、同様のグラフを得ることができた。ここに、簡単に説明をしておこうと思う。
留意点として、私は疫学の専門家ではないので、以下で使われている用語は疫学で使っているものと異なっているかもしれない。
再生産数は感染者1人がウイルスを伝染させうる人数のこと。ウイルスの性質で決まる、本来的な再生産数を基本再生産数といい、コロナウイルスではだいたい2~3ぐらいであると考えられているらしい。
1度感染した人間は免疫を獲得するとすれば、ワクチンが開発されていない限り、延べ感染者数=免疫獲得者数となる。免疫を獲得した人間にはもうウイルスが感染しないことから、次のような漸化式を立てて、感染者数の変化を考えることができる。(直接、感染者数や延べ感染者数=免疫獲得者数を扱うのは面倒があるので、それぞれ人口で割って、感染者率・延べ感染者数率=免疫獲得者率とする。)
この式を眺めれば分かるが、感染者率はが1を超えていればどんどん増えるし、1を下回ればどんどん減っていく。その境界となる免疫獲得者率の値を、集団免疫率という。
感染者率' = 感染者率 * 基本再生算数 * (1 - 免疫獲得者率)
— Ryusei (@mandel59) 2020年3月24日
免疫獲得者率' = 免疫獲得者率 + 感染者率'
と置くと、基本再生算数 * (1 - 免疫獲得者率) ≦ 1 のとき、すなわち 免疫獲得者率 ≧ 1 - (1/基本再生算数) なら感染者は増えない。この閾値が集団免疫率。
式変形すれば
これらの式から、免疫獲得者率=延べ感染者率が集団免疫率を下回っている間は、感染者は増え続け、延べ感染者率が集団免疫率を上回ったところで、ようやく感染者数が減少に転じるということがわかる。(わからなければ、シミュレーション結果のグラフを見て確認してほしい。上掲のグラフは基本再生産数が2の場合のシミュレーションで、集団免疫率は50%だ。免疫獲得率が50%のところで、感染者数が減少に転じている。)何もしなければ、社会の人口のうち集団免疫率の分の人間がウイルスに感染するまで、ウイルスの勢力は強まり続けるという、避けがたい運命が示されている。ウイルスの封じ込めができなくなった以上、どんな政策を行おうと、いずれ延べ感染者数は、人口の50%~66%に至るのだ。たとえ、新型コロナウイルスの致死率が数%程度だったとしても、大量の人間が死ぬことになる。
もはや、延べ感染者数の人口比が集団免疫率に至ることが避けられないのであれば、私たちは速やかに集団免疫率を目指して、感染するようにすべきなのだろうか?いや、違う。何もしなければ、集団免疫率を大幅に超えて感染者が発生してしまうことになるのだ。
集団が集団免疫を獲得した時点、すなわち、延べ感染者率が集団免疫率に達し、感染者数が減少に転じた時点で、自然と流行に終息に向かうというのは正しい。しかし、流行が終息に向かうというのは、直ちに終息するわけではなく、感染者数が0になるまでは、あらたな感染者が出続ける。シミュレーションでは延べ感染者率は約87%に至っており、集団免疫率とは大きな差が生じている。これこそが、「オーバーシュート」と呼ぶべき現象だ。
しかし、オーバーシュートを回避する方法はある。実効再生産数(感染者が実際に感染を広げる人数)を下げれば良い。つまり、衛生管理を徹底する、外出を避ける等の施策によって、人為的に実効再生産数を下げることができれば、延べ感染者率はより低い値に収束し、その時点で感染者数がほぼ0になる。延べ感染者率が集団免疫率に至った時、感染者がほぼ0である状況であれば、ここから元の生活に戻り、実効再生産数が上昇したとしても、免疫獲得者の割合が多く実効再生産数は1を下回るのでウイルスの流行はもはや拡大しない。(延べ感染者率が集団免疫率を下回った状態ではいけない。延べ感染者率が集団免疫率より少ない状況で、実効再生産数が基本再生産数に戻れば延べ感染者率が集団免疫率より少ない状況では、実効再生産数が1以上に戻りうるので、そこからまた流行が拡大することになる。)
実効再生産数を下げれば、感染者数の最大値も下がる。医療のキャパシティが有限である以上、このことも施策上重要な要素ではあるのだが、感染者数が医療のキャパシティを超えることをオーバーシュートと呼ぶわけではない。オーバーシュートは、延べ感染者数の割合が集団免疫率を超えて増えすぎてしまうことを指すのだ。延べ感染者数×死亡率=総死者数なのだから、延べ感染者数を最小化することは非常に重要だ。
以上が、是非とも実効再生産数を下げる政策を積極的に行わなければならない理屈だ。しかし、このような理屈をまともに書いている記事はほとんどない。医療従事者さえ、この理屈を正しく理解できていないから、オーバーシュートを誤用しているのだろうと思う。これは問題だ。自分でシミュレーションをやってみれば、上の理屈は容易に理解できるはずだ。
ウイルス感染シミュレーターといっても、Google Sheetsで簡単な数式を並べてグラフを作成するだけの、やり方を知っていれば誰でも作れるものだ。実際に自分でも、シミュレーションをして、パラメーターを変えて感染状況の変化を確認してみてほしい。
追記:
SIRモデルというのが出てきた https://t.co/lACyw8Dg4t
— 鷹羽さん (@selvaggio) 2020年3月25日
追記2 (2020-03-27): 上の記述で、「実効再生産数」が適切に使われていなかったので、訂正し、より正確な表現にした。
このツイートが目に触れた。これは問題だ。
lain、商用含む二次創作フリーになったから記念配信で急に岩倉玲音が凸してきてもいいのか
— 月ノ美兎🐰 (@MitoTsukino) 2020年3月17日
リプライにぶら下がっているリンクから、<利用ガイドライン>に飛ぶことができた。
本日より放送開始30周年となる2028年7月6日までの間において、以下の「利用ガイドライン」と「利用規約」の両方に同意いただくことを条件に、日本国内に居住されている個人の方に限り、アニメーション作品「serial experiments lain」(以下「本作品」といいます)の二次創作の利用を商用・非商用にかかわらず無償で許諾します。監修を受ける必要もありません。
また、個人の集合体となるファン・コミュニティによるOpen Source Projectであれば法人格を有しない限り、同様に許諾します。
Open Source という言葉は、1998年に生まれた。パーソナル・コンピュータが大衆化し、人々の相互接続性が次第に高まっていく、そういう時代だ。奇しくも、あるいは必然か、serial experiments lainが発生した年でもある。(私はそれが存在することを知っているが、触れたことはない。)
Open Source は、概念だ。概念はとらえどころのない存在だが、言葉で定義されることで、社会全体に客観的に理解できる、確固としたものとなる。Open Sourceは、Open Source Initiative によって定義されていて、次のページで定義を見ることができる。
日本語で読みたい人は、次の八田真行訳を読んでもいい。
第5条には、こういう定義がある。
No Discrimination Against Persons or Groups The license must not discriminate against any person or group of persons.
個人やグループに対する差別の禁止 ライセンスは特定の個人やグループを差別してはなりません。
ライセンスに「法人格を有しない」という条件を課すことは、当然、差別にあたる。これは、私には(おそらく、他のオープンソースソフトウェア・コミュニティーの人間にとっても)明らかに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を一括でフォローする仕組みがあれば、作成された転置インデックスを他のインスタンスに転送することもできる。もちろん、興味がある概念だけをフォローしてもよい。
個人は特定のインスタンスにしか存在しないが、概念はあらゆるインスタンスに存在する。そこで、上の考えの発展として、概念のフォローは、インスタンスを特定しない形式によるフォローができることが望まれる。インスタンスを特定せず、すべてのインスタンスの特定の概念をフォローできるようにする、あるいは、概念のフォローに関する情報を、連合インスタンス全体に伝播させる仕組みがあれば、わたしたちは、透過的に、純粋な概念をフォローすることができる。
人間やボットを介せず、純粋な概念によって構成されたタイムラインを、わたしは見てみたい。
GitHubのExplore repositoriesにたまたま表示されていた TopShell が気になったので、ここで紹介する。
TopShell開発の動機は TopShell: Reimagined Terminal and Shell · topshell-language/topshell Wiki · GitHub に書いてあるが、要点をまとめると「古典的なUnixシェルを使うのはつらい。いいところだけを抜き出して、全くシェルを考えたら、どうなるだろうか?」ということらしい。
set -u
を使えばエラーになる。】set -e
とか set -opipefail
を使えばエラー時に中断される。】sed
や awk
の不思議なコードを覚えないといけない理屈はともかく、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
ストリームのサンプルとして https://github.com/topshell-language/topshell#stream-example も試してみよう。この例では、時計のアニメーションが動く。
言語仕様についてはまた今度書く。
記号の正逆ピラミッドのうち、少なくとも逆ピラミッドは、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の図のような感じ。)
ISO/IEC standard 7498-1:1994 に載っている図。OSI参照モデルに言及するときは、この図をちゃんと載せるべきと思うんだけど、省略されている気がする。Wikipediaも載せていない。 pic.twitter.com/Yg9bYt7683
— Ryusei (@mandel59) March 9, 2019
上述したとおり、プレゼンテーション層の文字列表現は人間が読むことができる。(おそらく、そういった理由でプレゼンテーション層と呼ばれているのではないかと思う。)それは、この層の表現は、人間の思う文字列表象を、文字コード表を相互変換表として使うことで、表現できるように作られているからだ。(プレゼンテーションが表現している文字列表現は、抽象文字に対応するものであって、字形に対応するものではない。原則として、ローマン体とイタリック体の差異といった文字の形の差異は、このレイヤーでは捨象されている。)人間の思う文字列の情報が保存されているから、プレゼンテーション層の表現を(文字コード表を介して)人間がそのまま読むことができるし、その文字列の上に人間や機械が処理するアプリケーション層の言語を実装することができる。(もちろん、現在よく使われている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アプリをiOSやAndroid, WebAssembly上で動かすプラットフォームらしい。
デモも公開されている。
- The SQLite + Entity Framework Core App, a demo of the combination of Roslyn, Entity Framework Core, SQLite and the Uno Platform to manipulate an in-browser database.
練習で作ったリポジトリだったので、ドキュメントやコメントはほぼ無かったのに、よく拾い上げてハックしたな、と思った。まあ、シンプルな構成だからドキュメントがなくても理解できるとも思う。
(当のsqlite-wasm, TypeScriptで書いた部分には微妙なこだわりを出していて、普通に型を付けたらポインタが全部number型になるのを避けるために、前回紹介した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 で表現しています。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つには大きな差がある。そのちがい(差)が,情報である。
(『高等学校 社会と情報』14ページ)
並び方の乱雑な「テーブルに投げたマッチ棒」と、並び方に秩序があるように感じられる「人が並べた?マッチ棒」とを比較し、その差が情報であるとする「説明」は、理屈ではなく人間の直感に訴える説明であるため、理屈を気にしない人間ほど納得してしまうのではないかと思うけれども、しかしこの説明は変だ。
この説明は、乱雑な図1「テーブルに投げたマッチ棒」が〈情報がない方〉で、秩序だった図2「人が並べた?マッチ棒」が〈情報がある方〉だという想定で書かれているのではないかと思う。この説明をされて、何かが読み取れる方が〈情報がある方〉、何も読み取れない方が〈情報がない方〉だと受け取るのは、普通の感覚だと思う。
しかし、考え方によっては「左の方が情報が多い」と結論づけることもできる。どういうことか? こんな遊びを考えてみよう。
この遊びは2人でやる。片方の人間が、机の上のマッチ棒の状態をできるだけ少ない言葉で説明する。もう片方の人間は説明を聞いて、元の状態を見ずに、マッチ棒の状態を言葉だけから再現する。
図1と図2、それぞれでこの遊びをやるとどうなるだろうか。図1のマッチ棒の状態は乱雑で、素直に説明する言葉が見つからない。「マッチ棒が散らばっている」と言うだけでは図1の状態を精度よく再現することはできないから、マッチ棒1本1本について、その位置と向きを細かく説明する必要がある。一方、図2のマッチ棒の状態は秩序だっている。「マッチ棒が505の形に並んでいる」という言葉だけでも、ある程度再現可能だろう。マッチ棒の向きが問題だとしても、それぞれについて上下左右で指定すればいい。
結局、図1は図2よりも説明に必要な言葉が多いので、より情報が多いと考えられる。
こういう説明もできることを考えると、図2の方が〈情報がある方〉なのは一目瞭然だとするわけにはいかないはずだ。
乱雑に並んだマッチ棒は、意味のわかるパターンを含んではいない。しかし、意味のわかるパターンを含まないというのは、情報を持たないということではない。例えば、ジャングルの木々の並びにも、人間にとって直接意味のわかるパターンは含まれていないが、しかし、ジャングルの木々の並びを覚えることで、自分が今ジャングルのどこにいるのかを知ることができるようになり、ジャングルでの生活に役立てられるだろう。件の教科書は「情報」とは「意思決定の材料になるもの」(15ページ)だという考えを載せているが、その考えで情報を捉えるとしても、ジャングルの例で分かるように、人間が意図しない自然発生の差異もまた人間の意思決定の材料となる「情報」であるはずだ。
人間の作成したコンテンツを主に取り扱っていると「情報は人間が生む」という錯覚に陥りがちだが、情報を生むのは人間だけではない。宇宙のあらゆる存在が情報源となりうるのであり、それは人間がその意味を読解できるかとは関係がない。
lyrical-logical.hatenablog.com
読んでいて引っかかった部分について考えました。
この記事で僕が伝えたいのは、君が書くあらゆる関数には二組の入力と二組の出力があるってことだ。
間違いなく、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への入力となっていると表現して問題ないはずです。
このコード例から、次のことが言えます:
副原因と副作用は対称的で、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:こういう説明が簡単にできるようになるので、「副作用」と「副原因」を区別するのは有意義だと思います。