2019-02-15

スノーボード4回目

前回、とうとうベーシックターンができるようになった私には自信がついてきた。来週末、仲間内で滑りに行くが、これならば大丈夫だろう。しかしレッスンの回数券も買ったし、来週末まではまだ10日間ほどあるので、筋肉が回復した2日後の14日木曜日に再び日帰りでガーラ湯沢に行った。レッスンも回数券を買ったことだし、消費するためにも行かない理由はない。

そろそろ日帰りガーラ湯沢の感覚もつかめてきたので、今回の新幹線の時刻は最適なとり方をした。事前に早寝をすることにより05:00には起床し、6:44分東京駅発のMAXたにがわに乗る。帰りは17:01だ。ガーラ湯沢のリフトは16:30まで動いており、ゲレンデ自体は17:00まで開いているのだが、体力的には16:00で限界になるので、最後まで粘る必要はない。それに、平日の上越新幹線ガーラ湯沢発東京行は、17:01と17:50と19:08だけなのだ。たかが600メートルぐらいしか離れていない越後湯沢駅まで出ればもう少し細かいダイヤになっているのだが、それも面倒だ。そこで、16時過ぎに切り上げて17:01の新幹線に乗ることにした。

この日はとても寒かった。その分雪質はよく、到着直後の斜面はよい滑り心地だった。

ところでガーラ湯沢にはエンターテイメントという名前の初・中級者向けのコースがある。このコースは最初と最後の傾斜がきつくなっていて、安定してベーシックターンすることができない。できることはできるのだが、ヒザと足首に強い負荷がかかるため、連続したターンを維持できない。これは私の筋肉と関節が弱いと言うより、私の動きに問題があるに違いない。今日中に向上させたいものだ。ただし、明らかに上達はしている。逆エッジで転ぶことはほとんどなくなったので、転ぶときは予測ができる上、十分な減速ができるので、辛い転び方はしなくなった。

午前のレッスンではターンを上達させるために、ターンの各要素を分解して練習した。体重を前足にかけてターン、トーションだけでターン、エッジだけでターン、視線を適切にしてターン。頭と体と重心が正しく板の上に乗っていることを確かめるために軽くジャンプ、といった練習をした。

午前中の練習を踏まえて再びエンターテイメントに挑んでみたところ、多少マシになった感じはあるのだが、まだヒザと足首に削岩機でも使っているかのような強い負荷がかかる。

午後になって、再びレッスンの集合場所に向かった。ガーラ湯沢のスノーボードのレッスンは、初めて、初級、中級、上級に分かれている。初級はサイドスリップができること、中級は連続ターンができること、上級はどんな斜面でも連続ターンができること、という条件になっている。はたして今シーズン中に上級に上がれるのだろうか。

レッスン開始時間になったが、中級には私一人しかいなかった。なんと、これでは実質プライベートレッスンではないか。平日に来てよかった。

午後のレッスンでは緩斜面で荷重と抜重の訓練をした。インストラクターいわく「トーションなどの細かいことはひとまず忘れろ、荷重と抜重がしっかりできていればターンはできる」。いままで荷重と抜重はちょっとしゃがむ、ちょっと立ち上がる程度のヒントしかもらっていなかった。緩斜面ではそれで十分だった。そもそも、緩斜面では荷重と抜重をそれほど行わなくても簡単にターンができる。

しかしそれでは緩斜面でしかターンができない。私は腰を限界まで落としてしゃがみ、ターン開始時に前向きに斜めに立ち上がり、ターン終了時に再び腰を限界までしゃがむという、とても大げさな荷重と抜重の動きを訓練した。そしてとうとう、初めてレッスンで緩斜面ではないコース、エンターテイメントに向かった。

なんと、荷重と抜重をしっかりするだけで、斜面の傾斜が厳しくなってもしっかりと安定してターンをすることができた。しかもヒザと足首への負荷が消えた。スクワットをする負荷だけでターンができるようになった。

プライベートレッスンの効率は素晴らしい。プライベートレッスンはグループレッスンの3倍ぐらいの値段だが、この効率を考えると、今シーズン中にプライベートレッスンを頼むのも検討に値する。

インストラクターから、だいぶ動きがよくなった。後は反復演練するだけだと助言を受けて午後のレッスンが終わった。ちょうど来週はみんなでスキーに行く。はじめてレッスンではないスノーボードに挑戦するいい機会だ。

2019-02-13

スノーボード3回目

11日にガーラ湯沢まで日帰りスノーボードに行ってきた。今回はだいぶ寒く雪も降っていた。少し早めに着いたのでレッスン前に一滑りしたところ、あまりの寒さと横から打ち付けるが顔に張り付く問題のためにつらい思いをしながら滑り降り、直ちに売店でフェイスガード付きのフードを購入した。

レッスンはまだ連続ターンができないので初級だ。ターン自体はできるようになったのだが、連続できない。トゥエッジ側のターンをした後、不安定になり続かない。

今回の午前のレッスンでは、斜面にトゥエッジをかけてジャンプしてあがるトレーニングを学んだ後、ベーシックターンを学んだ。どうやらターンをするときに立ち上がることでうまくバランスを取ることができるようだ。どうやら立ち上がり系だのあるいはその逆の抱え込み系だの、荷重とか抜重といった用語が関係するそうだが、まだそのへんはよくわからない。後日スノーボードのそのへんの力学を解説した本を買って調べようと思う。

初めてベーシックターンが決まった時は爽快だった。レッスンを忘れてそのまま下まで連続でターンしながら滑ってしまった。

結果として、スノーボードを始めてから累計10時間ほど滑ったところでベーシックターンができるようになった。不思議なことに、一度感覚を掴んでしまうと大げさに屈伸をしなくてもターンができるようになった。

ベーシックターンはできるようになったが、まだトゥエッジ側のターンが安定しない。午後のレッスンではトゥエッジ側のターンを安定させるために、重心を前足に置くことや、トゥエッジのサイドスリップでできる限りゆっくりと滑り降りることや、直滑降でスピードを出した状態からトゥエッジのサイドスリップに切り替えて速度を落とさず一定距離を滑ること、はてはトゥエッジで一回展するなど、筋トレのような疲れる訓練をした。

レッスンが終わり、疲れたが最後に一滑りだけしようと初心者用の林間コースに行ったところ、地獄を見た。まずコースの幅が狭すぎてターンが難しい上に、あまりにも傾斜がなさすぎる。幅が足りずにターンしきれずブレーキをかけて止まってしまった後に、傾斜が緩すぎるために直滑降に戻しても滑らないという地獄を味わった。

どうやら初心者用の緩斜面の林間コースというのは、スキー初心者にとって都合がいいコースであり、スノーボード初心者にとってはつらいコースのようだ。

このような林間コースをスノーボードで滑るには、ターンの幅を短くする必要があるようだ。まだまだ学ぶことが多い。

ところでスノーボード用語について思うことがある。どうもスノーボード用語があまり統一されているように思えない。スキーは歴史のあるスポーツだ。日本はドイツからスキーを学んだので、ボーゲンといい、最近はあまり言わなくなったがパラレルクリスチャニアといい、ドイツ語由来の用語が多い。一方スノーボードは1980年台にようやく商業的な板が販売され始めたほど歴史の浅いスポーツだ。サーフィンやスケートボードのようなトリックを決める文化から強く影響を受けていて、オーリーやノーリーといったスケートボード用語が輸入されたりしている。

「木の葉落とし」という用語がどこから来たのかよくわからない。これは第二次世界大戦中に日本のゼロ戦がよく行っていたとされているマニューバの名称であるが、関係があるのかどうかはわからない。英語では振り子(Pendulum)というようだ。

ベーシックターンはそのままbasic turnだが、どうも立ち上がり系に対する抱え込み系のターンのことを、英語ではdynamic skidded turnというらしい。

インストラクターから何を学びたいかと聞かれて、今回のレッスンで一緒になった中国人は、「バターをやりたい」と言っていた。インストラクターは「バター」が何を意味するのか知らなかったが、おそらくプレス系のグラトリであろうと推測していた。調べたところ、どうやらバターというのはノーズプレスかテールプレスした上でスピンするトリックのようだ。

グラトリという用語もよくわからない。グラウンドトリックが4音節を好む日本風に省略されたのであろう。

次の目標はミドルターンとショートターンだ。その次の目標はおそらくカービングターンだろうか。

2019-02-09

スノーボード2回目

スノーボード初体験の興奮冷めやらぬ3日後、有給を取ってガーラ湯沢に日帰りスキーに行った。今回は旅行会社のツアーを申し込んだので、往復の新幹線とリフト券で12100円だった。他にロッカー代が1000円かかるので、最低でも13100円はかかることになる。

前回の最初のスノーボードでは怪我はなかったのだが、かえって寝ようとすると首に違和感を感じた。朝起きると首の側面に痛みがある。そんなところは打っていないはずだ。しかし、この痛みはどうも筋肉痛のような痛みだ。調べてみると、転んだときに頭を守るために瞬時に顎を引き頭を起こす動作をするために、その動きをする筋肉である胸鎖乳突筋が損傷した可能性が高そうだ。1日たった夜、筋肉痛はさらに激しくなり、仰向けのまま首だけを使って顔を起こすのが辛いほどであったが、2日目にはだいぶ楽になり、3日目にはかなりよくなった。

もうひとつは、尾てい骨の痛みがあった。これは翌日には完全に収まっていたが、気になるところだ。

怪我こそなかったものの、スノーボードはとても良く転ぶということがわかった。しかもスキーと違い、予測不可能なときに前後に転倒する。スキーでは転倒する時はだいたい事前に予測可能であり、受け身を取る準備ができる。しかも転ぶのはたいてい側面からだ。スノーボードでは逆エッジにより不意に転ぶ。しかも前後に転ぶので頭を打ちやすい。

これはあまりにも危険なので、プロテクターを買うことにした。

まずヘルメットだ。神保町から小川町にかけての通りの店をハシゴしたところ、スキーとスノーボード用のヘルメットは自転車用ヘルメットと同じ発泡スチロールとプラスチックで通気用の穴があいている作りであった。自転車用との違いは、通気穴に雪が入り込まないような設計になっている。

そして、高級品にはMIPSというコンピューターアーキテクチャーと同名の機構が使われている。これはヘルメットがアウターとインナーの二層構造になっていて、衝撃時に二層がずれることによって衝撃を分散させる仕組みだそうだ。GIROのEmerge MIPSというヘルメットを買った。

尻プロテクターは数千円の安いものと、BURTONの高いものがあった。どちらもパンツのように履いて装着するものだ。BURTONの高い製品は薄くて動きやすそうであったので、BURTONを買った。

これで終わりにするつもりだったのだが、手首とひじとひざのプロテクターも買った。手首はグローブの上からつけるもの、ひじとひざは筒状のインナーでスリーブやタイツのように装着する。

もうひとつ、上半身にインナーとして着込む胸部と背中のプロテクターがあるのだが、これはトリックを決めるときには必要だろうが、まだそのレベルには達していないので必要がないと判断して見送った。

さて、早速ガーラ湯沢で滑ってきたが、やはりプロテクターは正解だった。何度も尻だけで全衝撃を支えるような悪い転び方をしたのだが、尾てい骨は全く痛くならなかった。ひじとひざのプロテクターも手以外で積極的に受け身を取りに行くことができるのでよい。うっかり手を先についてしまっても手首のプロテクターによって手首を痛めることはなかった。途中、インストラクターから、プロテクターを付けているので大丈夫だろうとターンの練習として360度スピンを提案され、挑んで当然派手に転んだのだが、プロテクターのおかげで何の問題もなかった。また、プロテクターのせいで動きが制限されることもなかった。

さて、2回目のスノーボードでは朝早く出発し、午前、午後ともにレッスンを受けた。今回は、ヒールエッジ、トゥエッジ両方でのブレーキとターンを学んだ。トゥエッジのターンは難しく、まだ緩斜面でしか安定して成功しない。そして連続してターンすると安定しない。ガーラ湯沢のレッスンは、はじめて、初級、中級、上級と別れているが、中級に行くためには連続ターンができなければならない。見ていると簡単に思えるのだが実際に行うのは難しい。

インストラクターが言うには、私は足元を見る癖があるらしい。足元を見るとバランスが崩れるし、第一足元を見ても意味がない。というのも回避すべき対象は足元にはなく、足元に回避すべき対象が見えたところですでに手遅れで回避不可能だからだ。

スキーとスノーボードを比較すると、スキーは入門が簡単で上達が難しい。スノーボードは入門が難しく上達は簡単だそうだ。スキーはボーゲンですぐに滑ることができるし、ターンも難しくない。スノーボードはサイドスリップの習得が難しく、かつ後ろ向きに滑ることができなければターンすらままならない。

週末もスノーボードに行くつもりだ。

2019-02-04

スノーボード1回目

突然、スノーボードがしたいと思い立った。去年はスキーをしたのでにわかにスキー欲を出し、一時はスキー用具一式を買い揃えようと思ったこともあったが、ウィンタースポーツシーズンも末期の3月末にスキーをしたこともあって、そのままスキー欲はしぼんでしまった。今年は2月中に仲間内でスキーに行こうという話があったので、ふと、スキーではなくスノーボードも体験してみたいと思い立った。どうやら、都内から日帰りできるスキー場はいくつもあるようだ。

スキーやスノーボードは手軽にできる。手ぶらでカネだけ持ってスキー場に行って道具をすべて現地でレンタルすればいいだけだ。しかしレンタルというのも結構高い。スキーやスノーボード一式とウェアで1万円ぐらいはかかる。そんなにかかるのであればいっそのこと買ってしまうというのはどうだろうか。スキーやスノーボードの道具は安いものならば数万円で手に入るし、ウェアも数万円で手に入る。ということは6回から10回ぐらいレンタルするならば買ってしまっても損はないはずだ。特にウェアはスキーでもスノーボードでも使い回せる上、スキーやスノーボードと違って錆びることもないし手入れも楽で長期間使える。

そんなわけで土曜日に神保町から小川町にかけてのやたらとスポーツ洋品店が密集している場所に降り立った。なぜこんなところに古本屋やスポーツ用品店が密集しているかと言うと、この辺は学生街だったからだそうだ。

何軒かの店を冷かした末にとうとうスノーボード用具を一式買ってしまった。もっと安いものもあったし、もとよりスノーボード道具の質を判断する知識もないのだが、最安値よりは少し高い道具を買ってしまった。

スノーボードでやや想定していた予算を上回ったのでスキーウェアは安くしたいところだが、ここにも沼があった。ゴアテックスだ。過去の少ないスキーの経験から、スキーウェアはとても蒸れるということがわかっていた。しかしゴアテックス素材ならば蒸れにくいはずだ。しかしどの店でもゴアテックス素材のウェアは値段が倍以上に高い。一方で蒸れるとやる気を失う。結果としてゴアテックス素材のウェアを買ってしまった。

さて、道具はすべて買った。スノーボード板とブーツとウェアを入れる専用のリュックまで買ってしまった。これでいつでもスノーボードに行ける。そう、明日の日曜日でも。

都内から日帰りで行けるスキー場でも、旅行会社を経由して交通とリフト券を一括して入手すればいくらか安くなる。そういう事情もあるが、本当に都内から日帰りでスキー場に行けるのか試すためにも、何の計画もたてず、突発的に道具を購入した翌日に電撃的にスキー場に突撃することにした。一人で行こうと思っていたが、同じくスノーボードを一度もしたことがない妻もこの無計画を気に入り、同行するという。

我々の当初の計画では、9時頃にガーラ湯沢に到着し、午前中に現地でスノーボードレッスンを受け、午後は自由に滑るというものであった。

かくして我々は日曜日の朝7時に家を出て、9時過ぎにガーラ湯沢に到着した。ガーラ湯沢を利用したことは一度もなくて勝手がわからなかったため、リフト券の購入、着替え、レンタル手続きに手間取り、残念ながら午前のスノーボードレッスンの受付は逃してしまった。

午後のレッスンまでは時間がある。特に妻は「レッスン費用は甚だ高し。我は体で覚える派なり」と蛮族のような思考の持ち主である。そこで我々は前日にインターネット上のスノーボードの滑り方の解説を読んだ付け焼き刃の知識を元に、入口近くの訓練用の浅い傾斜角の斜面で訓練を試みたが、一向に要領を得ない。少なくともスキーはこうではなかった。スキーは未経験でも、もう少しはマシな動きができたはずだ。スキーの知識はスノーボードでは何の役にも立たないようだ。

そのような危うい状態で、蛮族系の妻は「レッスンは午後たり、遅し遅し。我が夫何ぞリフトに乗って初心者用のコースを滑らざる」と提案した。我々は苦労しながらリフトに乗り、つかの間の雪山の眺めを堪能し、そして死ぬ思いをしながらリフトから降りた。スキーはこうではなかった。スキーでリフトから降りるのに苦労したのは本当に最初の一回目だけであった。

ガーラ湯沢の初心者用のリフトには2つのコースがある。リフトの横を真っ直ぐ滑り降りるコースと、リフト降り場から奥に行きカーブして戻るコースだ。私は前者のコースは最も短いので最も簡単であろうと主張したが、妻は後者のコースのほうがより簡単であると主張した。その理由は、後者のコースは特に初心者用のコースであると看板が出ているのみならず、傾斜角も前者より浅いというものだ。なるほど一理ある。スキー経験者である我々には当然の理のように思われたので、我々は後者のコースに挑んだ。スノーボードを知る読者はこれから我々の経験する過酷な運命についてすでに予想がつくだろう。

我々は数メートルおきに転びながら、とても傾斜角の浅い初心者用コースを長時間かけて転がり落ちた。スノーボードの完全な初心者は、むしろ傾斜角の極めて浅い場所の方がつらいのだ。というのも、サイドスリップで進みにくいからである。

