2018-02-28

江添亮の詳説C++17出版記念勉強会を3月14日に開催

3月9日に発売される江添亮の詳説C++17の出版を記念して、3月14日に勉強会を行うことにした。

本の内容としては、2017年に発行された新しいC++17の新機能を余すところなく解説している。

本はGPLv3でライセンスされた自由な本であり、GitHubでソースコードが公開されている。

https://github.com/EzoeRyou/cpp17book

出版を記念した勉強会を3月14日に開催する。勉強会の詳細と参加登録は以下のconnpassのページにて確認できる。

https://connpass.com/event/80830/

当日は本を現地で販売できる見込みだが、数に限りがあるので確実に入手するためには、今から書店や通販Webサイトで予約注文をするとよいだろう。

最近、発表会と言うよりは、C++に興味がある人間が雑談をする場の方が有意義ではないかと思い、発表枠としてはLT枠のみを設けた。

本は購入するだけでは意味がなく、読まなければならない。今後、著者自ら読書会を開催しようと思っている。この本の所有者達が、広い部屋に、一斉に集まり、わざわざ読書会に参加したのだから、これはもう本を読むしかない。そのような状況をお膳立てするべきだろう。

さて、今年は入門書を書かなければならない。入門書の執筆は最も難しい。難しい機能を難しく解説するのは簡単だ。すべてを網羅的に記述すればいいだけだからだ。入門書は多くの詳細を大胆に省略、簡略化しなければならない。その結果、すべての入門書の内容は本質的に間違ったものになる。それでもなお間違いの少ない簡略化のルールを見いだせた入門書が良書となる。つらい仕事だ。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2018-02-24

NPM 5.7が重要なディレクトリの所有者を書き換える凄まじいバカをやらかす

https://github.com/npm/npm/issues/19883

Do not use NPM 5.7 | Hacker News

NPM 5.7において、sudo npmを非rootユーザーから行うと、/etc/とか/usrとか/bootなどの極めて重要なディレクトリの所有者を、sudo npmを実行した非rootユーザーにchownして書き換えてしまうというとてつもないすさまじい不具合が報告されている。

一体どうすればそんな間違いをしでかすというのか。

https://github.com/npm/npm/commit/94227e15eeced836b3d7b3d2b5e5cc41d4959cff

どうもディレクトリを作成するときにパーミッションと所有者を適切に設定するという名目でmkdirをラップして実行時のユーザーでchownするcorrectMKdirを作ってmkdirを置き換えたようなのだが、そもそもsudoしたユーザーがroot権限を持つユーザーである保証はなく、そもそも/etcや/usrの所有者であるユーザーである保証もなく、本当にNPM開発者は本来とっくの昔に解決したはずの問題を、ぼくちんのかんがえたさいきょうのほうほうで引っ掻き回すことにかけては驚異的な才能を発揮するのに余念がない。これだから特定の言語専用のパッケージマネージャーというのは使うべきではないのだ。

2018-02-21

C++をプログラミング入門に使うならHTTPSぐらい喋れないと話にならんと言われたのでそういうライブラリを作った

C++17の新機能を余すところなく解説した「江添亮の詳説C++17」は3月9日に発売される。今はC++によるプログラミングの入門書を書こうとしているが、同僚から、果たしてC++をプログラミング入門に用いるのは適切なのだろうかという疑問を提示された。私はC++はハードウェアに直接マッピング可能な低級機能からそれを隠匿する高級機能まで揃っている上に、継ぎ接ぎだらけの型システムは

「21世紀の入門コード片という意味では https download くらい標準で喋って欲しさ」

と言われた。なるほど、そういうのはsystem("wget -q https://example.com")すればいいのではないだろうか。

「なるほど、しかしそれではファイル経由で扱うことになる。初心者ならstd::stringとかに入った状態で扱いたいはずだ」

一理ある。そういうライブラリを実装すればいいのではないか。せっかくだからpopenを使って標準出力をパイプで受け取り、それをistreamにしてみよう。幸い、libstdc++にはstdio_filebufがあるのでFILE *からfilebufが作れる。

というわけで以下のようなライブラリを作った。