ほうほうの体でからくも初心者用のコースから生還した我々は、午後のレッスン開始を待つのが懸命であると判断した。しかし、果たしてこの何もできない状況から2時間ばかりレッスンをしたところで何を学べるというのだろうか。状況は絶望的なように思われ、早くもスノーボードの購入を後悔し始めていた。

時間になり、未経験者用のレッスンが始まった。我々はシューズの履き方、スノーボードの取り付け方、片足で平地を移動する方法を学び、初心者用コースのリフトに乗った。そして再び死ぬ思いをしてリフトから降りた。

レッスンでは、前者のコースを使った。傾斜角がやや深い方のコースだ。スノーボードでは傾斜角が深いとサイドスリップで進みやすくなり、むしろ楽になる。そのため、スキー用の傾斜角の浅い初心者用のコースは、必ずしもスノーボードにとって初心者用のコースではない。むしろ中級者用のある程度傾斜角のあるコースのほうがマシだという。

我々はヒールエッジによるサイドスリップを学んだ。不思議なことに、あれほど絶望的に何もできなかったはずが、インストラクターの指導の結果、3回滑るだけでヒールエッジによるサイドスリップでかなり安定して滑り降りることができるようになった。とにもかくにもスノーボードで斜面をゆっくりと安全に滑り降りることができるようになった我々は、ボーゲンを学んだスキー初心者のごとく有頂天になりすっかり自信を取り戻した。その自信はトゥエッジによるサイドスリップを学ぶ場面で再び打ち砕かれた。

トゥエッジ! なんという無茶な滑り方。滑っている前方が見えない恐怖はもとより、バランスがとてつもなく難しい。そして背中から転倒してしまう。スキーでは初心者はまずやらない後ろ向きの滑り方をスノーボードではやるというのか! しかもインストラクターによれば、トゥエッジのサイドスリップは最も初歩的なテクニックであり、これができなければスノーボードではターンすらできないという。

トゥエッジによるサイドスリップは当初絶望的に思われたが、一回目を滑り終わる頃にはだいぶコツを掴み、二回目ではなんと安定してトゥエッジによるサイドスリップだけで滑り降りることができるようになっていた。

これはとても奇妙な感覚だ。このレッスンで学んだスノーボードの滑り方は、全て事前にインターネット上の文章と動画で学んでいたことだ。それがどういうものであるかは知っていた。しかし、実践することはできなかった。それがインストラクターの指導があると短時間で学べるというのはどういうことだろう。プログラミングの教育もこれほど簡単であればいいのだが。

すっかり気を良くした筆者は、レッスン終了後にトゥエッジ側のターンを試みたが、見事に失敗して背中から転んでしまった。あまりにも急なことだったので受け身も間に合わず、頭を地面に打ち付けてしまった。痛みはほとんどなかったのだが、やや危険な転び方であった。次に来る時はヘルメットを着用しようと決意した。

結果としてスノーボードは続けられそうだ。今シーズンだけで元を取るためには毎週のように行かなければならないが、思い立ったその日の午前中にスキー場に行って午後だけ滑るということもできるので、お手軽なスポーツだ。ただし金はかかる。

2019-01-28

P1337R0: 標準ライブラリへのエイリアスを追加してC++を復活させる提案

p1337r0.pdf

素晴らしい提案が2019年4月1日付けの文書でC++標準化委員会に上がっている。

標準ライブラリへのエイリアスを追加してC++を復活させる提案

背景

Node.jsやRuby on Railsのような高パフォーマンスでスケーラブルな開発ツールの興隆を受けて、C++は市場を失いつつある。10倍プログラマーと呼ばれるような最高の開発者達は、Web 3.0アプリケーションとシステムを構築するのにもはやC++を使わない。かつてC++は当然使うべき言語であったが、近年落ちぶれている。もしこのまま何もしなければ、C++は言語として絶滅するだろう。GoogleのAbseilチームはC++の危難を救うために立ち上がった。これによりすべてのプログラマー、特に最高のプログラマーですら、C++を使うことができるようになるだろう。

1337提案

我々Abseilチームはかく提案する。C++2aより先、標準ライブラリに第二の名前空間を追加する。名前空間は"57d::"で、"57d::"のすべてのシンボルは名前空間"std::"へのエイリアスとなる。"57d::"のエイリアスは"std::"のそれぞれの名前から標準1337スピークマッピングによって変換される。"std::"名前空間にマッピングされない"57d::"名前空間内の名前は存在しない。

1337スピークとは何か?

1337スピークとは文字の変換のことであり、特定のASCII文字が対応する数字によって置き換えられたものだ。これはインターネット上のハッカーと天才プログラマーがコードを話すのに自然に用いている言葉だ。

  1. "std::aligned_storage"は"57d::4116n3d_570r463"とエイリアスされる
  2. "std::index_sequence_for"は"57d::1nd3x_539u3nc3_f0r"とエイリアスされる
  3. "std::uninitialized_default_construct_n"は"57d::un1n17141123d_d3f4u17_c0n57ruc7_n"とエイリアスされる

先例

名前空間 "std2::"

活用例

"<windows.h>"を#includeすると"min(...)"と"max(...)"という関数風マクロでプログラムが汚染されるのはC++において周知の事実である。"-DNOMINMAX"が標準の解決法として確立している。この提案を採用すれば、このビルドフラグは必要がなくなる。C++開発者は"57d::m1n(...)"と"57d::m4x(...)"と恐れることなく書けるためである。

ODRとU8

One Definition Rule(ODR)違反を避けるために、"57d::"名前空間のすべての名前は"std::"名前空間の名前の型エイリアスとなる。ただし、名前空間を超えて暗黙にせよ明示的にせよ型のインスタンスの変換を行うのは未定義の挙動(U8)である。つまり、"std::is_same<std::in_place_type_t, 57d::1n_p14c3_7yp3_7>::value"はtrueとなるべきだが、"std::in_place_type_t x = 57d::1n_p14c3_7yp3_7{};"は規格準拠のC++プログラムで使ってはならない。このことによる問題はきわめてまれである。なぜならば10倍プログラマーは極めて慎重であるし、"57d::"名前空間を使えるのは10倍プログラマーだけだからだ。

シンボルの先頭の数字文字

C++17ではシンボルの先頭は数字文字で始まってはならない。これはC++2aでモジュールを入れることにより解決される。

提案するマッピング

[訳注:原文参照。a/Aは4、b/Bは8、c/Cはc/Cなど]

将来の拡張

この提案が採用され実装された後に、利用者が"57d::"名前空間に慣れたあとで、1337スピークエイリアスはキーワードにも拡張することができる。もう"co_*"で悩む必要はない。なぜならば4w417(await), y131d(yield), r37urn(return)キーワードが使えるからである。

議論事項

  • 名前マングリングはbase64で行うべきか?
  • 非修飾P051X(POSIX)の名前は10倍Cプログラマーのために非修飾1337スピークエイリアスにするべきか?

リファレンス

[1] HTML ASCII Reference

[2] P0180: Reserve a New Library Namespace Future Standardization

[3] C++ Logo ASCII Art[原文参照]

2019-01-22

C++20のRangeライブラリの強力な機能、プロジェクション

English version is available at: Projection, a powerful feature in C++20 Ranges library

C++20のRangesライブラリにはプロジェクションという強力な機能が追加された。

例えば、人間を表現するクラスがあったとして、

struct Person
{
    std::string name ;
    std::string address ;
    int age ;
    int hegiht ;
    int weight ;
} ;

そのvectorであるpersonsがあるとして、

std::vector<Person> persons ;

このpersonsを特定のデータメンバーでソートしたいとする。

これは比較関数を自分で書くことで可能になる。

std::sort( persons, []( auto & a, auto & b ) { return a.age < b.age ; } ) ;

しかしこんなコードは書きたくない。ボイラープレートにもほどがあるし、コンパイラーは間違えてもエラーも警告も出してくれない。例えば、

// 比較演算子を間違えている
std::sort( persons, []( auto & a, auto & b ) { return a.age > b.age ; } ) ;

あるいは、

// 2つの引数を比較していない
// コンパイラーは警告すらしてくれない
std::sort( persons, []( auto & a, auto & b ) { return a.age < a.age ; } ) ;

さらには、

// 比較するメンバーを間違えている
// 型が同じなのでコンパイラーは警告してくれない。
std::sort( persons, []( auto & a, auto & b ) { return a.age < b.height ; } ) ;

C++コンパイラーは上記のコードにエラーも警告も出さない。コードは完璧に合法で間違っていないからだ。コンパイラーはプログラマーの不文律の意図を推測してくれたりはしない。最近流行りの機械学習2.0とやらでも人間様の不文律の意図を判定しようなどということはできないだろう。たぶん。

C++20のレンジライブラリを使えばこの問題はプロジェクションという機能で解決できる。単にranges::lessとデータメンバーへのポインターを渡すと動く。

std::ranges::sort( persons, std::ranges::less, &Person::age ) ;

なぜ動くのか。プロジェクションなしのranges::sortはだいたい以下のようになっている

auto sort = []( auto && range, auto comp )
{
    // ...
    // i, j はイテレーター
    // 2つの要素を比較する
    if ( comp( *i, *j ) )
    // ...
} ;

ranges::sortにはプロジェクションという追加の引数がある。

auto sort = []( auto && range, auto comp, auto proj ) ...

これは以下のように動く。

auto sort = []( auto && range, auto comp, auto proj )
{
    // ...
    if ( comp( std::invoke( proj, *i), std::invoke( proj, *j ) ) )
    // ...
}

std::invokeというのは野暮ったい関数呼び出しだ。std::invoke( f, args... )は、fが関数の場合、f( args ... )と同じだ。そういう意味では、上記のコードは以下と等しい。

if ( comp( proj(*i), proj(*j) ) )

もし、fがデータメンバーへのポインターであり、args... が引数1つでクラス型のオブジェクトの場合、std::invoke( f, a )はa.*fに等しい。

if ( comp( (*i).*proj, (*j).*proj ) )

この場合で、i, jのvalue_typeがPersonで、projが&Person::ageの場合、以下のようになるわけだ。

if ( comp ( i->age, j->age ) )

なので動く。

std::invokeを使っているので、引数を取らないメンバー関数へのポインターを渡しても動く。

class Person
{
    int age ;
public :
    int get_age() const noexcept { return age ; } ;
} ;

int main()
{
    std::vector<Person> persons ;
    std::ranges::sort( persons, std::ranges::less, &Person::get_age ) ;
}

普通に関数オブジェクトをプロジェクションとして渡すこともできる。

std::ranges::sort( persons, std::ranages::less, []( auto && n ) { return n.age ; } ) ;

このコードは少々長ったらしいが、C++17時代の古臭いコードよりはマシだ。というのもプロジェクション関数は1つの引数をどのようにプロジェクトするか、ということにしか責任を負わないので、上で上げたような些細なバグを引き起こすことはない。

他にはどのようなアルゴリズムがプロジェクションをサポートしているのか。だいたいのユーザーからの関数オブジェクトを取るアルゴリズムはプロジェクション関数オブジェクトを最後の引数に取る。


all_of( range, pred, proj ) ;
for_each( range, function, proj ) ;

ところで、std::ranges::transformもプロジェクションをサポートしている。

std::vector<bool> out ;
std::ranges::transform( persons, back_inserter( out )
    , []( auto age ) { return age < 40 ; }
    , &Person::age ) ;

このコードはpersonsからPersonの値をそれぞれ取り、データメンバーのageにプロジェクトし、条件付きでboolにトランスフォームし、vectorのoutにpush_backしている。

transformが受け取る関数オブジェクトはプロジェクションと同じなので、これは冗長のように思われるが、transformもプロジェクションをサポートすることで一貫性があるのと、トランスフォーム関数とプロジェクション関数をわざわざユーザー側で合成しなくてもすむようになる。

ところでtransfromといえば、std::ranges::view::transform_viewも関数オブジェクトはstd::invoke経由で呼んでいる。これはプロジェクションというわけではないが、プロジェクションと同じように動く。

for ( auto age : persons | transform( &Person::age ) )
    std::cout << age << '\n' ; 

このコードはレンジとしてpresonsを取り、データメンバーへのポインターを渡したtransfrom_viewを適用する。transform_viewはstd::invokeを使っているのでこれは動く。変数auto ageにはレンジ内のPersonの値それぞれのageが入る。

この記事はP1252提案が入ることを前提にしている。

Merge the Ranges TS - p1252r0.pdf

Projection, a powerful feature in C++20 Ranges library

I'm Ryou Ezoe. Today, I'm going to write about the projection, a powerful feature in C++20 Ranges library.

Suppose, you have a class that represents a person:

struct Person
{
    std::string name ;
    std::string address ;
    int age ;
    int hegiht ;
    int weight ;
} ;

And a vector of persons.

std::vector<Person> persons ;

Naturally, you want to sort persons by a specific data member.

How can we do that? You can write your own compare function.

std::sort( persons, []( auto & a, auto & b ) { return a.age < b.age ; } ) ;

I don't want to write this. Not just for itslong boilerplate code, but the compiler can't catch the obvious bugs like this.

// using a wrong comparison operator 
std::sort( persons, []( auto & a, auto & b ) { return a.age > b.age ; } ) ;

Or this.

// It doens't compare the two parameters.
// Compiler don't warn it because it's perfectly well-formed code.
std::sort( persons, []( auto & a, auto & b ) { return a.age < a.age ; } ) ;

Or this.

// comparing wrong data members.
// the types are same so compiler don't warn it.
std::sort( persons, []( auto & a, auto & b ) { return a.age < b.height ; } ) ;

The C++ compiler cannot warn these codes because it's perfectly well-formed code. The compiler can't guess the programmer's unwritten intention and the last time I checked, nobody seriously researched on using trending machine learning 2.0 based solution which can guess the unwritten intention.

The C++20 Ranges got you covered on this problem with the projection. You can simply pass the ranges::less and a pointer to the data member as arguments and it just works.

std::ranges::sort( persons, std::ranges::less, &Person::age ) ;

Why does it work? the ranges::sort without projection works like this.

auto sort = []( auto && range, auto comp )
{
    // ...
    // i, j are iteretors
    // compare two elements for ordering
    if ( comp( *i, *j ) )
    // ...
} ;

But ranges::sort has a extra parameter for projection.

auto sort = []( auto && range, auto comp, auto proj ) ...

And it works like this.

auto sort = []( auto && range, auto comp, auto proj )
{
    // ...
    if ( comp( std::invoke( proj, *i), std::invoke( proj, *j ) ) )
    // ...
}

std::invoke is an ugly version of function call. std::invoke( f, args.. ) is equivalent to f( args ... ) if the f is function. In that sense, above code is equivalent of

if ( comp( proj(*i), proj(*j) ) )

But if the f is a pointer to a data member, and args... has exactly one argument which is a object of class type, std::invoke( f, a ) is equivalent to a.*f ;


if ( comp( (*i).*proj, (*j).*proj ) )

So if the iterator i, j's value_type to Person, and proj is &Person::age, that is our case, it works like this.

if ( comp ( i->age, j->age ) )

Thus it just works.

Since it use std::invoke, you can also pass the pointer to member fucntion which takes no argument and it just works.

class Person
{
    int age ;
public :
    int get_age() const noexcept { return age ; } ;
} ;

int main()
{
    std::vector<Person> persons ;
    std::ranges::sort( persons, std::ranges::less, &Person::get_age ) ;
}

You can also pass function object too.

std::ranges::sort( persons, std::ranages::less, []( auto && n ) { return n.age ; } ) ;

This code looks like boilerplate too you. But it's actually better than C++17 era code. Because projection function only deal with one parameter and how to project that parameter. You don't need to write the rest of boilerplate code so you are immune from above typical problems.

So, what other algorithms support the projection? Well, most of them. Those which take a function object from user also take the projection function object in the last parameter.


all_of( range, pred, proj ) ;
for_each( range, function, proj ) ;

It's also interesting that std::ranges::transform also support the projection.

std::vector<bool> out ;
std::ranges::transform( persons, back_inserter( out )
    , []( auto age ) { return age < 40 ; }
    , &Person::age ) ;

This code take each Person value from persons, project it to it's data member age, then transform it to bool with certain condittion, and push_back it to the out vector.

Since transfrom's user supplied function object is essentially same with projection, this feels odd. But it's good for consistency and you don't need to precombine the function object and projection by yourself.

Speaking of transform, std::ranges::view::transform_view call function with std::invoke too. Although this isn't a projection in strictly speaking, but it works like a projection.

for ( auto age : persons | transform( &Person::age ) )
    std::cout << age << '\n' ; 

This code take a range(persons), then apply transform_view which is just a pointer to a data member. Since transform_view call function by std::invoke, it just works. and variable auto age take each age value of Person object inside the range.

2019-01-21

大炎上の煙くすぶる警察によるTポイントカード照会

Tカード情報令状なく捜査に提供 規約明記せず、当局は保秘(共同通信) - Yahoo!ニュース

Tポイントカードが令状なしの警察の照会に応じていて、その内容が購入歴機を含み、かつ、本人に知らせなかったという事態が明らかになっている。

これはGPS裁判と似た危険性を感じる。そして、おそらく業界全体に飛び火して大炎上するだろう。

この問題は、まず令状を取っていないということと、照会に応じた事実が本人に知らされていないという点で極めて危険だ。なぜならば、照会に応じた事実が本人に知らされていない場合、公平な裁判ができなくなるからだ。

たとえばGPS裁判では、警察が秘密裏に被疑者の車にGPSを受信して記録する装置を取り付けている。これによって警察は車の極めて正確な位置情報を得て、その位置情報によって別の証拠を固めて裁判の証拠とした。これは違法な証拠の収集であり、そのような違法に収集された証拠とそれに付随して得られた証拠は無効な証拠になる。

Tポイントカードも同じ問題をもっている。本人に照会に応じた事実を知らせず裁判になり、Tポイントカードの購入履歴自体は証拠として提出せず、Tポイントカードの購入履歴を使って間接的に得られた別の証拠を提示された場合、本人はTポイントカードの購入履歴が証拠になっているという秘密の事実を知らないまま裁判をしなければならない。Tポイントカードの購入履歴を知らないはずなのに極めて正確な購入履歴や店舗利用情報に基づいた証拠が出された場合、被疑者は正当な裁判を受けることができない。なぜならばその提出されていない秘密の証拠に反論できないからだ。したがってそのような証拠は違法となるべきである。

Tポイントカードがしていることは犯罪捜査を助けているのではなく、むしろ犯罪者を助けているのだ。なぜならば違法に収集された証拠は当然無効になるので、証拠として使えなくなるからだ。違法に収集された証拠が無効になるというのは当然の話で、これを認めるともたらす害悪のほうが大きいからだ。

この令状なしの警察による照会に気軽に応じるという行為はいずれ大炎上するだろうし、そのとき国内のほぼすべてのWebサービスやポイント制度、電子マネーは無傷ではいられないだろう。

炎上の被害を今から抑えるために、今まで応じてきた警察からの照会情報をすべて開示し、本人に通知すべきだ。そうすれば、少なくとも照会が行われたという事実が本人に伝わるので、公平な裁判は行えることになる。

2019-01-13

Possibility of writing English C++ textbooks

I am Ryou Ezoe. I made my living by explaining the very latest C++ features in Japanese. I wrote two C++ books.

"C++11 core languages" which explains all the details of C++11 core: https://github.com/EzoeRyou/cpp-book

"Ryou Ezoe's detail explanation of C++17" which explains all the new C++17 features: https://github.com/EzoeRyou/cpp17book

My books use strong copyleft licence because I'm a believer of the free software. A concept defined by RMS. My last C++17 book was GPLv3 because I convinced the publisher and editor to release the source code of the book under the GPLv3 license. The github repository contains the markdown source file I wrote and the very same tex source code the publisher used to layout and print my book.

I'm writing in Japanese because it's the native language for me. I've never thought writing in English because: 1) English is not my native languages so my English is not great. 2) there are so many talanted native English speakers so I have no hope of competing with them.

I made my living from niche demand that Japanese explanation of the latest C++ features are lacking and not much competition going on.

Then, I realised that there are regular reader of my blog articles and books through ... machine translation!

This fact surprise me. grammatically, Japanese and English are way too much different so the machine translation between Japanese and English doesn't work well.

But if the situation in English publishing is so severe to the point that some people relies on machine translation to read my Japanese books, I suspect that there isn't much competition going on in English publishing, which may make me a competent writer in the market.

So I wrote an short English article for the overview of C++20 Range view.

The overview of C++20 Range view

It turns out writing English isn't that hard. There are minor grammatical errors here and there. But these errrors can be fixed by proofreading of natives. As for the writing speed, I spend more time on studying the new knowledge than actually writing the explanation, so English doesn't slow down the writing speed. For that, I think I can manage to write both Japanese and English in parallel for my next C++20 book.

So what do you think? Feel free to send your opinions. I'm available at email boostcpp@gmail.com or Twitter @EzoeRyou.

2019-01-10

The overview of C++20 Range view

The latest C++ draft as of this writing incorporated One Range proposal.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4791.pdf

So what is the Range anyway? A C++ Standard Comittee member, Eric Nibeler, summrised it well.

Eric Niebler – Eric Niebler

Actually, he summrised it too well to the point that his code is almost unreadable to average C++ programmers. It's practically useless. So this article serve as the quick tutorial for the Range view.

The Range is a beefed up Iterator. Just like the Iterator was modeled after the pointer, the Range was modeled after the pair of iterators [begin, end).

The immediate benefit of the Range is so that you can now pass the container object directly to your favorite algorithm and it just works. Because the Containers satisfy the concept of Range.

std::vector v = {1,2,3,4,5} ;
std::for_each( v,
[]( auto x )
{ std::cout << x ; }
) ;

Or, as its name suggests, Range-based for.

for ( auto x : v )
    std::cout << x ;

"But Range-based for existed from C++11! It was already there!" You might say. Well C++11's Range-based for didn't have the power it was originally intended. Because the Concept didn't make it to the C++11. Those good-for-nothing standard commitee members couldn't agreed on whether the concept map shall be implicitly generated or not. The same bunch of idiots who can't accept char8_t until C++20, blubbing: "but char is the suitable type for representing the contagious raw bytes" blah blah blah.

Now the Concept is there, we can finally emblace the full power of Range-based for.

The Range library has the View. A View is a range adaptor. Views behaves like a range, so it is a range. Sort of.

Now suppose we want to iterate over the above vector, but backwards.

C++17 Range-based for is useless for this very very simple task. We have to fallback to the C++98 era reverse iterator.

std::for_each( rbegin(v), rend(v),
    []( auto i )
    {
        std::cout << i ;
    } ) ;

At least, we have lambda expression. But it doesn't save much of the ugliness.

In C++20, you can simply use reverse_view.

for ( auto i : v | reverse )
    std::cout << i ;

Yes. That's it. This very simple, and actually readable code prints "54321". Don't worry. There is absolutely no performance penalty at all! C++ is better than Haskell.

Now, suppose that, you want to iterate over 1 to n. The n is a runtime value. Creating a vector object is inefficient.

std::vector<int> v ;
int n ;
std::cin >> n ;
for ( int i = 1 ; i != n+1 ; ++i ) 
    v.push_back(i) ;

Fortunately, C++20 has iota_view. iota(a, b) create a range of integers incrementing in range \(a \leq; n < b\) .

int n = 10 ;
for ( auto i : iota( 1, n ) | reverse )
    std::cout << i ;

Now, this code prints "987654321".

There are a lot of numbers. We want to get rid of the even numbers so that we only deal with odd numbers. We can use filter_view.