int main()
{
    popen_istream ps("curl -s https://example.com") ;

    std::string line ;

    while (ps)
    {
        std::getline( ps, line ) ;
        std::cout << line << '\n' ;
    }
}

とりあえず動く。

「しかし、HTTPSをお話したい理由というのは、サーバーの提供するWeb APIを叩きたいためだ。そのためにはサーバーにPOSTでデータを送りつけられるとなおよい」

wgetは標準入力からPOSTで送りつけるデータの入力に対応していないが、curlは対応している。なのでパイプで標準入力にデータを流し込み、標準出力をパイプで繋げばよいのではないか。

popenは入出力双方向のパイプに対応していない。この理由は、デッドロックを起こしやすいためだ。結局、入出力のバッファリングがユーザースペースで行われている場合、実際にパイプに書き込まれるタイミングが想定と異なる場合があり、また多くの伝統的なコマンドも、標準入力をすべて読み込まないうちは出力を始めないような設計のものも多いためだ。

なので、入出力を双方向にパイプするには古典的なpipeを使ってやる必要がある。iostreamはistreamとostreamで別々のfilebufを持てるためこのような入力先と出力先が違う場合にも対応できる。istreamとostreamが別々なので、ostreamにすべて書き込んだ時点でパイプを破棄することもできる。

とここまで考えて思ったが、やはり標準に高級なネットワークライブラリがほしい。HTTPSをお話できたりブラウザー操作ができたりするようなライブラリだ。

class popen_filebuf
{
    FILE * fp ;
    __gnu_cxx::stdio_filebuf<char> fb ;

    static FILE * init_popen( std::string_view command,  )
    {
        std::string cmd( command ) ; // for null-terminated c_str()
        FILE * result = popen( cmd.c_str(), "r" ) ;
        if ( result == nullptr )
            throw std::runtime_error("popen failed.") ;

        return result ;
    }

protected :
    auto get_filebuf_ptr() noexcept
    {
        return &fb ;
    }
public :
    popen_filebuf( std::string_view command )
        :   fp( init_popen( command ) ),
            fb( fp, std::ios_base::in )
    { }

    ~popen_filebuf()
    {
        std::fclose( fp ) ;
    }

} ;

class popen_istream :
    protected popen_filebuf,
    public std::istream
{
public :
    popen_istream( std::string_view command )
        :   popen_filebuf( command ),
            std::istream( get_filebuf_ptr() )
    { }

} ;



std::string popen_reader( std::string_view command )
{
    popen_istream ps( command ) ;
    
    std::istreambuf_iterator<char> iter( ps.rdbuf() ), end ;
    std::string buf( iter, end ) ;
    return buf ;
}

2018-02-11

アマゾンで江添亮の詳説C++17が予約可能になった

江添亮の詳説C++17 | 江添 亮 |本 | 通販 | Amazon

2017年に発行された最新のC++17の新機能をあますところなく解説した私の本、江添亮の詳説C++17がアマゾンで予約可能になったようだ。

本の内容はすでにGitHubで公開している。ライセンスはGPLv3だ。プログラミングの参考書は自由なライセンスで提供されるべきである。

https://github.com/EzoeRyou/cpp17book

次は入門書とTips集を書きたい。

2018-02-09

新しいプログラミング言語を学ぶということ

去年の暮から一ヶ月ほど、Haskellを学んでいる。目的は色々あるが、まったく新しいプログラミング言語を学ぶ経験をもう一度するのが最大の目的だ。

C++17の新機能を余すところなく解説した「江添亮の詳説C++17」は3月に出版される。C++20にはまだ間がある。C++の入門書を書く絶好の機会だ。入門書というのは毎月腐るほど出版されているが、良書は少ない。結局、参考書の中で最も売れ行きが容易に予想可能なのは、最も読み手がいるであろう入門者向けの本であり、入門書の出版がいたずらに増え、粗製濫造されているとみえる。入門書は上級者は読まないので適切な批判もされにくいのでこのような惨状になっているのだろう。

私の入門書はそのような悪書にしたくない。しかし、私はすでにC++の初心者ではないし、C++の初心者がつまづく点がわからない。というより、ここしばらくまったく新しいプログラミング言語を学んだことはない。C++以外にもPython、JavaScript、bashなどを必要に迫られて書き散らすこともあるが、相当古いJavaScript以外は真剣に学んだことがない。

これはよくない。入門書を書く前に、少し時間を割いてでも、全く新しいプログラミング言語に入門すべきだ。そのための言語としてはHaskellがふさわしい。と、こう考えた。

なぜHaskellなのか。HaskellはC++とは全く違った言語だからだ。例えばJavaScriptは表面的にはC++とかなり似た文法を持っているので、全く新しいプログラミング言語を学ぶ目的には使えない。PythonやRubyは文法的には少し違うが、すでに触ったことがあり、それほど苦労した覚えがないのでやはりだめだ。Scalaは汚らわしいJavaの実行環境を私の神聖なメインPCにインストールしたくないために不適だ。

Haskellを学ぶには、Learn you a Haskell for great goodという本が良いらしいので、早速買い求めて読み始めた。この本はとても軽くて読みやすい本だ。そしてユーモアが多い。真面目な参考書に個々までのユーモアは必要なのかと疑問に思うぐらいに多いのだが、しかし全くの初心者が読む本には、これぐらいのユーモアが必要なのではないかと思う。

というのも、だ。私自身、久しぶりに経験して驚いているのだが、全く新しいことを学ぶのは途方もなく疲れる上に非効率的な作業だからだ。不思議なもので、それほど難しいはずはない内容なのに、なぜかなかなか頭に入らない。一日に本を読む時間は何時間もあるが、短期的にいくら時間をかけても内容が頭に入ってこない。一晩寝るなどして時間を開けないとわずか数ページの内容も読み進めることができない。文字を読むことはできるにしても、その内容が理解できず、また残らないとしたら意味がない。

そういう賽の河原のような不毛な作業を続けるには、ユーモアがとても効果的だということがわかる。ただでさえ退屈な作業なのに、ユーモア欠落症患者によって書かれた文章まで読まされるのでは続くものも続かない。

私が書く入門書にはユーモアが必要だということがわかる。

プログラミング言語を学ぶには、プログラミングの実行環境が手元にあったほうがよい。しかしHaskellの実行環境はあまりすぐれているとは言い難い。Haskell PlatformはDebian系ならばapt一発で入るのでまだいいのだが、クールなキッズは皆使うというStackは、私の手元ではまともに動いた試しがない。

そもそもだ。CabalもStackもてんでなっていない。パッケージマネージャと名乗るのもおこがましい。だいたい管理できていない。パッケージを追加することはできるが削除できないのだから。削除するにはどうするか? すべてのパッケージを削除して最初からインストールをやり直す。富豪にもほどがある。Stackもだめだ。結局管理できていない。管理できないからどうするか? とりあえずプロジェクトごとに特定のディレクトリ下に全部環境を構築しよう。富豪にもほどがある。

そういうわけで、Haskellの実行環境を整えるだけで1日を浪費した。C++の入門書では環境構築についてそれなりにページを割かなければならないと痛感した。具体的にはWindowsとMacはC++の学習に不適切なOSなのでGNU/Linuxを推奨したい。主要なディストロならC++の実行環境はデフォルトで入っている。

さて、そうこうしているうちに一ヶ月たち、流石にHaskellである程度のコードは書けるようになってきた。まだモナドについて完全に理解していないのだが、理解できるのも時間の問題だ。なんだ、Haskellも言うほど難しくはないではないか。

しかし、プログラミング言語の学習に当たってはこの時期が一番つらい。プログラミング言語の学習過程では、序盤はさっぱりわからず、中盤はすべてを理解したつもりになり、終盤でやはりわからない状況になる。

プログラミング言語の学習の中盤でつまづくのは、言語の以外な落とし穴だ。例えば私の場合は以下のような例で躓いた。

モナドを学び始めたので、確認がてら、以下のようなコードを書いた。

getContents >>= 複雑な式>>= putStr