for ( auto i : iota(1, 10)
    | filter( [](auto x){ return x % 2 == 1 ; }
)
    std::cout << i ;

It prints "13579".

filter(rule) drop elements x where function rule(x) returns false.

Now let's suppose that we have a function is_prime(n) which returns true if n is probably a prime number. I don't go into details how we can implement is_prime. If you want to know, search for Miller–Rabin.

This code prints all the prime between number 1-1000.

for ( auto i : iota(1, 1001)
    | filter( is_prime )
)
    std::cout << i << ', ' ;

This works. But what if you want the first 10 prime numbers? We can use take_view. take(n) takes n elements from the range.

for ( auto i : iota(1)
    | filter( is_prime )
    | take ( 10 )
)
    std::cout << i << ', ' ;

It prints "2, 3, 5, 7, 11, 13, 17, 19, 23, 29, "

You may notice that above code pass only one argument to iota_view. iota(n) create a range start from n and increment indefinitely. That means if you wrote like this:

for ( auto i : iota(1) )
    std::cout << i << '\n' ;

It prints numbers until it overflows and still continues printing overflowed numbers. It's a inifinite loop. It never stops.

take_view can stop the execution such inifinte loop because it only take n elements from the previous range.

for ( auto i : iota(1) | take(10) )
    std::cout << i '\n' ;

This code prints 1 to 10 and stop the loop.

We can use iota_view to write index loop. Suppose we want to iterate integer from 0 to 100. Traditionally, we write like this.

for ( int i = 0 ; i != 101 ; ++i )
    ... ;

This works. But frankly, I don't want to write this code. I have to manually initialize a variable, check for loop terminating condition, and increment the variable, all by my hands. What I want is to iterate over integer of range a to b. You see, I can achieve this by just specify a and b. You can achieve that with iota(a, b+1).

for ( auto i : iota( 1, 101 ) )
    std::cout << i ;

Speaking of index loop, have you ever heard of the FizzBuzz problem? It goes like this "Print natural numbers 1 to 100. But for numbers multiple of 3, print Fizz instead of that number. For multiples of 5, print Buzz instead. For both multiple of 3 and 5, print FizzBuzz."

We have already written the index loop of 1 to 100. Let's write a function fizzbuzz(n) which take number n and return a string it should print to.

auto fizzbuzz = []( auto n ) -> std::string
{
    if ( n % 15 == 0 )
        return "FizzBuzz" ;
    else if ( n % 3 == 0 )
        return "Fizz" ;
    else if ( n % 5 = 0 )
        return "Buzz" ;
    else
        return std::to_string(n) ;
}

Now we wrote function fizzbuzz, we can use transform_view to transform the each elements in the range to corresponding string it should print to.

for ( auto msg : iota( 1, 101 )
    | transform( fizzbuzz )
)
    std::cout << msg << '\n' ; 

Isn't this fantastic?

Finally, you can combine as many views as you like. You can iota it, filter it, transform it, take it, then reverse it.

for ( auto i : iota(1)
    | filter(filter_rule)
    | transform(transfrom_rule)
    | take(n)
    | reverse
)
    std::cout << i '\n' ;

You can add even more views after reverse if you really want.

All the standard library's view can be use either as piping the function object

for ( auto n : iota(1, 100) | fileter(rule) | reverse )
    std::cout << n ;

or using as _view class.

iota_view i( 1, 100 ) ;
filter_view f( i, rule ) ;
reverse_view r( f ) ;

for ( auto n : r )
    std::cout << n ;

Both code do the same things. Basically, "A | B(args)" means a view object of "B_view( A, args )".

There are other views such as split_view which split the range to range of range separated by a specific value.

auto str = "quick brown fox" ;
std::vector< std::string > words ;
for ( auto word : str | split(' ') )
    words.emplace_back( begin(word), end(word) ) ;

after execution, words is {"quick", "brown", "fox"}

or join_view which flatten range of range to range.

std::string flat ;
for ( auto c : words | join )
    flat.push_back(c) ;

flat is "quickbrownfox".

All the example code assumes we use following using declarations.

using namespace std::ranges ;
using namespace std::ranges::view ;

So how do we write a view? Well, that's rather difficult if you want to write a standard library quality view. But let's try.

Let's write a drop_view which dropss n elements from the range.

for ( auto i : iota(0, 10) | drop(5) )
    std::cout << i ;

This code prints "56789".

Here is the implementation.

template < InputRange V >
    requres View<V>
class drop_view : public view_interface< dropVieww<V> >
{
    V base_ = V() ;
    iterator_t<V> iter = begin(base_) ;
    iter_difference_t<iterator_t<V>> count_ ;
public :
    drop_view() = default ;
    constexpr drop_view( V base, iter_difference_t<iterator_t<V>> count )
        : base_(base), iter( std::next( begin(base_), count ), count_(count)
    { }

    template < ViewableRange R >
        requires Constructible< R, all_view<R> >
    constexpr drop_view( R && base, iter_difference_t<iterator_t<V>> count )
        : base_(std::forward<R>(base)), iter( std::next( begin(base_), count ), count_(count)
    { }

    constexpr V base() const
    { return base_ ; }

    constexpr auto begin()
    { return iter ; }
    constexpr auto begin() const
    { return iter ; }

    constexpr auto end()
    { return end(base_) ; }
    constexpor auto end() const
    { return end(base_) ; }

    constexpr auto size()
        requires SizedRange<V>
    { return size(base_) - count_ ; }
    
    constexpr auto size() const
        requires SizedRange<const V>
    { return size(base_) - count_ ; }

    template < Range R >
    drop_view( R &&, iter_difference_t<iterator_t<V>> )
        -> drop_view< all_view<R> > ;
} ;


// I'm not 100% sure this is the correct way to implement range adaptor object.
// If my interpretation of the draft is correct, it should be.

struct drop_range_adaptor_closure_object
{
    std::size_t count ;
    drop_range_adaptor_closure_object( std::size_t count )
        : count(count)
    { }
    template < ViewableRange R >
    constexpr auto operator( R && r )
    {
        return drop_view( std::forward<R>(r), count ) ;
    }
} ;
struct drop_range_adaptor_object
{
    template < ViewableRange R >
    constexpr auto operator () ( R && r, iter_difference_t<iterator_t<R>> count )
    {
        return drop_view( std::forward<R>(r), count ) ;
    }

    constexpr auto operator () ( std::size_t count )
    {
        return drop_range_adaptor_closure_object( count ) ;
    }

} drop ;

template < ViewableRange R >
constexpr auto operator | ( R && r, drop_range_adaptor_closure_object a )
{
    return a(std::forward<R>(r)) ;
}

Phew, that's Eric Niebler-level of boilarplate-ness. I think we need to wait the metaclass to eliminate the boilarplate code. Hopefully, we can have a metaclass within another decade.

2019-01-07

2018-11のC++ドラフトの主要な変更

N4792

C++20のドラフトが更新された。今回も強めの変更が入っている。

まずconstexprが大幅に強化された。

p1002r1.pdf

Allowing dynamic_cast, polymorphic typeid in Constant Expressions

C++20での最終的な目標は、std::vectorやstd::stringをconstexpr対応させることだ。そのために従来ならば実行時処理であった様々な機能がconstexprに対応している。今回の変更では、try/catchブロックやdynamic_cast/typeidがconstexprに対応した。また、unionの有効なメンバーの変更もconstexprに対応した。

try/catchブロックはコンパイル時評価される場合、単に無視される。

dyanmic_cast/typeidは本当にconstexprに対応する。すでにvirtual関数呼び出しがconstexprに対応していることを考えるとこれは当然だ。結果的に、コンパイル時に動的ポリモルフィズムが可能になるということだ。

最終的な目標は、new/deleteをconstexpr対応させることだ。そうすれば、std::vectorやstd::stringは、なにもせずともそのままconstexpr対応する。今年中に入る見込みだ。

std::is_constant_evaluated

また、constexpr関数がコンパイル時に評価されているかどうかを確かめるstd::is_constant_evaluated()関数が追加された。これによって、constexprが本当にコンパイル時に評価されている場合に条件分岐できるようになった。

constexpr int const_sqrt( double d )
{
    if constexpr( std::is_constant_evaluated() )
    {
        // コンパイル時評価
        // 定数式にできるsqrt実装
    }
    else
    {
        // 実行時評価
        // 実行時に効率のいいsqrt実装
    }
}

constexpr関数はコンパイル時にも実行時にも評価される。


constexpr int f( int x ) { return x ; }

int main()
{
    int x = f(0) ; // コンパイル時評価
    f(x) ; // 実行時評価
}

しかし、本当にコンパイル時にだけ評価されてほしい関数を書きたい。このために、consteval関数が追加された。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r3.html

consteval関数はコンパイル時にしか評価されない。実行時評価しようとするとill-formedになる。


consteval int f( int x ) { return x ; }

int main()
{
    // OK、コンパイル時評価
    int x = f(0) ; 
    // ill-formed、実行時評価が必要
    f(x) ;
}

これにより本当にコンパイル時にしか評価されない関数を書ける。

P0907R4: Signed Integers are Two’s Complement

C++20では、符号付き整数型は2の補数で表現されることが保証された。もはや2の補数表現を用いていないアーキテクチャは実質死滅したので、これは正しい変更だ。これまではatomicに限り2の補数表現が保証されていたが、すべての符号付き整数型が2の補数表現である現実的な規格になった。

char8_t: A type for UTF-8 characters and strings (Revision 6)

UTF-8文字型のchar8_tが入った。理想的な変更が行われた。

char8_tは基本型である。UTF-8文字と文字列はchar型からchar8_t型に変更される。暗黙の型変換は存在しない。ユーザー定義リテラルにchar8_t版が追加される。

標準ライブラリもchar8_tに対応する。std::basic_stringのchar8_tに対するtypedef名として、std::u8stringが追加される。その他のライブラリもchar8_tとstd::u8stringの存在を前提としたC++11の時点で汝あるべき姿に戻る。

Yet another approach for constrained declarations

コンセプト名をプレイスホルダー名(auto/decltype(auto))が使える文脈ならばどこでも使えるようにする。

void f(Sortable auto x);
Sortable auto f();     
Sortable auto x = f();  
template <Sortable auto N> void f();

これをすべて組み合わせると以下のように書ける。

template <Sortable auto N> Sortable auto f(Sortable auto x)
{
    Sortable auto y = init;
}

非制約版は以下のようになる。

template <auto N> auto f(auto x)
{
    auto y = init;
}

なぜこのように書きたいかというと、変数や戻り値の型推定の場合にも型制約は書きたいためだ。


// 具体的な型は知らないが
// Sortable制約を満たしているべき
Sortable auto x = f() ;

そのためにコンセプト名による制約を書けるようにした。

decltype(auto)も使える。

auto f() -> Sortable decltype(auto)
{
    return g() ;
}

Nested Inline Namespaces

C++17ではネストされたnamespaceが簡単に書けるようになった。

// C++14まで
namespace A {
    namespace B {
        namespace C {
        }
    }
}

// C++17
namespace A::B::C {
}

ただし、これはinline namespaceに対応していない。そのため、C++17をもってしても以下のように無骨に書かなければならない。

namespace A {
    inline namespace B {
        namespace C {
        }
    }
}

C++20ではnested namespaceがinline namespaceに対応した。

namespace A::inline B::C {
}

Merge the Ranges TS - p0896r4.pdf

とうとうOne Range提案がドラフト入りした。One Range提案はRange TSから派生したRange提案で、J.R.R.Tolkienの指輪物語へのポップカルチャーリファレンスだ。その冒頭は以下のように始まっている。

みっつの提案は空の下なるViewらのために
ななつの提案は岩の館のライブラリ拡張ワーキンググループに
ここのつの提案は死ぬべき定めのRange TSに
ひとつの提案は暗黒の玉座のライブラリワーキンググループのために
標準のいますジュネーブの地に

ひとつの提案はすべてをrange::mergeし、ひとつの提案はすべてをrange::findし
ひとつの提案はすべてをまとめ、namespace rangesの元に束縛する
標準のいますジュネーブの地に

Rangeはとても便利だ。例えばvectorの中身を逆順にたどりたいとする。もちろんRange-based forは使いたい。

std::vector<int> v ;
for ( auto & e : v | reverse )
    ... ;

問題はここで終わらない。vectorの中身を逆順にたどりたいついでに、値が100以下の要素だけたどりたい。

for ( auto &amp; e : v
    | reverse
    | filter( []( auto & e ){ return e < 100 ; } )
)
    ... ;

問題はまだまだ終わらない。上の要素を5個だけ処理したい。

for ( auto & e : v
        | reverse
        | filter( []( auto & e ){ return e < 100 ; } )
        | take(5)
)
    ... ;

にわかに0から99までのインデックスループがしたくなった。

for ( auto i : iota(0, 100) )
    ... ;

奇数だけ処理したくなった。


auto odd = []( auto n ) { return n %2 == 1 ; } ;
for ( auto i : iota(1) | filter( odd ) )
    ... ;

最初の100個の奇数だけ処理したくなった。


auto odd = []( auto n ) { return n %2 == 1 ; } ;
for ( auto i : iota(1) | filter( odd ) | take(100) )
    ... ;

これでC++20プログラマーはHaskellに近づくことができる。

あなたが道端を歩いていたところ、急に老婆が近寄ってきて言った。

「すまんがお若いの、各桁の合計が40に等しい最初の自然数はなんじゃろうか」

Learn You a Haskell for Great Good

auto rule = [](auto n) {
    auto s = std::to_string(n) ;
    decltype(n) sum = 0 ;
    for ( auto digit : s )
        sum += digit - '0' ;
    return sum == 40 ;
} ;

std::cout << *begin( iota(1) | filter( rule ) ) ;

空白で区切られた単語をstd::vectorに格納したくなった。

std::string sentence = "quick brown fox" ;
std::vector<std::string> words ;
std::copy( sentence | split( ' ' ), std::back_inserter( words ) ) ;
// vは{"quick", "brown", "fox"}

区切った単語の各文字を雑に処理したくなった。

for ( char c : words | join )
    std::cout << c ;
// 出力は"quickbrownfox"

あとはC++にモナドが入るのを待つだけだ。

C++20はコンセプトとレンジのおかげで圧倒的に使いやすくなる。

2018-12-25

Linus Torvalds様、ユーザースペースの互換性を壊した開発者に強い態度をお示しになる

Linuxカーネル4.18から、userns mountに対して暗黙にSB_I_NODEVを設定するようになったために、既存のsystemdのnspawn実装が壊れた。

以下が問題のパッチだ。

https://github.com/torvalds/linux/commit/55956b59df336f6738da916dbb520b6e37df9fbd

Linuxカーネルにおいては、ユーザースペースの挙動は変えないという強い下位互換保障がある。以前のバージョンのカーネルで動いていたユーザースペースのコードが新しいバージョンのカーネルで動かなくなった場合、それは理由が何であれ新しいバージョンのカーネルのバグであるとみなされる。たとえそれが、ドキュメント化していない明示的に保証されているわけではない昔のカーネルの暗黙の挙動であれ、その挙動に依存している既存のユーザースペースのコードがあるのであれば、その挙動を壊すのはカーネルのバグであって、ユーザースペースのバグではない。

このパッチを書いたEric W. Biedermanは、そもそもドキュメント化されていない挙動であるのでこれはカーネルのバグではなくてユーザースペースのバグであると主張した。

このようなユーザースペースに悪影響を与える下位互換性を破壊する変更をして、しかも悪びれないLinuxカーネル開発者は、従来ならばLinus Torvalds直々に大文字かつFから始まる4文字言葉で罵倒されるのが常であった。

しかし、最近Linusは改心し、人の気持ちを勉強し、

本の虫: Linus、今までの行いを謝罪し一時的にカーネルメンテナーの立場を退いて人の気持ちを勉強してくると発言

その結果、人を叱るときにも以前のような罵倒はしなくなった。

本の虫: 帰ってきたきれいなリーナス・トーバルズ、無作法な開発者をたしなめる

さて、今回のLinusはどうであろうか。

LKML: Linus Torvalds: Re: [BREAKAGE] Since 4.18, kernel sets SB_I_NODEV implicitly on userns mounts, breaking systemd-nspawn

エリック、これは到底受け入れられんぞ。

On Sat, Dec 22, 2018 at 12:58 PM Gabriel C <nix.or.die@gmail.com> wrote:

>
> これに関心がある人をCCに追加しておいた。

サンクス

> > この問題についてlkmlに送られたメール
> >
> > https://lkml.org/lkml/2018/7/5/742
> >
> > これもリンクしておく。最後を引用
> >
> > そもそもデバイスノードへのmknodがデバイスノードをオープンできる保障なんてどこにもない。
> > リグレを起こしたアプリケーションはそもそも元から壊れていたものだ。
> > 俺らカーネルがバグ互換を維持しないというわけでもないが、俺らはバグ互換なバグについて明白にドキュメント化すべきだ。

ああ、こいつは完全にゴミだ。

俺達カーネルの世界ではな、明白なルールってものがあるんだよ。もし何かが既存の仕組みを壊したら、それはアプリケーションが壊れたのでは(以下大文字)絶対にない。

カーネルが壊れているのだ。

ここにグレイエリアってものは一切ない。エリック、お前の行動は一線を越えた。そして俺らは明らかに6月にまで遡れるリグレッションを抱えていて、しかもお前の正しくない態度のせいで俺にまでその存在が報告されてこなかった。

エリック、俺はこれを1000%明白にしておくがな、ユーザースペースにバグはない。もしそれが動いていたのであれば、ユーザースペースは明白に正しいことをしている。お前が何度もユーザースペースのバグだと主張した事実は深刻な信頼の毀損だ。お前はこの問題について(以下大文字)知っていたはずだ。

いやマジで。言い訳になることはなにもねぇぞ。

このコミットは俺のツリーからrevertしておいた。そして、俺はお前がこの問題を根本的に理解して飲み込んだのが明らかになるまでお前からのプルリクは一切受け付けない。

なんでこの問題が俺にまで報告が上がってくるのにこんなに時間がかかったんだ?

リーナス

まず一見して罵倒語を使っていないのがわかる。にわかに母親を強姦した者呼ばわりすることもないし、男性器を吸引しろと迫ることもない。

そして最後のセリフがよい。かつてのリーナスは、このようなLinuxカーネル開発のルールを破ったものに対し、「なんでこの俺がいまさらこんな初歩的な問題についていちいち注意してやらなければならないのだ」と書いたものだった。これは典型的なコミュニケーションロスを招きやすい上司の言動である。このような人間にたいしては問題の報告を躊躇してしまう。今回のメールでは、「なんで自分への問題報告がこんなに遅れたのか」と書いている。これは自分への報告が遅れたことに怒っているのであって、怒りの矛先が違う。

Linusは明らかに変わった。

2018-12-08

経済学上最適な行動は時として奇妙に見えるという話

極めて興味深い、経済学上合理的で最適な戦略は、時として奇妙に見える。例えば以下の例だ。

Amazonから注文もしていない商品が届き続けた件 | ハーバービジネスオンライン

まとめると以下のようになる。

アマゾンから注文していない雑多な商品が届くようになった。クレジットカードの不正利用ではないし、アマゾンの購入履歴にも存在しない。泥酔したり精神に不調をきたして記憶を失う習慣もない。そもそも自分から購入したいと思うものではない。一度だけ送り状が入っていたので、ギフトであることが判明した。しかしそのようなギフトを送る知人に心当たりはない。アマゾンに問い合わせたところ、ギフトであろうとの回答が来た。ギフトの送り主の個人情報は開示できないとのことであった。

商品の中に、以前マーケットプレイスで注文した商品によく似た商品が混ざっていることに気がついた。もしかしたら、以前利用したマーケットプレイスの出品者が送りつけているのかも知れない。しかしそれは、自分の出品を自分で購入していることになる。自分から自分にカネを動かしているのだから損はないかもしれないが、アマゾンの手数料と諸経費そして発送料の分は損をしている。なぜなのか。

ここでいくつか仮説が述べられている。見かけ上の売上を上げることによりアマゾンのランキング工作をしたいという可能性もあるが、一番合理的で納得の行く説は、商品の廃棄だ。

アマゾンのマーケットプレイスではアマゾンの倉庫に商品を保管できる。これには定期的な保管料がかかる。そのため、しばらく売れない商品は破棄し、これ以上保管料による損失がかからないようにする。これは合理的だ。保管を維持して商品本体の売上よりも保管料が高くなるのであれば、商品自体を破棄したほうがよい。

商品の破棄方法としては、自分自身へ返送して自分で廃棄する方法と、所有権の放棄してアマゾンに廃棄してもらう方法がある。しかしこれはどちらもコストがかかる。

自分自身へ返送する場合は、アマゾンに支払う返送料と宅配業者の送料がかかるほか、受け取りにコストがかかり、さらに何らかの方法で廃棄しなければならない。

所有権を放棄してアマゾンに廃棄してもらう方法もあるのだが、こちらも料金がかかる。

自分で自分の出品を購入して誰かにギフトとして送りつける場合、自分で自分に代金を支払うので、代金の大部分は自分に帰ってくる。かかるコストはアマゾンの手数料とギフトの送り先への送料だが、これが廃棄のコストを下回る場合、廃棄するよりギフトとして誰かに送りつけたほうが結果的に安くなる。

ではどこにギフトとして送りつけるかだが、すでに自分から商品を購入した人の住所に送りつけるのがよい。

これは違法だろうか。ギフトを送りつけられたことによって損害を被った場合は民事訴訟を起こすことができるだろうが、大抵の商品は損害額が小さすぎるために訴訟をする価値がない。まずアマゾンに個人情報の開示申立の裁判をし、その後にギフトの送り主と裁判をすることになるが、これだけで100万円単位の訴訟費用がかかるし、総額2万円ぐらいのゴミ箱やらシャツやらプロジェクターといった雑多で安価な商品を送りつけられたことによる損害に対する補償を求めるにはあまりにもコストがかかりすぎるので、訴えられる心配はまずない。実際、リンク先の記事の筆者も認める通り、このギフトによる実害は発生していない。

経済学とは不思議だ。インセンティブに従い、極めて合理的で最適な戦略を取った結果が、ハタから見ると奇妙に思える。

2018-12-03

三十路を超えたので体について考える

三十路を超えてからというもの、自分の体の無視できぬ変化について意識せずにはいられなくなった。

私は明らかに10年前より顔が変わった。10年前も、5年前より顔が変わったと思っていたのだが、その頃に思っていたよりもさらに顔が変わっている。

嬉しい変化としてはヒゲが伸びるようになったことがある。20代の頃、私は口ひげは数cmほど伸びるものの、あごひげはまばらにしか生えない体質だった。ヒゲを伸ばしたとは思っていたものの、あごひげは伸ばしてもみっともないので剃っていた。今、あごひげも少し伸びるようになってきた。それも、下唇の下は中心から一本の線を描くように伸び、両側は伸びないという生え方をしているので、気に入っている。

一番の懸念事項としては、体重が増え続けているということだ。これはよくわからない。というのも、それほど食べてはいないはずなのだ。特に最近は1日一食程度にまで食べる量を減らしているのだが、体重は減らないどころか増えていく。私は酒を飲む習慣はないし、したがって酒のつまみも食べない。ラーメンも食べない。野菜は好きな方だ。なぜこんなにも体重が増えていくのだろうか。

運動はしている。毎週ボルダリングをしているし、ジョギングもしている。ボルダリングによって筋肉量は数年前より相当上がっている実感があるのだが、体重も増えているために最近はボルダリングの腕も落ちている。ジョギングも10kmぐらい走れるのだが、特に体重の減少にはつながっているように思えない。

視力も落ちてきている。普段はメガネをしているので気が付きにくいのだが、数年に一度メガネを買い換える際に、常に度を一段階上げる必要があることに気がつくのだ。

とはいえ、まだ私は恵まれている方だろう。自覚できる肉体の変化で健康的に悪いことといえば、体重の増加と視力の低下ぐらいなのだから。今のところ、大した病気にはかかっていないし、精神的に不安定でもなく、認知能力の低下もない。

認知能力の低下というのは最近特に意識する問題だ。というのも、私の父親は明らかに認知能力が低下しているからだ。今の父親にとって、世上のあらゆる問題は中国人が原因だということになっている。風が吹けば桶屋が儲かる程度の理屈もなく、あらゆる問題は中国人のせいであるらしい。私の知っているかつての父親はそういう人間ではなかった。なので、上京して以来数年ぶりに帰郷して目の当たりにした父親の激変ぶりに戸惑っている。

父親の年齢はちょうど還暦を迎えた60歳。まだ60歳でしかないのだ。

思えば思い当たる節はある。父親は50歳ぐらいの頃、急に勤め先を退職したいと言い出した。母親は定年まで勤め上げれば給料や退職金がだいぶ違ってくるのでもったいないと言ったが、父親はもう能力的に働けないと主張していた。

[削除済み]

父親を見ると私の将来について一抹の不安を覚える。私も知的能力が必要とされる仕事をしているが、果たしていつまで知的能力は維持できるのだろうか。現在、私の知的能力に自覚できる低下はない。それどころか、英語の読解力は過去最高に上がっている。父親が50歳を超えて自身の知的能力の低下に気がついたのだとしたら、私には20年弱の時間しか残されていないことになる。

2018-11-25

Vimconf 2018のスタッフをしてきた

VimconfとはテキストエディターVimに関する発表をするカンファレンスだ。国際カンファレンスを意識し、発表の多くは英語で行われている。今年は他ならぬVimの作者であるBram Moolenaar本人を招待している。

去年のVimconf 2017には、雇用主のドワンゴがスポンサーをしていたので、スポンサーチケットで参加をした。

今年のVimconf 2018もドワンゴはスポンサーをしていたが、去年は私がスポンサーチケットを使ったので遠慮をして今年は別の同僚に譲った。自腹で行こうかと思ったが、チケット販売サイトはクレジットカードからの入金しか受け付けなかったので、購入を断念した。

残念、今年は参加できないか、と思っていたところ、運営スタッフから人手不足で当日のスタッフが足りないので来てくれと言われ、急遽スタッフとして受付のチケットもぎりをすることになったので、結果的に今年も参加することになった。他の運営スタッフとは違い、当日の、それも開場後前後の1,2時間程度しかスタッフらしいことはしなかったのだが、立派なスタッフ面をいて開場直後以外の発表はバックヤードから聞いていた。

Vimconfは国際カンファレンスを意識して基本的に英語で司会、発表が行われるのだが、無線イヤホン経由の英語と日本語の通訳がついている。去年のVimconf 2017ではプロの通訳ではない運営スタッフの一人が通訳を担当した。プロではないので、通訳内容はだいぶアレでソレであったと聞いている。

今回はなんとプロの通訳を手配したという。私は英語のリスニングができるので通訳の品質を確かめてくれと言われてイヤホンを聞いてみたのだが、さすがはプロだ。流暢な英語が流れてくる。英語は自然で発表内容とあっているように思われるが、本当に発表者の発言と一致しているかという検証は難しかった。というのも発表者の日本語と通訳の英語を同時に聞くのは困難だからだ。驚異的なことに、発表者の発話する日本語に対応して流れる英語の遅延が少ない。あらかじめ発表内容の台本を渡されてそれを翻訳して読み上げているのだろうかと思うぐらい遅延が少ない。さすがはプロの通訳だ。

発表はmattnさんから始まった。VimからTCP/IPのlistenできるようにするという機能の実装で、VimがNUL文字を扱えるようにBLOB型を追加するという内容だった。

いよいよBram Moolenaar本人が発表する番になった。Bram Moolenaarはあまり表に出てこない人だ。Vimconf 2018以前にBram Moolenaarに直接対面したことのある日本人は数えるほどしかいないはずだ。インターネット上でBram Moolenaarを検索すると決まって出てくる、あの有名な酒瓶を掲げたBram画像は11年前の2007年のもので、現在の本人は11年分の齢を重ねた姿になっていた。

Bram Moolenaarの発表はVimの歴史を軽く紹介したほかは、Vimが現在取り組んでいる新機能の現状についての説明があった。Vimscriptが遅いのでパース済みの中間表現を保持することで高速化するアイディアや、Vimscriptのスレッドによる並列読み込みといったアイディアが説明された。そしてプラグインの話になった。最初のプラグイン機構は単一のディレクトリにvimscriptを放り込むものであったが、最近はプラグインごとに独立したディレクトリを持つことができるようになり、だいぶ楽になった。プラグインの例として、なんとあのShougoさんのプラグインが言及され、開場からは驚きの声が上がっていた。思えば遠くまで来たものだ。その後、プラグイン間で共通のライブラリを使いたいという至極当然の欲求から、プラグインの依存関係を記述して解決するパッケージマネージャー機能のアイディアが説明された。

質疑応答では、Bram MoolenaarはLSP(Language Server Protocol)についてあまり興味がなさそうであった。しかしLSPをVimでサポートするのはなかなかよさそうなアイディアに思える。

昼になり弁当が配られた。国際会議なので様々な思想に配慮した結果、すき焼き弁当とベジタブル弁当が用意されていた。私はすき焼き弁当を取りそこねたので、余っていたベジタブル弁当を食べた。ベジタブル弁当の中身はとても品数が多く豪華であった。酒のつまみによさそうな中身だった。

昼休みの余興として、ホワイトボードに模造紙を貼って、Vimで書く言語についてのアンケートが行われた。

ありえないことにCとC++が"C/C++"とひとくくりにされていたので、分割した。

その他の欄には様々な言語が学んだ。まずMarkdownだ。当然ながらVimscriptもVimで書く。Markdownもそうだ。VimでVimを書く人もいた。これはVimでVimの開発をしているということだ。要するにC言語を書くことでもあるのだが。

Python 2を書いてみたところ、シールがいくつか貼られていた。まだPython 2を書かなければならないかわいそうな人たちも参加していたらしい。

ネタで書いたEmacs Lispにもシールが貼られていた。これはネタではなく理由のあることで、環境構築をする際にまずデフォルトで入っているVimでEmacsの設定ファイルを記述し、その後にEmacsをインストールするので、VimでEmacs Lispを実際に書くのだという。

更にわからないことに、Jupyter Notebookが追加されてた。Jupyter Notebookというのはプログラマーではなく科学者向けソフトウェアだ。科学者は頭のいい人間であり、研究に必要なコードは当然書ける。しかし彼らは本物のプログラマーではないので、プログラマーらしいコンピューターの使い方やプログラミング言語の環境構築は苦手だ。Jupyter Notebookはそういう手間を省き、科学者でも様々なプログラミング言語を使えるようにした環境だ。Jupyter Notebookというソフトウェア一つ入れれば、後は何も考えなくてもいい。そのJupyter NotebookをVimから使うとはどういうことか。聞けばVimからJupyter Notebookを操作しているのだという。それができる人間なら、Jupyter Notebookをわざわざ使わずともプログラミング言語の環境構築は簡単にできるはずなのだが、世の中はわからない。

午後の発表になった。Vimの従来のプラグインの機能は、実は今のVim標準の機能で代替可能であることを示す発表があった。Ctrl-Xから始まる各種保管の説明があったが、私は使いこなしていない。

今回の複数の発表によれば、Vimは就職活動に役立つらしい。

vim-historyレポジトリも興味深い。これは1991年にリリースされて27年の歴史を持つVimの更新履歴を単一のgitレポジトリで再現したものだ。Vimは当初、当時の慣習としてtarballで配布され、その後CVSで管理されるようになり、何度かのレポジトリの断絶を経て、今はgitで管理されている。レポジトリの全歴史をgitレポジトリで表現することにより、gitによる様々な操作が可能になる。例えば特定のコントリビューターは何件コミットしているのか。あるコントリビューターの最初のコミットはどれか。などといった、様々な変更の歴史がgitで検索できるようになる。

似たような試みはUNIXにもある。UNIXの歴史をgitレポジトリで再現するプロジェクトがある。

そして、:termdebug機能が言及された。この後の発表はあまり覚えていない。:termdebug機能のあまりの素晴らしさに発表を聞くのがおろそかになってしまったからだ。

:termdebugはVimにデフォルトで同梱されているVimによるGDBのフロントエンドを提供するプラグインだ。使い方は、":packadd termdebug"して、":Termdebug プログラム名"するだけだ。VimはGDBを起動してGDBと通信する。そして、GDBとやり取りするウインドウ、デバッグされるプログラムの標準入出力のウインドウが追加される。現在のウインドウはソースコード表示に使われる。

素晴らしいことに、マウスサポートを有効にしている場合、StepやNextといったボタンが現れ、クリックすら可能になる。そしてGDBに該当のコマンドを送る。

これがすべてVimの中で動くということは、リモートサーバーにsshして動かすことすら可能になるということだ。

しかし、使うとすぐにバグが見つかった。ブレイクポイントはソースコード上で表示されるのだが、break/deleteを繰り返すと存在しないブレイクポイントの表示が消えなくなるのだ。この問題は原因を特定したのだが、最新版のVimでは治っていることが判明した。

もう一つの問題は、Bram MoolenaarがC言語しか想定していなかったための機能不全だ。C++では複数の関数が同じ名前を持つことができる。

void f() {}
void f(int) { }
void f(long) { }

この状態で"break f"とすると、関数fすべてにブレイクポイントが設定される。termdebugはこれに対応していない。

この場合のGDBのブレイクポイント番号の付与は変わっている。"break f"とした場合、1.1(f()), 1.2(f(int)), 1.3(f(long))のようにメジャーブレイクポイント番号と関数ごとにマイナーブレイクポイント番号が付与される。結果としてブレイクポイントは3つできるが、それはすべてブレイクポイント番号1として扱われる。enableコマンドなどは"enable 1.1"のように指定できるが、deleteは指定できない。"delete 1"とするとブレイクポイント番号1に属するすべてのブレイクポイントが削除される。

termdebugはブレイクポイント番号をキーにしてmapでブレイクポイントを管理しているのだが、ひとつのbreakコマンドで複数のブレイクポイントが設定されることを想定していないし、ましてやその複数のブレイクポイントが一つのメジャーブレイクポイント番号に属することを想定していない。そもそもマイナーブレイクポイント番号があることすら想定していない。ブレイクポイント番号をキーにしてmapでブレイクポイントを管理していて、削除時にブレイクポイント番号をキーに検索して削除している。

現在のコードを大幅に変えずに関数のオーバーロードのブレイクポイント表示に対応するのは難しい。HandleNewBreakpointで"1.1", "1.2", "1.3"のようなキーで複数のブレイクポイント番号をキーにしてs:breakpointsに挿入し、HandleBreakpointDeleteで"1.1", "1.2", ...のようにマイナー番号の削除を見つからなくなるまで試みる実装が、とりあえず使えるだろうとは思う。

termdebugは素晴らしい。そして、Termdebugとそのバグのおかげで、Bram Boolenaarと会話ができたという思わぬ副産物もあった。VimconfにBram Moolenaarが来ると聞いて会話をしたいとは思っていたものの、特に話すべき内容は思いつかなかった。なるほど、私はVimを毎日使っているが、Vimの開発に参加しているわけはない。せいぜい挨拶をするのが関の山だと思っていたのだが、Termdebugの存在で会話内容ができてしまった。英語によるスピーキングは普段全くしていないので英語が口から一切出てこないのだが、不思議なことにtermdebugがC++をサポートしていないことについてとその原因についてであれば、英語で説明をすることができた。言語の利用には慣れた文脈が必要のようだ。

termdebugをなぜ今まで知らなかったのだろうと疑問に思っていたが、どうやら今年の7月にVimに入ったばかりのだいぶ新しい機能であるようだ。道理で知らないわけだ。termdebugの存在を知ることができたという理由だけでvimconf 2018のスタッフをしたかいがあった。

その後、もう一枚ホワイトボードが追加され、今度はVimに欲しい新機能のアンケートが行われた。人気の機能はVimscriptの高速化であった。私は、VimがtmuxやScreenのように、シェルからのdisownによるログアウト後の実行継続と、detach/attach機能がほしい。この機能があれば、tmuxはいらなくなる。Vimがtmuxの代わりを務めることができるのだ。リモートサーバーにsshしてvimでテキストを編集し、その編集中のvimの実行を継続したままログアウトし、後にログインして前回の続きから作業を再開したい時、今はtmuxの中でvimを実行しなければならない。これがvimだけで済むようになるのだ。

新機能では端末でエスケープシーケンスによるグラフィック表示をするSixelも人気があった。これは解せないことだ。Sixelはテキスト処理だけで完結するが、グラフィックの表現としては非効率的すぎる。効率を重視するならば、ビットマップデータを直接流し込むようなAPIがほしい。もちろんこれはNULも扱えるようにBLOB型が必要になるだろう。

Vimconf 2018は素晴らしかった。来年も開催されるだろうか。楽しみだ。

ワンナイト人狼とボードゲームの知的財産権について

なぜか一部のボードゲーム作者は、事実の羅列や純粋な思想、新規性も進歩性もない発明であるゲームルールに排他的な独占権を欲しがる。そのような権利が認められた場合、我々は日常会話すら困難になるのだが、そのことに思いが至ることはないようだ。

ワンナイト人狼と同等のルールがオリジナルを考案した我々の許諾なく販売されたと嘆いている。

「太刀打ち」という物騒な言葉まで用いて攻撃的な対立姿勢を明らかにしている。

しかし、本人も認めるように、事実の羅列や抽象的な思想にすぎないゲームルールは著作権では保護されない。特許として認められるほどの新規性と進歩性も満たしていない。

ライセンスというのは排他的な独占権があってはじめて成立するものだ。そのような権利を持たずして一体何を求めているのか。

唯一なにかできるものがあれば、「ワンナイトルール」という商標の有効性についてだけだ。

望む内容に注意せよ。期待通りの結果をもたらさないことがある。

2018-11-18

自転車を買おうか悩んでいる

私は職場から直線距離4kmの場所に住んでいるのだが、電車による通勤は40分ほどかかる。理由は自宅が駅から遠いことと、乗り換えが必要なためだ。

自宅から職場まで歩くと50分かかる。道なりに5kmほど歩くので、6km/hで歩くとそんなものだろう。

これを考えると、自転車通勤をしたほうがいいのではないかと思う。しかし、駐輪場に自転車を止めるのは面倒だ。では折りたたみ式の自転車を使えばいいのではないか。

と考えていると、同僚からCARRYMEを勧められた。これは10万円するとても小さな折りたたみ自転車の自転車だ。実際に載ってみたが、やはりホイール径が8インチでは乗り心地が悪い。するとBROMPTONという自転車を勧めらた。これは20万円する折りたたみ自転車でホイール径も14インチ。なかなか悪くないがまだ乗り心地が悪そうだ。調べたところDAHHONはホイール径が20インチの折りたたみ自転車だ。これはなかなかよさそうだが少し大きい。これならKHSのような普通の自転車を2つに折りたたみましたぐらいの自転車の方が乗り心地がよさそうだ。

しかし、折りたたみ性能と乗り心地は両立できないらしく、どちらかに振らなければならない。そして、10万、20万も出すのであれば、とても乗り心地のいい折り畳めない自転車が買える。であれば駐輪場の手間を考えても普通の自転車を買うべきだろうか。

2018-11-13

C++標準化委員会の2018サンディエゴ会議の結果

2018 San Diego ISO C++ Committee Trip Report (Ranges v1 TS for C++20; consensus on modules design; new Language and Library Evolution Incubators) : cpp

2018年サンディエゴ会議のトリップリポートが公開されている。今回も大きく変わった。

Range

Rangeが入った。Rangeは膨大なのでここでは解説しない。

Yet another approach for constrained declarations

autoと書くべきところをCocept autoと書けるようになった。


template <auto N >
auto f( auto x )
{
    auto y = x ;
}

というコードを、


template < Concept auto N >
Concept auto f( Concept auto x )
{
    Concept auto y = x ;
}

と書ける。

関数の戻り値の型と変数宣言の場合はautoを省略できる。


template < Concept auto N >
Concept f( Concept auto x )
{
    Concept y = x ;
}

こんなところがまだ変わるようでは、まだまだC++20参考書は書けそうにない。書いたそばから変わっていく。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r2.html

必ずコンパイル時に評価されるconsteval関数が追加された。

consteval int f( int x )
{
    return x+1 ;
}

constexpr関数は実行時評価でよい場合は評価を実行時に遅延させてもよいという規定がある。consteval関数は必ずコンパイル時に評価される。

std::is_constant_evaluated

コンパイル時評価されているときにtrueを返すstd::is_constant_evaluatedを追加する。


constexpr double power(double b, int x) {
  if (std::is_constant_evaluated() && x >= 0) {
    // A constant-evaluation context: Use a
    // constexpr-friendly algorithm.
    double r = 1.0, p = b;
    unsigned u = (unsigned)x;
    while (u != 0) {
      if (u & 1) r *= p;
      u /= 2;
      p *= p;
    }
    return r;
  } else {
    // Let the code generator figure it out.
    return std::pow(b, (double)x);
  }
}

これにより、constexpr関数の中にコンパイル時処理と実行時処理を同時に書くことができるようになる。

少し異質なライブラリで、コンパイラーマジックでサポートされるので、ヘッダーファイルに依存せず使うことが可能となっている。

[C++標準化委員でなければ読めない] p1330r0.pdf

unionの有効なメンバーを切り替える処理をコンパイル時定数にする。std::stringやstd::optionalをconstexpr化するのに必要。

p1002r0.pdf

try, catchをコンパイル時処理では無視する。コンパイル時定数への対応ではない。標準ライブラリの多くをconstexpr化するのに必要。将来的に例外をコンパイル時定数に対応する可能性を閉ざすものではない。

Allowing dynamic_cast, polymorphic typeid in Constant Expressions

dynamic_castとtypeidをコンパイル時定数にする変更。すでにコンパイル時にvirtual関数を使えるようになっているため、制限する理由がなくなった。C++20ではC++コンパイラーはコンパイル時に確保されたオブジェクトの型を把握して適切にディスパッチする必要がある。

p1006r1.pdf

std::pointer_traitsをconstexprに対応させる変更。std::vectorをconstexprにするために必要。

今回はまだ入らなかったが、動的メモリ確保も次回あたりにコンパイル時定数になる予定だ。つまりコンパイル時に動的メモリ確保ができるようになる上、その他の例外やらvirtual関数やらunionやらといった処理もすべてコンパイル時定数になるので、std::stringやstd::vectorがそのままconstexprに対応することになる。C++20ではほとんどの処理がコンパイル時定数になる。これは静的リフレクションを入れるために必要な変更だ。

Misc constexpr bits

標準ライブラリのconstexprにできる部分を積極的にconstexprにしていく変更。

P0668R4: Revising the C++ memory model

C++のメモリーモデルの変更。一部のアーキテクチャのとても弱い保証に対応した。一部のアーキテクチャー、PowerやNVIDIAのGPUとARMは、memory_order_seq_cstに対応しつつrelease/aquireに対応できない。memory_order_seq_cstの存在を許すaquire/releaseを実装するためには、よりペナルティの高い強めのフェンスを挿入しなければならない。しかしそのような理論的な問題のためだけに強いフェンスを使いたくはない。そのために、memory_order_seq_cstには対応しない弱いatomic型を追加する。

P1236R0: Alternative Wording for P0907R4 Signed Integers are Two's Complement

符号付き整数型の値の表現は2の補数であることがC++の規格で保証する変更。

char8_t: A type for UTF-8 characters and strings (Revision 5)

UTF-8文字リテラル、UTF-8文字列リテラルの文字の型を表現するchar8_tを追加する提案。私が9年前にC++0xのときに提案したところ、「でもchatは生のバイト列を表現するのに適切な型だからー」と寝ぼけた主張で却下されたにもかかわらず、後になって「やっぱchar8_tにしとけばよかったなぁ」となったので変更された。私には愚痴を言う権利がある。

Nested Inline Namespaces

インライン名前空間をネストで書けるようにする。


namespace lib::container {
    inline namespace v1 {
        namespace node {
        }
    }
}


namespace lib::container {
    inline namespace v2 {
        namespace node {
        }
    }
}

のように中間のinline名前空間を書く際にはC++17に追加されたネストされた名前空間で書けなかったが、


namespace lib::container::inline v1::node {
}

namespace lib::container inline v2::node {
}

のように書けるようにする。

p1289r0.pdf

contractの中ではアクセス指定を無視する変更。

p1007r2.pdf

std::assume_aligned<N>(ptr)の追加。ポインターptrの指すアドレスがNでアラインされていることをコンパイラーにヒントとして与える


// intの配列から合計をSIMD演算で計算する関数
int sum_ints( int * ptr, std::size_t n )
{
    std::assume_aligned( ptr, alignof(int) ) ;    
    // アライメント要求のあるSIMD演算で合計を計算
    return 
}

実際に指定したアライメントになっていることを保証するのはユーザーの仕事だ。std::assume_alignedはアライメントが保証されていると仮定してよいとコンパイラーにヒントを与えることによって、コンパイラーがSIMD演算のようなコード生成を行うときに、アライメント調整用のコードを生成せずに住むようにする。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1085r2.md

spanからoperator ==を取り除く変更。spanをregularにするために必要な変更。

STLの作者Alexander StepanovはC++では型はRegularであるとが重要だと力説した。型がRegularでない場合、もたらす利便性よりも混乱のほうが大きくなる。

コピーコンストラクターとコピー代入演算子は、オブジェクトの「値」をコピーする。

operator ==やoperator <はオブジェクトの「値」を比較する。

型がRegularであるためには、コピーと比較は同じ「値」を比較しなければならない。コピーと比較で「値」の定義が異なっている場合、混乱の元だ。

さて、spanはどうなっているか。spanのコピーはshallowだ。つまり、ポインターとそのサイズがコピーされる。一方、spanのoperator ==はdeepだ。つまり、ポインターの参照する先のストレージが比較される。

spanをRegularにするためには、spanのoperator ==を廃止する。

Smart pointer creation with default initialization

make_unique_default_init<T>/make_shared_default_init<T>を追加する。これはデフォルト初期化されたunique_ptr<T>/shared_ptr<T>を返す。

size_t型の引数nを取るものもあり、こちらはunique_ptr<T[]>/shared_ptr<T[]>を返す。


// デフォルト初期化されたint型の値の
// std::unique_ptr<T>
auto p = std::make_unique_default_init<int>() ;
// それぞれデフォルト初期化されたint型の値で要素数が5の
// std::unique_ptr<T[]>
auto a = std::make_unique_default_init<int>(5) ;

C++標準化委員会では、特定の分野について議論するStudy Groupが設置されるが、今回、新しいStudy Groupとして、SG19 Machine LearningとSG20 Educationが追加された。SG19は名前だけみると機械学習についてで、SG20は前から作ると宣言されていた教育に関するSGだ。

C++を発展させるEvolution Working Groupでは以下のような興味深い議論があった。

モジュールの中でmain関数を定義できる提案と、プログラムにデータを埋め込むstd::embed提案はより深い議論とフィードバックが必要だとされた。

void main提案は却下された。

興味深いのは、operator []の中の operator , の利用をdeprecatedにしようという決定だ。


int a[5] ;
a[1,2] ; // a[2]と同じ

このコードがdeprecated扱いになる。operator []の中のカンマは、多次元配列を実装するための何らかの新しい機能として予約される。

short float提案についてコンセンサスは得られなかった。

std::colonyはもっと作業が必要だとされた。

設計的には賛同できるのでC++20に追加する方向で進めるライブラリとして、テキストフォーマット(std::format)、スタックトレースライブラリがある。

SG13 Graphics Study Groupではオーディオに関する興味もあるらしい。またweb_viewに対するさらなる作業を推奨する雰囲気だ。

今後の予定としては、2019年春のKona会議でFeature freezeをし、2019年夏のドイツのCologne会議でCommittee Draftの文面を完成させる。つまり来年の半ばにはC++20の概要は決定するわけだ。

モジュールはおそらくC++23以降に延期される。コルーチンやExecutorも延期される。ネットワークライブラリはおそらくC++26以降になるだろう。

2018-11-10

かつてPSエミューレーターにスラップ訴訟を仕掛けたソニーのPSクラシック、自由ソフトウェア実装のPSエミュレーターであるPCSX ReARMedを使っていることが判明

かつてPSエミュレーターをスラップ訴訟により嫌がらせをして事実上の販売停止に追い込んだ邪悪なソニーが販売するPSクラシックには、自由なソフトウェア実装のPSエミュレーターであるPCSX ReARMedが使われていることが判明した。

Kotakuによるレビューによれば、PSクラシックの使用する自由ソフトウェアのライセンス表記の一覧にPCSX ReARMedが確認できたという。

PCSXは自由ソフトウェアによるPSエミュレーター実装で、2000年に公開された。その開発は停滞したが、2006年にPCSX-dfとしてforkされた。またPCSX-Revolutionいうforkもあった。2009年にはこの2つのforkを参考にPCSX-Reloadedいうforkも行われている。

PCSX ReARMedはPCSX-Reloadedのforkで、PCSXをARMアーキテクチャに移植する目的で開発されている。

さて、ソニーはPSエミュレーターに対して悪名高いスラップ訴訟を仕掛けてきた歴史がある。

Mac用のPSエミュレーター実装であるConettixのVirtual Game Stationの販売を著作権侵害のスラップ訴訟を起こして差し止めようとした。これは最終的にソニー側に不利な和解で終わっているが、その間VGSの販売が差し止められた。

また、Bleem CompanyによるPSエミュレーターBleem!を著作権侵害と不正競争防止法によりスラップ訴訟を起こして差し止めようとした。この訴訟でソニーが完全に敗北している。PSエミューレーターは不正競争防止法に反しないばかりか、宣伝に使ったPSゲームのスクリーンショット利用すら、著作権法に照らし合わせて正当な引用であるとの当然の判決が下った。しかし、物語はハッピーエンドには終わらない。長引く訴訟により膨れ上がった訴訟費用に耐えかね、Bleem Company は倒産。結果的に邪悪なソニーはBleemの販売を事実上差し止めることに成功した。

その悪名高い札付きのソニーが当時の愚かな行いに対する謝罪もなく、何食わぬ顔で自由ソフトウェアを使ったPS互換機を販売するとは、恥知らずにも程がある。ソニーはPSクラシックの販売にあたって、過去の過ちを認め、公に謝罪するべきである。

結局、抵抗は無意味であり自由ソフトウェアが勝利するのだ。

参考文献:

PlayStation Classic Plays Fine, But It’s A Bare-Bones Experience

Sony using open source emulator for PlayStation Classic plug-and-play | Ars Technica

Sony to sue Connectix over PlayStation emulator • The Register

Connectix Virtual Game Station - Wikipedia

Bleem! - Wikipedia

PCSX-Reloaded - Wikipedia

PCSX ReARMed

2018-10-29

帰ってきたきれいなリーナス・トーバルズ、無作法な開発者をたしなめる

Linus Torvalds Shows His New Polite Side While Pointing Out Bad Kernel Code - Phoronix

人の心の読み方を学んで復帰したリーナス・トーバルズは、さっそく無作法なプルリクエストをたしなめている。その文章は大文字センテンスも4文字言葉も使っていない優しいものに変わっている。

問題はプルリクエストはBigBenゲームコントローラーに対するドライバーの追加で、このドライバーはデフォルトで有効にされていた。これはLinuxカーネルの慣習にそぐわないものだ。新しく追加された名前もきいたこともないようなデバイス用のドライバーが、いきなりカーネルでデフォルトで有効にはされないものだ。新参者のドライバー開発者は、大抵自分のドライバーはとても重要で、自分の所有しているデバイスは全員所有しているのでデフォルトで有効にするのは当然だと傲慢にも考えている。このような既存の慣習に従わない傲慢な態度は、通常、リーナス・トーバルズによって発見され次第、罵詈雑言を持って鼻っ柱を叩き折り、厳しくしつけられる。

それがなんと以下のように穏やかなメールになっている。

新しいポッと出のドライバーがデフォルトで有効にされることは「ない」。そのデバイスが聞いたことのないような名前の場合は「特に」ありえない。

にもかかわらず、このマージ期間で新しく追加された「BigBenインタラクティブ」ドライバーとやらはまさにそういうことをしている。

そういうことはするな。

分かってる分かってる。開発者は全員、自分のドライバーはとても特別で魔法のように重要なので、当然デフォルトで有効にされるべきだと考えているものだ。だが違う。何千ものドライバーがあるなかで、ポッと出の新しいドライバーが、一部の開発者が特別だと思っているという理由だけで、デフォルト有効にされることはない。

そういうわけで、コミット256a90ed9e46 ("HID: hid-bigbenff: driver for BigBen Interactive PS3OFMINIPAD gamepad")の

default !EXPERT

は完全に間違いだ。プリーズこういうことはしないでくれ。

リーナス

比較のために、去年の11月に似たような問題をたしなめたリーナスのメールを見てみよう。

テメーは新しいドライバーを追加してデフォルトonにしただと。

(大文字)コイツァゼッテーに受け付けらんねーッ。

何でこの俺が直々にマージ期間のたびに毎回言わなくちゃなんねーんだよ。だがもっかい言ってやるぞ。

開発者として、テメーは自分のドライバーとか機能が超絶最高に重要なものだと思ってて、しかもテメーは対応するデバイスも持っているわけだ。

(大文字)だがそんなのァ誰も気にしちゃいねぇんだよ。

これ読んで泣きベソでもかいとけ。オメーのハードウェアが超絶に普及してない限り、全員の設定でデフォルトでデフォルト化されているべきじゃねぇ。

(一部単語が大文字)このブランチで追加されたすべての"defult"の行は間違ってる。

こういうことはやめろ。みんなの期待を裏切る行為だぜ。俺が"make oldconfig"したとき、ポッと出の新しいハードウェアドライバーが有効にされてほしくはない。

もう一つリーナスの忍耐力を試すイベントがあった。リーナス様の環境がKernel oopsをお出しになったのだ。

俺のラップトップが今のgitツリーでカーネルページフォルトを出した。今の所怪しいコミットは

9ee3e06610fd ("HID: i2c-hid: override HID descriptors for certain devices")

これだが、そう思った理由は単に今回のマージ期間でこの辺をいじってるコミットがこれしかないからだ。

oopsは以下の通り

(省略)

だから俺は新しいi2c_hid_get_dmi_hid_report_desc_override()のコードを疑ってるわけだ。

思うに問題はi2c_hid_dmi_desc_override_table[]がNULLエントリーで終端されていないことなので、今からテストする。

この問題はすごく悲しい。これはつまりこのコードは文字通り誰にもテストされてないってことで、誰もこのリストにエントリーを入れてないってことだ。

ちなみに以前、リーナスがパッチを適用したカーネルをブートするだけで確実にカーネルoopsを発動させるような初歩的なミスを発見した場合は、大文字で「テストされてないクソ」となじり、罵詈雑言あふれるメールが飛ぶものだ。

2018-10-24

C++標準化委員会の10月の興味深い文書

2018-10のC++標準化委員会の文書集が公開されていたので、興味深い新機能の提案に限って紹介する。

secure_val

セキュリティ上の理由でメモリの内容を破棄したい場合、コンパイラーの最適化によって意図通りのコードが吐かれないことがある。


void decrypt()
{
    char password[64] ;
    // パスワードを取得
    get_password(password) ;

    // 複合処理
 
    // パスワードをメモリから破棄
    std::memset( buffer, 0, 64 ) ;
}

このコードでは最適化の結果memsetが省略されるかもしれない。なぜなら、bufferはmemsetの後に使われていないから、memset自体が不要だとコンパイラーは判断できるからだ。

そのために、最適化によって消えずに値を消去できるライブラリを提供する。


void secure_clear( void * data, size_t size ) noexcept ;
template < typename T >
void secure_clear( T & object ) noexcept ;

この提案はさらに、secure_val<T>クラスを提案してる。secure_valはT型を保持するクラスで、デストラクターはT型をセキュアに破棄する。T型の値にアクセスする方法は関数オブジェクトを指定するもので、コピーを許さない。


void decrypt()
{
    std::secure_val<char [64]> val ;

    val.access([]( auto & data )
    {
        get_password( data ) ;
    } ) ;

    // 複合処理

    // valのデストラクターがセキュアにメモリ上の値を廃棄
}

unique_val

secure_valに似ているunique_val。これはT型をコピーせずにムーブするクラスだ。

ムーブしたあとのT型はデフォルト初期化された値になる。

ポインターやシステムリソースへのハンドルを扱うのに使える。

Remember the FORTRAN - p1300r0.pdf

C++に現在提案されているモジュールの実験的な実装はFORTRANが30年間解決できなかったパフォーマンス上の課題を抱えていると警鐘を鳴らす文書。

GCCのモジュール実装では、モジュールのソースファイルをコンパイルして.nsmファイルを作成しその後そのモジュールをimportするソースファイルをコンパイルすると、該当する.nsmファイルを使う。

FORTRAN-90はモジュール機能を備えており、これは今のC++のモジュールと原理的に同じ機能を同じ実装で提供している。つまりFORTRANは30年前からモジュール機能がある。

あるモジュールをimportするソースファイルをコンパイルするためには、事前に依存するモジュールをコンパイルしなければならない。モジュールは別のモジュールをimportできる。つまりモジュールをコンパイルするには事前に依存する別のモジュールをすべてコンパイルしなければならない。

これはビルドシステムととても相性が悪い。ビルドシステムが複数のソースファイルからなるプログラムをビルドするとき、モジュールの依存関係を把握してDAGを構築し、適切な順序でモジュールをコンパイルしなければならないということだ。つまりビルドシステムはC++ソースファイルを解釈する必要がある。

IntelのFORTRANコンパイラーのマニュアルは「プログラムが依存するモジュールのファイルは事前に生成しておけ」という。Intelという超巨大で世界的な大企業ですら、莫大な利用料を支払う顧客に対して、「依存してるモジュールは事前に全部コンパイルしとけよ」ぐらいの助言しか与えられていないのだ。

著名なGNU Fortran開発者ですら、Fortranのプログラムをビルドするときは、「Makeを成功するまで十分な回数実行する」と言っている。

モジュールを正しくコンパイルするにはモジュールの依存関係の解析が必要で、そのためにはソースファイルの解釈が必要になる。Makeやninjaのような汎用的なビルドシステムにC++を解釈する機能をつけることは現実的ではないので、C++コンパイラーが依存関係を解決する機能を提供するようになるだろう。

ところで、Windowsはプログラムを起動するパフォーマンスが極めて悪い。1ソースファイルごとにC++コンパイラーを1回起動して依存関係を解決するような実装はWindowsではパフォーマンスが悪い。

不自由で低能なWindowsはプロセスの作成もスレッドの作成も遅いし、ましてやファイルの作成に至っては、i7でNVMeのSSDを積む高性能なコンピューター上で動くWindows 10がRaspberry PiとSDカード構成のRaspbianにすらパフォーマンスで負けるという信じられないほどの低能を誇っている。

Benchmarking OS primitives – Bits'n'Bites

そして、ソースファイルの一部だけを変更した後の部分的なビルドですら、モジュールの依存関係が変わるかもしれないので依存関係の解決が必要だ。

モジュールはビルド時間を削減するべきであって、ビルド時間を増やすのは本末転倒だ。

FORTRANが30年かかっても解決できていない問題は解決できそうにない。

P1283R0: Sharing is Caring

Linuxのshared libraryやWindowsのDLLのためにエンティティをexportする属性、sharedの追加の提案。

P1282R0: Ceci N’est Pas Une Pipe: Adding a workflow operator to C++

新しいワークフロー演算子として<|と|>を追加する提案。


// 右から左
a <| b ;
// 左から右
a |> b ;

C++20にはレンジやExecutorやコルーチンやモナドが提案されているがこれらの提案は右から左、もしくは左から右といった処理の流れを記述する。例えば以下は1から始まる整数列を生成し、奇数だけをフィルターした整数列にし、その先頭から5個だけを取り出した整数列にするレンジのコードだ。


iota(1) | filter(odd) | take(5) ;

これは処理の流れがわかりにくい。すでにoperator >>はあるが、これは演算子の評価順序の関係で使えない。operator >>=なら使えるがこれは汚い。

そこでオーバーロード可能なワークフロー演算子を追加する。


iota(1) |> filter(odd) |> take(5) ;

これはほしい。

P1281R0: Feature Presentation

本当の条件付きコンパイル機能の提案。

constexpr ifは条件付きコンパイルではなく、条件付きテンプレート実体化抑制機能だ。そのために意味上エラーとなるコードを書くことができない。

この提案では、条件付きコンパイル機能を提供する属性により、文法上正しいが、意味上エラーとなるコードを書けるようにする。


[[ feature("key")]] 

[[feature()]]はキーとなる文字列を受け取る。このキー文字列が宣言されていないか、ブロックリストに入っている場合は、その属性がある宣言とその中身がASTから取り除かれる。

利用例は以下の通り。


struct [[feature("vulkan")]] Device {
  [[feature("glsl-to-spirv")]]
  static Shader compile(std::filesystem::path filename);
  static Shader load (std::filesystem::path spirv_file);
};

struct [[feature("direct-x")]] Device {
  [[feature("hlsl-to-spirv")]]
  static Shader compile (std::filesystem::path filename);
  static Shader load (std::filesystem::path spirv_file);
};

いまグラフィックAPI用のライブラリを書きたいとする。このライブラリはVulkanとDirectXを両方サポートする。ただしコンパイル時の環境では、VulkanかDirectXのどちらかしか提供されていない。上のようなコードで、"vulkan"か"direct-x"のどちらかのキー文字列だけを宣言することで、2つのDeviceクラスの実装のうち、どちらか片方だけを有効にできる。無効な属性のクラスはまるごとASTから取り除かれる。

さらにこのライブラリはシェーダー言語であるGLSLやHSSLからSPIR-Vへの変換機能を提供するが、条件次第ではこの機能を提供しないことも選択できる。

文法上妥当である必要があるので、比較的穏当な条件付きコンパイルだ。#ifdefはいずれ廃止したいものだ。

P1280R0: Integer Width Literals

ビット長を指定した整数型intN_tとuintN_tに対するユーザー定義リテラルとして、operator "" iNとoperator "" uNを追加する提案。


using namespace::literals ;

auto a = 0i16 ; // std::int16_t
auto b = 0i32 ; // std::int32_t

auto c = 0u8 ; // std::uint8_t
auto d = 0u64 ; // std::uint64_t

便利だ。

P1279R0: std::breakpoint

プログラムからブレイクポイントを設定できるライブラリstd::breakpointの提案。


using namespace std::literals ;

int main()
{
    std::breakpoint() ;
    std::cout << "hello"sv ;
    std::breakpoint() ;
    std::cout << "world"sv ;
    std::breakpoint() ; 
}

P1278R0: offsetof For the Modern Era

std::offsetofの提案。offsetofはマクロでstandard layout classにしか使えない。std::offsetofはstd::bit_castで実装できるが、std::bit_castはconstexprではない。std::offsetofはconstexprになるべきだが、議論が必要だ。

P1277R0: Subscripts On Parade

operator []で複数の引数を取れるようにする提案。


struct S
{
    int & operator [] ( int a, int b, int c ) ;
} ;

int main()
{
    S s ;
    s[1,2,3] = 1 ;
}

多次元配列ライブラリが提案中だが、多次元配列へのアクセスをできるだけ直感的に書けるようにしたい。

P1276R0: Void Main

void mainを認める提案。すでにmain関数は空のreturn文を認めていて、その場合は0を返したものとみなされる。ならばvoid mainも認めてよいはずだ。

P1275R0: Desert Sessions: Improving hostile environment interactions

C++にプログラムの引数の参照と、環境変数を参照、変更できるライブラリを追加する提案。C++風にイテレーターでアクセスできる。

P1274R0: Bang For The Buck

識別子としてダラーサイン($)を認める提案。さらに、識別子の最後に限り驚嘆符(!)と疑問符を(?)を認める。

これにより"set!"や"empty?"のような関数名も使えるようになる。

$は静的リフレクションのキーワードreflexprの代わりに使えるようにすべきだという声もあるが、著者は識別子として使えるようにしたほうが有益だと主張している。

私としては$はreflexprの代わりに使いたい。jQueryのように・・・というと縁起が悪いが。

Pattern Matching - p1260r0.pdf

パターンマッチの提案。文法は比較的穏健。

整数の例


// Before
switch (x) {
    case 0: std::cout << "got zero";
    case 1: std::cout << "got one";
    default: std::cout << "don't care";
}

// After
inspect (x) {
    0: std::cout << "got zero";
    1: std::cout << "got one";
    _: std::cout << "don't care";
}

文字列の例


// Before
if (s == "foo") {
    std::cout << "got foo";
} else if (s == "bar") {
    std::cout << "got bar";
} else {
    std::cout << "don't care";
}

// After
inspect (s) {
    "foo": std::cout << "got foo";
    "bar": std::cout << "got bar";
    _: std::cout << "don't care";
}

tupleの例


// Before
auto&& [x, y] = p;
if (x == 0 && y == 0) {
    std::cout << "on origin";
} else if (x == 0) {
    std::cout << "on y-axis";
} else if (y == 0) {
    std::cout << "on x-axis";
} else {
    std::cout << x <<','<< y;
}

// After
inspect (p) {
    [0, 0]: std::cout << "on origin";
    [0, y]: std::cout << "on y-axis";
    [x, 0]: std::cout << "on x-axis";
    [x, y]: std::cout << x <<','<< y;
}

他にもvariantの例、ポリモーフィック型の例、式を評価する例がある。

pattern_matching

別のパターンマッチの提案。こちらはどの関数型言語からやってきたんだというぐらい既存のC++にそぐわない異質な文法になっている。

enumの例


enum color { red, yellow, green, blue };

// Before
const Vec3 opengl_color = [&c] {
  switch(c) {
    case red:
      return Vec3(1.0, 0.0, 0.0);
      break;
    case yellow:
      return Vec3(1.0, 1.0, 0.0);
      break;
    case green:
      return Vec3(0.0, 1.0, 0.0);
      break;
    case blue:
      return Vec3(0.0, 0.0, 1.0);
      break;
    default:
      std::abort();
  }();

// After
const Vec3 opengl_color =
  inspect(c) {
    red    => Vec3(1.0, 0.0, 0.0)
    yellow => Vec3(1.0, 1.0, 0.0)
    green  => Vec3(0.0, 1.0, 0.0)
    blue   => Vec3(0.0, 0.0, 1.0)
  };

あまりにもC++として異質すぎる。

クラスに対するパターンマッチの例


struct player {
  std::string name;
  int hitpoints;
  int lives;
};

// Before
oid takeDamage(player &p) {
  if(p.hitpoints == 0 && p.lives == 0)
    gameOver();
  else if(p.hitpoints == 0) {
    p.hitpoints = 10;
    p.lives--;
  }
  else if(p.hitpoints <= 3) {
    p.hitpoints--;
    messageAlmostDead();
  }
  else {
    p.hitpoints--;
  }
}

// After
void takeDamage(player &p) {
  inspect(p) {
    [hitpoints:   0, lives:0]   => gameOver();
    [hitpoints:hp@0, lives:l]   => hp=10, l--;
    [hitpoints:hp] if (hp <= 3) => { hp--; messageAlmostDead(); }
    [hitpoints:hp] => hp--;
  }
}

あまりに既存のC++の文法とは違いすぎる。

p1235r0.pdf

implicit constexprの提案。

constexpr関数の制約は今後ますます減っていき、コンパイル時定数式にしたい処理は今後ますます増えていく。

C++17ではlambda式のoperator ()は暗黙にconstexprだ。この結果、以下の同じように見えるコードの挙動が異なる。


int f( int x ) { return x ; }
auto g = [](int x ) { return x ; }

// Error
constexpr int a = f(0) ;
// OK
constexpr int b = g(0) ; 

そこですべての関数を暗黙にconstexprにしてしまおうというのがこの提案だ。

C++ Compile

C++のコンパイル方法を標準化しようという提案。C++の教育SGが提唱されたり、パッケージシステムも議論される中、必要な提案ではあると思うが、果たして受け入れられるだろうか。

この提案では、コンパイラーのオプション指定は+で指定する。長いオプションは++で指定する。

cpp hello.cpp ++output=hello

まず<compile>ヘッダーにコンパイラーを呼び出すライブラリが追加される。


namespace std
{
  int compile(int, char * *) noexcept;
  int compile(vector<string>) noexcept;
}

一つ一つの文字列がargumentとして処理される。+で始まらないargumentはソースファイル名だ。

+で始まるのはコンパイル時のオプションで、例えばヘルプメッセージの出力、出力ファイル名、デバッグといったC++コンパイラーによくあるオプションを定義している。

コンパイル方法を標準化することによって、教育や提案中のパッケージシステムで使いやすくなる。

ファイルシステムライブラリも標準C++にある今、C++ライブラリとしてのビルドシステムという不思議な概念が浮かんだ。

p1177r0.pdf

パッケージシステムの全容の提案、コンパイラーAPI、ビルドシステムAPI、パッケージ依存解決API、パッケージ検索APIによって構成される。

P1214R0: Pointer to Member Functions and Member Objects are just Callables!

メンバーへのポインターをINVOKEのように振る舞わせる提案。


struct Foo
{
    int data ;
    int func( int x ) { return x ; }
} ;

int main()
{
    int (Foo::*data_ptr) = &Foo::data ;
    int (Foo::*func_ptr)(int) = &Foo::func ;
    Foo obj ;

    // Before
    obj.*data_ptr = 123 ;
    (obj.*func_ptr)(123) ;

    // After
    data_ptr(obj) = 123 ;
    func_ptr(obj, 123) ;
}

ジェネリックコードで大量の特殊化を書く必要がなくなる。

p1227R0: Signed size() functions

符号付き整数を返すsize関数としてssizeの提案。

std::vector<int> v ;
for ( int i = 0 ; i < v.size() - 1 ; ++i )
{ }

このコードは"0u-1"を実行してしまうので意図通り動かない。ssizeがあれば、"v.ssize() - 1"と書ける。

P1108R1: web_view

画期的に使いやすいグラフィックライブラリ、webviewの提案の改定案。変更点としては議論が追加されている。これはHTMLとCSSをブラウザーで表示し、JavaScriptを注入できる極めて簡単なライブラリだ。

2018-10-23

SQLiteの行動規範がキリスト教徒の戒律を全文引用していて香ばしすぎる

Code Of Conduct

SQLite creator crucified after code of conduct warns devs to love God, and not kill, commit adultery, steal, curse... • The Register

SQLiteの行動規範(Code of Conduct)は大真面目に西暦500年の聖ベネディクトの作った72か条の戒律を全文引用している。この行動規範はSQLiteの利用者が同意する必要はないが、開発者は同意しなければならない。結果としてSQLiteは危険すぎるので使ってはならない。なぜならばSQLiteの開発者は全員キリスト教原理主義者で奴隷制度を肯定し非武装でキリスト教徒に改宗すらさせた敵をジェノサイドしたカルト宗教の信奉者であり、キリスト教の教義に合わないコードは書かず、異教徒を攻撃するだろうからだ。

聖ベネディクトが西暦500年ごろに作った72か条の戒律のほとんどは現代の価値基準に照らし合わせて香ばしい。

  1. まず第一に、主たる神をまつたき心、まつたき魂、まつたき力を以って愛せよ
  2. しこうして、汝の隣人を汝がごとく愛せよ
  3. 殺すなかれ
  4. 姦淫するなかれ
  5. 盗むなかれ
  6. 欲するなかれ
  7. 偽証するなかれ
  8. 皆を敬え
  9. 汝自身にせぬことを他人にするなかれ
  10. キリストに従うために汝自身を否定せよ
  11. 汝自身を折檻せよ
  12. 喜びに耽るなかれ
  13. 断食を愛せよ
  14. 貧者を慰撫せよ
  15. 裸人に服を与えよ
  16. 病人を訪れよ
  17. 死人を埋葬せよ
  18. 艱苦を救え
  19. 嘆きを慰撫せよ
  20. (訳注:異教徒どもの罪深い)世界とは手を切れ
  21. キリストへの愛のほかには何も好むな
  22. 怒りに身を任せるな
  23. 遺恨を養うな
  24. 心に偽りを抱くな
  25. 偽りの和平を結ぶな
  26. 寄進を惜しむな
  27. 呪うな、汝自身を偽るがゆえに
  28. 心からの真実のみを口にせよ
  29. 邪悪に邪悪をもって返すな
  30. 他人に邪ならずして、汝への邪は甘んじて受けよ
  31. 汝の敵を愛せよ(訳注:敵の存在も神の思し召しなるがゆえに)
  32. 汝を呪うものを呪うな。祝福せよ
  33. 正義のために懲罰を受け入れよ
  34. 誇るなかれ
  35. ワインを痛飲するなかれ
  36. 鯨飲馬食するなかれ
  37. 寝ぼけるなかれ
  38. 怠けるなかれ
  39. 愚痴るなかれ
  40. 中傷するなかれ
  41. 望みを神に託せ
  42. 汝にかかる良きことは汝の功にあらずして神の功とせよ
  43. 己の行いは邪にして己の責に帰すことを思え
  44. 審判の日を恐れよ
  45. 地獄を恐れよ
  46. 全身全霊で永久の命を欲せよ
  47. 毎日死を見つめよ
  48. 人生における行動に常に気をつけよ
  49. 神はすべての場所を見張っていることを確実に知れ
  50. 邪なる思いが胸中に生ずるときはただちにキリストに帰依し、精神の父なることを認識せよ
  51. 邪なる言葉を口にするなかれ
  52. 饒舌を愛するなかれ
  53. うぬぼれや笑いをもたらす言葉を話すなかれ
  54. やかましい笑いと行き過ぎを愛するなかれ
  55. 聖書の朗読を積極的に聞け
  56. 頻繁に祈れ
  57. 祈りにおいては毎日己が神に対する罪に落涙嘆息し、かかる邪を将来に償うことを思え
  58. 肉の欲望を満たすなかれ
  59. 己が意思を憎め
  60. 神が権威を託した者の命にすべて従え。たとえ権威者が自ら率先せずとも従え(そは神の禁じたもうことなり)。主のかの言葉を思い起こせ「他人の言葉を汝行い、他人の行いを汝行うなかれ」と
  61. 己の至らぬのに聖人と呼ばわるることを望まずして、呼ばわるるところの聖人たらんとせよ
  62. 神の戒律を日々満たせ
  63. 禁欲を愛せよ
  64. 誰をも嫌うな
  65. 嫉妬羨望するなかれ
  66. 諍いを好むなかれ
  67. 傲慢を避けよ
  68. 目上を敬え
  69. 目下を愛せよ
  70. 汝の敵にキリストの憐憫を祈れ
  71. 日没までに汝の反対者と和平せよ
  72. 神の慈悲に悲嘆するなかれ

2018-10-22

江添誕生日ボドゲ会@10月28日

江添誕生日ボドゲ会@10月28日 - connpass

10月は誕生日なので週末の28日に昼からボドゲ会、夕方からすき焼き会をすることにした。参加表明はconnpassから行える。

2018-10-14

消費税について考えたこと

「消費税10%は100円が110円になると考えるのではなく、1000万円が1100万円になると考えるべきだ」というつぶやきを目にした。しかしこれは言っていることが同じだ。

私はむしろ、消費税とは手取りに対する税金ではないかと考えている。貯蓄をしない家計では、消費税は実質手取りに対する税金だ。貯蓄をしない家計にとって、消費税10%とは、手取り20万円が18万円になることだ。

もっとも、この考え方にも問題はある。というのも貯蓄をしない家計は賃貸住宅に住んでいるだろうが、不動産の貸付料である家賃には消費税はかからない。手取り20万円の家計で家賃6万円の場合、14万円が12万6千円になるということだ。

消費税と言えばイタリアは先進国だ。1973年という昔から12%の消費税を導入し、1997年には20%に引き上げられた。そのイタリアでは、表に出ず消費税を脱税した地下経済とマフィアが発展した。

消費税が10%ともなると、日本でも地下経済と、地下経済の信用取引を履行させるためのヤクザが発展するだろうか。

日本ではヤクザの人権を剥奪する法律によりヤクザの人口が急激に減りつつある。また個人間の送金手段は経済に悪影響を及ぼすほど厳しく規制しているので地下経済も発展しにくい。

とはいえ、消費税があまりにも高くなれば地下経済は発展するだろうし、地下経済が発展したならば、その信用取引を確実に履行させるための機関としてヤクザが必要になり、ミカジメ料を支払うようになるのでヤクザも再び興隆するだろうか。

ということを考えながら、消費税制度の解説を眺めていたところ、興味深い定義を発見した。

「郵便切手類、印紙、証紙、物品切手の譲渡は非課税取引」

興味深い定義だ。すべての取引が地下経済に潜ることはできない以上、我々は表の取引もする。であれば、一部の取引を切手や印紙による取引とするのは現実的ではないだろうか。素晴らしいことに地下経済にする必要すらない。単なる非課税取引をしたにすぎないのだから。

具体的に考えよう。君は牛を二頭持っている。今君は牛を一頭売る契約をしたので契約書作成の税金を収めるため収入印紙を貼る必要がある。君は残りの牛一頭のミルクを売って現金を得て消費税を払い、残りの現金で収入印紙を買う。もし、君がミルクと収入印紙を直接交換したならば、消費税を支払う必要はない。

すばらしい。我々は現金ではなく収入印紙で支払うことによって消費税を節税することに成功した。消費税を支払わない分、ミルクの値段を下げるか、利益を上げることができる。

こう考えると、消費税の存在は貨幣経済に対する足かせであるように思えてくる。

しかし収入印紙は日本国に対する支払いにしか使えない用途の限定された通貨だ。現金という汎用的な通貨ではない。この問題をどうすればいいのだろう。

収入印紙には需要がある。需要があるものは汎用的な通貨である現金と交換できる。例えば金券ショップでは、収入印紙は額面の95%ぐらいで交換できる相場だ。ところで今の消費税は8%である。収入印紙で取引して収入印紙を現金化した場合の損失が、消費税率を下回る場合、これは得をしたと言えるのではないか。しかも合法だ。つまり収入印紙での取引は合法的な節税になるのではないか。

私は経済学にも消費税制度にも詳しくないのだが、この解釈は正しいのだろうか。

2018-10-07

Steven WeinbergのTo Explain The Worldを読んだ

邦訳では「科学の発見」という題名がついているSteven WeinbergのTo Explain The Worldを読んだ。

著者のSteven Weinbergはノーベル物理学賞の受賞者でイスラエルの熱烈な支持者だ。

冒頭で「本書で筆者は過去を現代の基準で批判する愚を犯す」と書きながら、この本は科学がどのように発展してきたかを解説している。

本書はまず、古代ギリシャにおける万物を構成する元素の説について取り上げる。古代ギリシャの哲学者が、元素は水だとしたり、火だとしたり、水、火、土、空気の4種類だと主張している歴史を取り上げる。

ここまではまあいいとして、プラトンの提唱した説を取り上げて悶絶する。プラトンは四大元素である火、水、土、空気について、とても小さい元素が存在し、5種類ある正多面体をそれぞれ割り当てた。たとえば火は正四面体で、水は正二十面体といった具合だ。その仮説を立てるのはいいとして、割り当ては一体どういう理由で行われたのか。プラトンは単にそれが理想だからとしか語らない。

科学というものは、まず観測し、観測結果から仮説を立て、仮説が観測結果に従うことを検証するものだ。

しかし古代ギリシャでは、科学は科学ではなく哲学で、単に理想を追求するものでしかなかった。なので万物を構成する元素は元素は火や水といったわかりやすい理想的なものになり、元素は5種類ある正多面体であるとし、完全数を尊びといった、現実の観測結果より理想を追求し、理想が現実に従わないことは無視されていた。

その後、本書の大半は天体の運行の予測を通じた科学の発展に費やされる。

天体の運行には規則性があり、古代から占星学などの存在により、天体の運行を正確に予測する需要があった。

古代ギリシャ時代から地球が中心で月、惑星、太陽、その他の恒星は全て地球の周りを回っている説が主流であった。

古代ギリシャ時代にはすでに地球を中心とした天体の運行を予測する数式モデルが考案されていたが、他の天体はすべて地球を中心とした真円で回るなどと定義されていたため、現実の観測とは大いに異なっていた。しかも、それぞれの天体が謎の理由で割り当てられた正多角形に内接する真円で回るなどという、これまたプラトンのように理想を追求したモデルであった。

天体は地球を中心に真円で回転するモデルを使うと、惑星はある時期だけ逆方向に回りだしたりする。惑星が大抵の言語で語源からして、惑う星であるのも、これが原因だ。

そこでプトレマイオス説が考案された。この説では、天動説で現実の観測結果に近似させるために、あまりにも無理やりな天体のモデルを考案した。天体は太陽を中心に真円で回っている。この真円を従円と呼ぶ。天体は従円を線上を中心としてさらに真円で回っている。この真円を周転円と呼ぶ。

プトレマイオス説は複数の真円を組み合わせ、真円の大きさをパラメーター化することで、惑星の運行を現実に近似させることを試みた。その結果、かなりの制度で現実の観測に近似した天体モデルを作ることに成功した。

プトレマイオス説は科学だ。観測から観測結果に従う数式モデルを作ったわけで、これは科学と言える。

プトレマイオス説は哲学者からは理想ではなく天体の予測のための方便であるとされていた。天文学者(この時代の天文学者は占星学をかねる)は積極的に使っていた。というのも、プトレマイオス説は現実の観測結果の近似していて、予測に役立つからだ。

その後、様々な天文学者がプトレマイオス説により複雑な周転円を追加することで、さらに現実の観測結果に近づける努力が行われた。

太陽を中心として天体が公転している地動説は以前にも理想として提唱されたことはあったが、現実の観測結果を説明できる具体的な数式モデルはコペルニクスによってまず作られた。ただし、コペルニクスの数式モデルは精度が悪かった。というのも、コペルニクスは天体は太陽を中心に真円で公転していると定義したが、実際には太陽は中心ではなく、天体の公転軌道は真円ではないからだ。

本書は中東の科学についても解説している。中東の科学はイスラム教の普及によって妨害されるまでヨーロッパより進んでいた。ギリシャ時代の学説は中東からもたらされる形でヨーロッパに再発見された。その後イスラム教の普及により中東の科学の発展は阻害された。

そしてガリレオ・ガリレイの時代までやってくる。ガリレオ・ガリレイは優秀な望遠鏡を発明し、望遠鏡によって精密な天体観測をした。その結果地動説を唱えるわけだが、その上でパトロンであるローマ教皇を天動説を信じているなどと批判したので宗教裁判にかけられて地動説を封印する。後世に残る逸話によれば、このときガリレオは「それでも地球は回っている」とつぶやきながら法廷を後にしたと言われる。

宗教裁判の後、ガリレオは晩年まで自宅に軟禁状態におかれるわけだが、ガリレオの科学への情熱は冷めていなかった。ガリレオは落下する物体の速度について研究していた。落下する物体の速度を観測するのは当時としては難しい。というのも、速度が速すぎるために正確な観測ができないためだ。

ガリレオはこの落下速度の問題に対し、科学的な観測方法を考案する。緩やかな傾斜面を転がる球を使うことで速度の計測を可能にした。時間の計測には水時計を使った。傾斜角を変えることでガリレオは速度は傾斜角に比例することを示し、傾斜角を転がる球は落下速度の計測の代わりに使うことができることを示した。もちろん現実には、球と傾斜面の摩擦にもエネルギーが使われるが微々たるもので当時実現可能な観測方法としては十分なものだった。ガリレオは傾斜角を転がる球を机の端から飛ばし、その軌道が放物線であることも観測している。

本書はニュートンの説明に移る。ニュートンによって科学革命はクライマックスを迎えるわけだが、しかしこのニュートンという人物はなんという奇人だろうか。ニュートンは生涯、イングランドのごく狭い地域より外に出ることはなかった。ニュートンは潮の満ち引きについて多大なる関心を持っていたにもかかわらず、生涯一度も海を見ることはなかった。中年に至るまでニュートンは身近に女を寄せ付けることがなかった。母親とて例外ではない。50代になってから親戚の美しい娘を家政婦として雇っているが、この2人の間に男女関係はなかった。ニュートンは科学以外にも、非科学的な錬金術や宗教について多大な著作を残している。

ニュートンの研究者の間でよく言われることには、ニュートンは最初の科学者ではない。ニュートンは最後の魔法使いである。時代が魔法から科学に変わる節目の時期にあって、魔法から科学への橋渡しをした人物だ。

ニュートンの法則を記述したPrincipiaは、単に重力を説明し得たために偉大だというわけではない。ニュートンはPrincipiaによって、物理的現象は簡単な数学的原則と、その原則の応用で説明できることを示したのだ。

中国の悪意あるハードウェアの細工を見破る方法

中国で生産されているハードウェアに悪意あるチップが取り付けられておりAppleやAmazonが被害にあっているとする報道があり、真偽について議論がある。

これに関連して、Hacker Newsで興味深いコメントが寄せられていた。

I have worked in card payment industry. We would be getting products from China ... | Hacker News

俺はカード支払い業界で働いている。中国から送られてくる製品にクレジットカード情報を送信する装置が取り付けられていることがある。これは国家による攻撃ではない。装置は生産ラインの途中で取り付けられている。大抵は賄賂を受け取った従業員によるものだ。装置が組み立てられた後は、改造防止の機能が動くので、改造を検知させずに装置を分解するのは不可能だ。

この問題が発覚してから、我々は製品の重さを計測することにした。我々も製品を分解することはできないからだ。分解したならば改造検知により装置は動かなくなる。

攻撃者は重さの計測に気がついたので、製品内部の必須ではないプラスチックを引っぺがして装置を追加することによって増加した重さの調整をしてくるようになった。

結局、我々は特別な土台を使い製品の角運動量(訳注:慣性モーメントか)を計測するようになった。とても高価な装置で角運動量を計測する。俺が作った土台を使って2つの角度から角運動量を計測している。2つの製品の角運動量がどの角度からでも一致するならば製品は同一というわけだ。すべての角度から角運動量を計測できないが、今のところ破られていない。

角運動量ではなく慣性モーメントだろうという用語の甘さや、慣性モーメントは3軸を使って全角度を測定できるのではというツッコミが入っているが、この話自体はよくあるものらしい。

なんでそんな製造段階で悪意ある細工が混入した製品を送ってくるところと取引を続けるんだというコメントに対し、この製品を製造しているのは今や中国をおいて他にないとか、中国の商習慣では信頼を前提としない契約による取引に応じてくれないとか、中国と取引するには製品の欠陥は自前で発見できなければならない、発見できないのはマヌケな顧客であり欠陥品で十分だとみなされる、などの中国のお国柄を指摘するコメントが続いている。

2018-09-20

健全なP2Pネットワークの信用のためには全利用者の参加が必須であるという話

「Zaifが5966BTCやられたようだな」
「ククク、あれは後発取引所ゆえ保有量が最弱」
「85万BTCを溶かした余の足元にも及ばぬ」

また一つ暗号通貨の取引所が失敗した。人はいつになったら学ぶのだろうか。中央権威のないP2Pネットワーク上の信頼は、利用者全員が参加することでしか担保できぬと。

信じられないことに、暗号通貨の利用者の中でも、なぜ暗号通貨が信用できるか、というより暗号通貨の何を信用しているかを理解しているものは少ない。皆暗号通貨と日本円などの国家通貨の交換レートしか気にしていない。国家通貨との交換レートなど、暗号通貨の実現している技術的な価値に比べればチリほどの価値もない。暗号通貨の価値は、中央権威のない通信越しに偽造できない通貨の取引を実現したということにある。

問題を簡単にするために、暗号通貨とは違う例で考えよう。

アリスとボブが仕切りを隔てて音声による会話しかできない状況に置かれている。この状況で、アリスとボブはコイントスによる勝負をする。コイントスは投げたコインが表か裏のどちらを上にして落ちるかで勝敗が決まる。アリスが表で勝つのであれば、ボブは裏で勝つ。アリスが裏ならばボブは表で勝つ。

アリスとボブが賭ける表裏を音声による会話で選択したとして、一体どうやって信頼できるコイントスをすればいいのだろうか。アリスとボブのどちらか一方がコイントスをして結果を相手に伝える場合、音声による会話しかできないので、相手はコイントスの結果を信用できない。アリスとボブ以外に勝負の審判をしてくれる都合のいい第三者はいない。

実は、この状況でアリスとボブがどちらも信用できるコイントスを行うことは数学的に可能だ。

では問題を変えよう。ここに60億人の人間がいる。全員、音声による会話しかできない。60億人のうちのある一人のアリスが、ある一人のボブに自分の持っている通貨の支払いたい。支払った後のアリスは通貨を失い、ボブは通貨を得る。しかし全員音声による会話しかできないのに、どうやったらそんな取引が信用できるのだろうか。

これも、数学的に信用できる方法がある。bitcoinを始めとする暗号通貨が行っているのはこれだ。

信用を担保するには、60億人が全員同じ方法で数学的な計算を行い、不正がないことを確かめなければならない。もし、30億人と一人が不正のための計算に協力したならば、アリスが支払ったはずの通貨はアリスの手元に残り、ボブの手元には通貨がない常態にすることも可能だ。しかし、そのような参加者の過半数を超える大規模な計算は難しい。

ただし、この方法で信用できるのは、ある暗号通貨の取引だけだ。暗号通貨と日本円の取引とか、物の取引は暗号通貨の信用の範囲外だ。

残念ながら、世の中の暗号通貨利用者の大半はズボラで、自ら信用を担保することを考えていない。こういうマヌケ共は、自ら信用のための計算をせずに、取引所と呼ばれる他人に計算を任せる。本来ならば証明された数学と計算力を信用すればよいはずの暗号通貨を、他人の信用に置き換えてしまう。こうなるともはやその計算を任された取引所が権威となり、単一障害点になってしまう。その結果、取引所が失敗すると任せていた全員が失敗する。

人はいつ学ぶのだろうか。

そういえば、CloudflareがIPFSのゲートウェイを提供するという興味深いニュースがあったので、今はIPFSについて学んでいる。今学んだ限りでは、名前解決であるIPNSを除けば、IPFSが喧伝していることはBitTorrentプロトコルに対するフロントエンドで実装できそうで、あまり目新しい価値はないように思える。そして、Cloudflareのような大手がゲートウェイを提供し、皆が自前でフルノードを動かさずにゲートウェイに依存することで、健全なP2Pネットワークの信用が損なわれてしまうだろう。

2018-09-17

Linus、今までの行いを謝罪し一時的にカーネルメンテナーの立場を退いて人の気持ちを勉強してくると発言

完全に背景事情を調べ上げたわけではないのだが、どうもLinusが毎年参加しているLinuxカーネルの会議に、Linusがスケジュールを間違えて参加できなくなるという事態が発生した。当のLinus本人はもう20年も続いている会議だし自分がいなくてもやっていけるだろうと楽観視していたが、会議自体がLinusの都合にあわせてリスケジュールされた。

LinuxにおいてLinus Torvaldsといえば第一人者であり極めて重要な存在で、そのLinusが毎年参加している重要な会議にLinusが参加できないとあれば、その他のあらゆるコストを度外視して根回し調整を行い、Linusが参加できるようにイベント全体のリスケジュールを行うのは人間の感情から考えて当然である。しかし当のLinus本人は他人の感情が読めず、そこまでの大事になるとは考えていなかった。その認識の差が今回の騒動に発展した。

Linux 4.19-rc4 released, an apology, and a maintainership note - Linus Torvalds

[ その他の長々しい話 ]

いつもの発表とは違う先週のことについて。メンテナーとカーネルコミュニティについての、公になっているカーネルサミットによるものと様々なプライベートなやり取りの議論について。議論の一部は俺がメンテナーサミットのスケジュールを間違えたから起こったわけだが。

こういう議論は今週になって初めて始まったわけでもない。メンテナーとコミュニティについては長年議論してきたわけだ。プライベートでもメーリングリストでもたくさん議論されてきた。カンファレンスでも毎回トークがあるし、これも、一般向けのものと、廊下を歩きながら話す種類のものがある。

先週がいつもと違ったのは、俺の反応と、俺が反応しすぎたためだ(判断は読者に任せる)

要するに議題は2つある。

ひとつは俺がメンテナーシップサミットのスケジュールでヘマをやらかしたことに対する俺の反応だ。いや、実際日付を間違えたのは失敗だったが、でも正直なところ、俺がここ20年ぐらい参加していたカーネルサミットに今回ぐらい参加しなくてもいいんじゃないかと思っていたのだ。

実際には、サミットがリスケジュールされたし俺の「俺なしでもやれるんじゃない」って意見は覆されたわけだ。だがこの状況が別の議論の呼び水になった。これが議題の2つめにかかってくるわけだが、俺は気がついたんだよ。俺って人の気持ちが読めないんじゃないのってことに。

要するに「鏡で自分の顔を見てみろよ」って瞬間だな。

というわけで、俺がついに気がついたこととして、俺が毎年のカーネルサミットを今回ばかりパスするのは面白くもないしいい兆候でもないってことと、俺は今の今までコミュニティの気持ちを汲み取れていなかったということだ。

何というかな。無視できるってものは、だいたい俺が首を突っ込みたくないものなんだな。

これが俺という人間の現実だ。俺は他人の感情を読み取るのが得意ってタイプの人間じゃないし、そのことはみんなも当然気がついていただろうから驚きはないだろう。俺以外はな。俺が長年、他人の気持ちを読み取れずにいて、大人でない環境を作り出していたってのは良くないことだ。

今週、コミュニティ内の人間は俺が人の感情を理解できていないということについて批判してきた。俺の反論は大人気なかったし良いものでもなかった。特に俺個人の問題としたことについてだ。俺がよりよいパッチを追求することについて、これは俺の中では当然のことだ。これはどうもよろしくない態度だと気がついたわけで、正直すまんかった。

こうくどくど書き連ねているのはつまり俺が誤りを認めて、おいおい、お前は態度を改めなければならんぞ、という辛い思いをかみしめているのであって、俺の態度で気分を害したりカーネル開発から抜けてしまった人間にあやまりたい。

ちょっとここらで休みを取って、他人の感情を理解する方法と、適切な応対の方法について、誰かから学んでくる。

別の視点で考えると、カンファレンスに呼ばれた時、俺はよく、カーネル開発の厄介な問題点はたいてい技術的な問題ではなくて、開発フローと態度の変化の問題なのだというトークをよくする。

この厄介な問題点というのは、パッチを管理する方法とか、やり方の大規模な変更とかだ。「パッチとtar-ball」によるリリース(15年前に「Linusはスケールしない」という厄介な議論が持ち上がった原因)から、BitKeeperを使う方法に変わり、それも無理になってきたのでgitを使う方法に変わった。

そういう厄介な問題点は、ここ10年ぐらいなかったわけだ。だが今週、それに匹敵する厄介な問題点が見つかった。

これを4.19-rc4のリリースに結びつけると(いや、実際関係しているのだが)、4.19はなかなかよいものになると思うよ。このリリースサイクルは「落ち着いた」期間になるし、Gregに話はつけておいたので、Gregが4.19を取り仕切ってくれる。その間に俺が休みを取って、俺の態度について修正してくるわ。

これは「俺はもう燃え尽きた。もう逃げ出したい」っていう休みじゃない。Linuxのメンテナーを辞めたいなどとは思っていない。実際逆だ。俺はこの30年近く関わってきたプロジェクトをまだ続けたい。

これは前にもあったように、"git"という小さなツールを書くためにカーネル開発をちょっと停止するひつようがあったように、今回もちょっと休憩して、誰かに手伝ってもらって、態度を改め、俺のツールとワークフローの問題を修正するということだ。

実際のところ、修正の一部はツールで解決できるかも知れないのだ。例えば、メールフィルターをかまして、俺が罵詈雑言を含むメールを送信しようとしたら差し止めるとか。ほら、俺はツールの信者だからさ、一部の問題は、簡単な自動化で防げるかもしれないだろ。

俺が本当に「鏡を見た」とき、明らかに俺に必要なのは変化だけではないわけだが、いや、ほら・・・何か提案があったらメールで送ってくれよな。

メンテナーサミットで会えるのを楽しみにしているよ。

Linus

2018-09-10

TempleOSの作者Terry Davisが列車に引かれて死んだ

Man killed by train had tech following | The Dalles Chronicle

Man killed by train had tech following By Neita Cecil As of Friday, Septem - Pastebin.com

両親と喧嘩をして勘当され路上生活をしていたTerry Davis(Terrance Davis)が8月11日に列車に引かれて死んだことが確認された。享年48歳。

Terry DavisはTempleOSの作者だ。

TempleOS

読者の多くはTempleOSを知らないだろう。TempleOSとはx86-64 Ring-0上で動作するOSだ。プロセス分離はなく、メモリ保護もなく、そもそも仮想メモリではなく物理メモリアドレスを直接使うOSだ。コンセプトは古き良きCommodore 64の現代版だ。あの頃のPCはメモリアドレスは直接メモリアドレスであって、仮想メモリによってどこか不定の物理メモリアドレスにマッピングされたりなどしなかった。

TempleOSはキリスト教の影響を受けておりキリスト教の聖書やキリスト教に由来するゲームが多数入っている。

TempleOSはHolyCで書かれている。HolyCはC言語に似た文法を持っている。TempleOSのシェルはHolyCのJITコンパイラーになっていて、シェルに書き込むと、HolyCがJITコンパイルされ実行されるようになっている。

作者は統合失調症を患ったOS開発者で、大抵のインターネット上のフォーラムからは出禁の扱いを受けていた。なぜならば、Terry Davisの発言には必ず人種差別と罵詈雑言が含まれていたからだ。彼にとってほとんどの人間は「ニガーでCIAのスパイ」だと認識されていた。

Terry Davisは統合失調症の症状が強く現れてからは職を辞め、両親と同居して、キリスト教の神の神殿をコンピューター上に再現するTempleOSを開発していた。

何年もそういう状況であったが、2017年に両親とだいぶひどいいさかいを起こし、ホームレスになっていた。

ホームレスになった後も定期的に動画が上がったりなどして生存が確認できていたが、ここ最近、死亡説が噂されていた。それが確認されたことになる。

悲しい

GitHubからDXVKレポジトリが消失

GitHubのDXVKレポジトリが500を返すようになった。

https://github.com/doitsujin/dxvk

DXVKはDirectX 10/11の自由なVulkan実装だ。DXVKによってWineやProtonはDirectXの使われたWindows用ゲームをGNU/Linuxで実行することができる。DXVKにより不自由なMicrosoft Windowsはとうとうゲーム用OSとしても完全に敗北し、その座をGNU/Linuxに明け渡すことになる予定だが、どうしたことだろう。

DXVK github not found and valve's copy throws error? Whats happening? : linux_gaming

どうやら、DXVKの作者のGitHubアカウントが謎の理由でBANされたそうだ。

DXVK github not found and valve's copy throws error? Whats happening? : linux_gaming

作者によってすぐにGitLab上でDXVKが公開された。

Philip Rebohle / dxvk · GitLab

Redditでは「そういえばGitHubはMicrosoftに買収されるんだよな」というジョークが書き込まれているが、ジョークで済むだろうか。

追記2018-09-10 9:14: 復活した。