しかしこれでは見づらいので、複雑な式を関数に分けることにした。複雑な式の中身は特に重要ではないので、ここでは単にpureとする。

-- こうすればカリー化によって引数を書く必要がないぞ。Haskellは完全に理解した。
f = pure
getContents >>= f >>= putStr

その後、fはそのままにしたまま別の処理を試したくなった。

f = pure
getContents >>= 別の式 >>= putStr

するとエラーになった。意味がわからない。

Haskellではf = pureと書くとfは多相にならず、型を決定しなければならないが、型を推論するための文脈がないのでエラーになるというのが原因だ。解決方法としては、fの型を明示的に書く、引数を書く、GHC拡張を使うなどの方法がある。

しかし、これは割と初心者が引っかかりやすい落とし穴ではないか。例えば以下のコードがなぜエラーになるか、初心者がわかるのだろうか。

f = pure
main = do
    print ( f 0 == Just 0 )
    print ( f 0 == [0] )

どうでもいいことだが、C++を長年やっていた人間として、f = pureとしたあとにf 0 == [0]と書くと、fはpureになり文脈上リストなのでf 0は[0]になるというのは割と驚きがあるし、リストという割と特別扱いされているような昨日でさえ、単なるライブラリ実装であり、(:)はデータコンストラクターにすぎないというのも驚きがある。

早くモナドを完全に理解したい。

2018-02-05

Haskellで書いてみたらC++の10倍遅かった

エレガントな解法、エレファントな解法 〜モンテカルロ法を添えて〜

コインを100回投げたうちに、表もしくは裏が連続して10回でる確率をモンテカルロ法で計算する。

なにはともかくC++で実装してみた。

template < typename Random >
bool coin_toss( const unsigned int try_count, const unsigned int length, Random & r )
{
    unsigned int count{} ;
    int prev{} ;

    std::uniform_int_distribution<> d( 0, 1 ) ;

    for ( unsigned int i = 0 ; i != try_count ; ++i )
    {
        int result = d(r) ;
        if ( result == prev )
        {
            ++count ;
            if ( count == length )
                return true ;
        }
        else
        {
            prev = result ;
            count = 1 ;
        }
    }

    return false ;
}

template < typename Random >
double monte_carlo( unsigned int try_count, Random & r )
{
    unsigned int count{} ;
    for ( unsigned int i = 0 ; i != try_count ; ++i )
    {
        if ( coin_toss( 100, 10, r ) )
            ++count ;
    }

    return double(count) / double(try_count) ;
}

int main()
{
    std::array<unsigned int, sizeof(std::mt19937)/sizeof(unsigned int)> c ; ;
    std::generate( std::begin(c), std::end(c), std::random_device{} ) ;
    std::seed_seq s( std::begin(c), std::end(c) ) ;
    std::mt19937 engine(s) ;

    for ( unsigned int i = 100 ; i != 1000000 ; i *= 10 )
    {
        auto r = engine ;
        std::cout << i << "\t : " << monte_carlo( i, r ) * 100.0 << "%\n" ;
    } 
}

C++11から高度な乱数ライブラリが使えるようになったのだが、現在、実行時に適切に初期化する方法に面倒なボイラープレートコードが必要だ。実行時に乱数で初期化してくれる機能が現在提案されている。

ところで、最近Haskellを学んでいるので、このコードをHaskellでも書いてみようと思った。

まずは乱数だ。コインの表裏はBoolで表現するとして、Haskellらしく無限の乱数リストを生成しよう。

coin_seq :: (RandomGen g) => g -> [Bool]
coin_seq gen = randoms gen

あとはこれをtake 100して一回分の試行とする。1000回試行したければ、drop 100したのを再帰的にtake 100すればよい。しかし、もっといい方法がある。要素数100の[Bool]が入ったリスト、つまり[[Bool]]を無限に作ればいい。そのリストをtake 1000すれば1000回の試行分になる。

split_n :: Int -> [a] -> [[a]]
split_n n seq = take n seq : split_n n (drop n seq)

さて、seq_n = take 100 $ split_n 100 (coin_seq gen)のようなリストseq_n :: [[Bool]]は作れた。あとは[Bool]に連続した要素がn個あるかどうかを調べるcoin_toss nと、それをリストのすべての要素に対して行うmonte_carloを書けばいいだけだ。

coin_tossが結果をBoolで返すとすると、とりあえずseq_nにcoin_tossをmapしてTrueだけfilterして、その結果のリストの要素数を数えれば、成功した試行数がわかる。あとは試行数で割って確率を求めればいいだけだ。

monte_carlo :: Int -> [Bool] -> Double
monte_carlo try_count seq = ((fromIntegral n) / (fromIntegral try_count)) * 100.0
    where
        seq_n = take try_count (split_n 100 seq)
        n = length . filter id $ map (coin_toss 10) seq_n

coin_toss len seqはInt -> [Bool] -> Boolな関数で、seqの中にlen個の連続したTrueもしくはFalseがあるかどうかを数えればよい。

いろいろ考えたが、まずgroup seqして連続した同じ要素を分割して[[Bool]]を作り、それぞれの[Bool]の要素数をいれたリスト[Int]をmapし、len以上の要素だけをfilterして、結果のリストに要素があればTrueを返す計算をすればいいのではないか。つまり、

has_contiguous_elems :: (Eq a) => Int -> [a] -> Bool 
has_contiguous_elems len seq = not $ null $ filter (>= len) $ map length (group seq)

coin_toss :: Int -> [Bool] -> Bool
coint_toss _ [] = 0
coin_toss len seq = has_contiguous_elems len seq

となる。全体的にはこうだ。

import System.Random
import Data.List

coin_seq :: (RandomGen g) => g -> [Bool]
coin_seq gen = randoms gen

split_n :: Int -> [a] -> [[a]]
split_n n seq = take n seq : split_n n (drop n seq)

has_contiguous_elems :: (Eq a) => Int -> [a] -> Bool 
has_contiguous_elems len seq = not $ null $ filter (>= len) $ map length (group seq)

coin_toss :: Int -> [Bool] -> Bool
coint_toss _ [] = 0
coin_toss len seq = has_contiguous_elems len seq

monte_carlo :: Int -> [Bool] -> Double
monte_carlo try_count seq = ((fromIntegral n) / (fromIntegral try_count)) * 100.0
    where
        seq_n = take try_count (split_n 100 seq)
        n = length . filter id $ map (coin_toss 10) seq_n

main = do
    gen <- getStdGen
    let s = coin_seq gen
        in mapM (\n -> putStrLn ((show n) ++ "\t : " ++ (show (monte_carlo n s)) ++ "%") )
                [100, 1000, 10000, 100000] 

このコードは動いた。ただし、最適化オプションを使っても、C++の10倍遅かった。

やはりlen個の連続した要素が存在するかどうかを調べるhas_contiguous_elemsが遅いのではないかと思い、これを再帰で実装することにした。

has_contiguous_elems' :: (Eq a) => Int -> [a] -> Bool 
has_contiguous_elems' _ [] = False
has_contiguous_elems' len (x:xs) =
    if count >= len
        then True
        else has_contiguous_elems' len $ dropWhile (== x) xs
    where count =  1 + (length $ takeWhile (== x) xs)

この実装に差し替えたところ、実行速度はC++の9倍遅かった。

すると、他の部分もリストからリストを生成するという記述をやめて、再帰で実装すればもう少し早くなるのだろうか。

そして思うのは、同様のリストからリストを清々するような処理は、所詮リストのメモリーサイズがキャッシュに収まる程度なので、C++で書くとHaskellより早いのではないかとも思ったが、まだ試していない。

2018-02-04

年収1200万円と2億の資産でできる贅沢はパトロネージュ

以下のはてな匿名ダイアリーの記事が注目を集めている。

贅沢な生活って何が楽しいの?(追記しました

独身34歳男、年収900万+配当収入300万くらい。

贅沢の良さがわからない。

家は相続で貰ったので家賃無し。金融資産は相続したものと合わせて2億を超えた。

生活費は月に12万くらい。配当込みで年間800万以上金が増えていく。

別に金を使うのが嫌いなわけではない。ただ、使う気がおきない。

この増田は1200万円の年収と2億の資産を保有している。増田が過去に行った贅沢は以下の通り。

  • 飛行機のファーストクラス(100万円)
  • ホテルのスイートルーム(100万円)
  • オーダーメイドのスーツ(50万円)
  • 懐石料理(20万円)
  • ステーキ(10万円)

これらの贅沢がありきたりでつまらないのは当然だ。なぜならば、これらの贅沢には、何も1200万円の年収と2億円の資産は必要ないからだ。ただ100万円を貯金して一度に使えばできる程度の贅沢だ。100万円を1年ぐらいかけて貯金するのであれば、34歳男の平均年収である400万円でも可能だ。

せっかく贅沢をするのであれば、年収1200万円と2億円の資産をもって初めて可能になる贅沢をするべきだ。では増田の境遇ではじめて可能になる贅沢とは何か。多額の借金と長期的な支出だ。そして、究極的にはぱとろネージュと呼ばれる行為、すなわち食客を抱えることだ。

多額の借金

増田のような境遇にない我々凡人は、それほど多額の借金ができない。大抵の凡人が生涯に負うことのできる多額の借金は、奨学金と住宅ローンぐらいなものだろう。しかも学費や住宅といった目的が限定されている。

しかし増田は違う。1200万円の年収と2億円の資産を担保に、1億や2億の借金ができるだろう。借金ができる環境はとても贅沢だ。借金で得た金を使って利息を上回る利益を出すことができれば借金をした以上の価値を生む。例えば増田は自分の興味がある事業を立ち上げることができる。ただし、これには利息を上回る利益を出すことができずに資産を失うリスクはある。

長期的な支出

増田が例に挙げている贅沢は、短期的な一度きりの支出ですむものだ。100万円を払ってファーストクラスに乗ったりスイートルームに泊まったりするのはいかにも贅沢に思えるが、一回払うだけでその後は何の支出もいらない。

しかし増田は違う。増田は月に50万円ぐらいの支出を長期的に続けることができる。長期的に支出を行えるのは、我々凡人には不可能な贅沢だ。では、長期的な支出が必要な贅沢とは何か。

答えは人だ。増田は長期的に人をフルタイムで雇うことができる。増田は秘書を雇い日常の雑事をすべて押し付けることもできる。増田の時間は貴重である。増田がファーストクラスやスイートルームや懐石料理を楽しむとして、その予約作業という雑事は、わざわざ贅沢な増田の手をわずらわす作業ではない。秘書にやらせればよい。

そして、古来より最高の贅沢とされる人への支出がある。パトロネージュ、すなわち食客を抱えることだ。

増田にはより発展してほしい芸術や学問はないだろうか。もっと具体的に書こう。自分の気にいる漫画や小説を読みたい。自分の気にいるゲームで遊びたい。ある自由なソフトウェアに自分の望みの機能を追加したい。ある病気を根絶する研究が進んでほしい。ある技術がもっと発展してほしい。しかし、増田は芸術や学問を極めることはできない。増田に絵や詩人の才能はなく、プログラミングはできず、医学博士や工学博士として研究に身を捧げる人生も送りたくはないとする。しかし、世の中には芸術や学問に身を捧げたいと願う天才はいる。増田はそのような天才を食客として衣食住の不便をなくし、芸術や学問を追求させることができる。

増田が増田を書くことができるのは、コンピューターが発展したからだ。ところで現代のコンピューターの発展はチャールズ・バベッジの研究に負うところが大きい。もしチャールズ・バベッジがコンピューターの研究に打ち込まなければ、コンピューターの発展は数十年は遅れていただろう。チャールズ・バベッジは裕福な家の出身ではあったが、コンピューターの研究のためにパトロンが必要だった。

この話の教訓としては、自分が楽しむためにカネを使えないのであれば、他人が楽しむためにカネを使ってみてはどうか。その結果として芸術や学問が発展するのであればこれ幸い。

2018-02-02

江添ボドゲ会@2月11日

だいたい毎月行っている自宅ボドゲ会を2月11日に開催します。

江添ボドゲ会@2月11日 - connpass