2017-12-26

Haskellのエラーメッセージについて

Haskellの実装であるGHCのエラーメッセージがわかりにくい。

例えば以下のコードがあるとしよう。

f p as@(x:xs) =
    if p x 
    then f p xs
    else as

main = return ()

この関数fはdropWhileと名付けてもいい関数だ。この関数の型は( t -> Bool ) -> [t] -> [t]だ。

ところで、この関数をうっかり書き間違えてしまい、then f p xsとすべきところを、第一引数のpredicateを渡し忘れ、then f xsとしてしまった場合を考えよう。

f p as@(x:xs) =
    if p x
    then f xs
    else as

main = return ()

このコードをGHC 8.0.2でコンパイルすると以下のようなエラーメッセージが表示される。


[1 of 1] Compiling Main             ( prog.hs, prog.o )

prog.hs:1:1: error:
    • Couldn't match type ‘t -> Bool’ with ‘[t]’
      Expected type: [t] -> [t]
        Actual type: (t -> Bool) -> [t] -> [t]
    • Relevant bindings include f :: [t] -> [t] (bound at prog.hs:2:1)

このエラーから読み取れる情報は、(t -> Bool)型は[t]型にマッチできないということだ。f p xsとすべきところをf xsとしてしまったのだから当然の話だ。pは(t -> Bool)でxsは[t]だ。

だが、このエラーメッセージからはどこの箇所が悪かったのか全然わからない。

しかし、このコードをGHC 7.10.3でコンパイルすると、以下のようなとてもわかり易いエラーメッセージが表示される。

prog.hs:3:10:
    Couldn't match expected type ‘[t]’ with actual type ‘[t] -> [t]’
    Relevant bindings include
      xs :: [t] (bound at prog.hs:1:11)
      x :: t (bound at prog.hs:1:9)
      as :: [t] (bound at prog.hs:1:5)
      p :: t -> Bool (bound at prog.hs:1:3)
      f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:1:1)
    Probable cause: ‘f’ is applied to too few arguments
    In the expression: f xs
    In the expression: if p x then f xs else as

prog.hs:3:12:
    Couldn't match expected type ‘t -> Bool’ with actual type ‘[t]’
    Relevant bindings include
      xs :: [t] (bound at prog.hs:1:11)
      x :: t (bound at prog.hs:1:9)
      as :: [t] (bound at prog.hs:1:5)
      p :: t -> Bool (bound at prog.hs:1:3)
      f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:1:1)
    In the first argument of ‘f’, namely ‘xs’
    In the expression: f xs

問題は3行目の10文字目と12文字目にあることがわかり、関連するbindingが一覧表示され、問題のある式とその式を含む式まで表示してくれる。これならわかりやすい。バージョンアップしてわかりにくくなるとはどういうことだ。

GHC 8.2.1ではエラーメッセージが改良されたそうだ。果たして直っているだろうか。

https://wandbox.org/permlink/leQ7uQaoN1eqBPLS

prog.hs:1:1: error:
    • Couldn't match type ‘t -> Bool’ with ‘[t]’
      Expected type: [t] -> [t]
        Actual type: (t -> Bool) -> [t] -> [t]
    • Relevant bindings include f :: [t] -> [t] (bound at prog.hs:1:1)
  |
1 | f p as@(x:xs) =
  | ^^^^^^^^^^^^^^^...

なるほど、Clangのプリティなエラーメッセージを真似ようという意思は感じられる。しかしその箇所は関数を宣言している箇所だ。関数の引数が間違っている箇所を指定してくれなければ意味がない。なぜGHC 7でできていたことがGHC 8でできなくなっているのだ。

Wandboxで最新のHEADも試したが、この問題はまだ解決していなかった。

さて、fの型を明示的に書くとエラーメッセージが変わるらしい。早速試してみよう。

f :: (t -> Bool) -> [t] -> [t]
f p as@(x:xs) =
    if p x
    then f xs
    else as

main = return ()

これをGHC 8.0.2でコンパイルすると以下のようなメッセージが表示される。

https://wandbox.org/permlink/307bjIWpMGZ3jJhO

prog.hs:4:10: error:
    • Couldn't match expected type ‘[t]’
                  with actual type ‘[t0] -> [t0]’
    • Probable cause: ‘f’ is applied to too few arguments
      In the expression: f xs
      In the expression: if p x then f xs else as
      In an equation for ‘f’: f p as@(x : xs) = if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)

prog.hs:4:12: error:
    • Couldn't match expected type ‘t0 -> Bool’ with actual type ‘[t]’
    • In the first argument of ‘f’, namely ‘xs’
      In the expression: f xs
      In the expression: if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)

ようやくGHC 7に戻ってきた。GHCはfの型を正しく推定できているのに、なぜ型tを明示的に書かなければ親切なエラーメッセージを出してくれないのだ。不親切にもほどがある。

さて、ではエラーメッセージが親切になったというGHC 8.2.1ではどうか。

https://wandbox.org/permlink/8j00LitIvUUuzDTM

prog.hs:4:10: error:
    • Couldn't match expected type ‘[t]’
                  with actual type ‘[t0] -> [t0]’
    • Probable cause: ‘f’ is applied to too few arguments
      In the expression: f xs
      In the expression: if p x then f xs else as
      In an equation for ‘f’: f p as@(x : xs) = if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)
  |
4 |     then f xs
  |          ^^^^

prog.hs:4:12: error:
    • Couldn't match expected type ‘t0 -> Bool’ with actual type ‘[t]’
    • In the first argument of ‘f’, namely ‘xs’
      In the expression: f xs
      In the expression: if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)
  |
4 |     then f xs
  |            ^^

なるほど、わかりやすくなっている。できればこれを型を明示せずともやってもらいたいものだ。

この記事はめるぽんさんの運営するWandboxという大変便利な本物のWebサイトを活用して書いた。今年もめるぽんさんに野菜をスポンサーしにいかなければならない。

Haskellでwordsを実装してみた

Haskellを学び始めたが、いまだにまともなコードを書くことができないでいる。理由は簡単で、まだ標準入出力が扱えないからだ。

標準入出力はUNIXでは極めて根本的な機能だ。標準入出力が扱えるようになればだいたいの処理はできると考えてよい。というのも、UNIXではパイプによって標準入出力の入力元と出力先を変えることができるからだ。パイプを使えば、ファイル操作やネットワーク操作をコードで表現する方法を知らなかったとしても、操作ができるようになる。

ところが、Haskellでは標準入出力を扱えるようになるまでが遠い。別に書けないわけではない。今でもHaskellでHello,Worldぐらい書けるし、特定の処理がしたいのであれば似たような入出力処理をするコードをどこからか探してきて改変することで目的のコードを作り出すことはできる。そういう意味では、現時点でもHaskellである程度のコードは書けるだろう。しかし、それでは言語を真に理解したことにはならない。言語の仕様を理解し、他人の書いたコードの改変ではなく、自分でコードを無から書けてこそ、自由自在のプログラミングが可能になる。

しかし、関数型言語であるHaskellでは入出力などという副作用を伴う処理には特別な配慮が必要らしく、いまだに標準入出力にたどり着いていない。

しかし、今までに学んだHaskellの知識を使って自力で何かを実装してみたいので、今回はwordsを実装することにした。

wordsは文字列を空白文字を区切り文字として分割した文字列のリストを返す。

> words "aaa bbb ccc"
["aaa","bbb","ccc"]

処理自体は簡単なはずなのだが、これをHaskellの流儀でやるのは割とだるい。アルゴリズム自体はすぐに思い浮かんだのだが、実際にコードを書くと、様々な問題に悩まされた。

takeWord s = takeWhile (not . isSpace) $ dropWhitespace s
dropWhitespace s = dropWhile isSpace s

words' [] = []
words' s =
    let
        word = takeWord s
        rest = drop (length word) $ dropWhitespace s
    in
        word:(words' rest) 

アルゴリズムとしては、文字列の先頭から連続する空白文字をdropし、空白文字が現れるまでtakeし、今回の処理した文字列分dropし、再帰する。

これで使った関数も実装してみた。

takeWhile' _ [] = []
takeWhile' f (x:xs) =
    if f x
    then x: takeWhile' f xs
    else []

dropWhile' _ [] = []
dropWhile' f p@(x:xs) =
    if f x
    then dropWhile' f xs
    else p

drop' _ [] = []
drop' n x | n <= 0 =  x
drop' n (_:xs) = drop' (n-1) xs

length' [] = 0
length' (x:xs) = 1 + length' xs

not' True = False
not' False = True

ちなみに、模範解答はghc/libraries/base/data/OldList.hsにある。

words s                 =  case dropWhile {-partain:Char.-}isSpace s of
                                "" -> []
                                s' -> w : words s''
                                      where (w, s'') =
                                             break {-partain:Char.-}isSpace s'

なるほどbreakは面白い仕組みだ。文字列の切り出しと文字列の先頭のdropを同時にやれるのでコードがきれいになる。早速実装してみよう。

break' _ [] = ( [],[] )
break' f p@(x:xs) =
    if f x
    then ( [], p )
    else let ( a, b ) = break' f xs
         in ( x:a, b )

模範解答。

break                   :: (a -> Bool) -> [a] -> ([a],[a])
#if defined(USE_REPORT_PRELUDE)
break p                 =  span (not . p)
#else
-- HBC version (stolen)
break _ xs@[]           =  (xs, xs)
break p xs@(x:xs')
           | p x        =  ([],xs)
           | otherwise  =  let (ys,zs) = break p xs' in (x:ys,zs)
#endif

USE_REPORT_PRELUDEは、Haskellの仕様書であるHaskell Reportの定義どおりの実装だ。Haskell Reportの定義は効率ではなく正しさだけを目的としている。通常はUSE_REPORT_PRELUDEではない方が使われる。

ところで、"break _ xs@[] = (xs,xs)"は、"break _ [] = ([],[])"と書くのと変わりがないと思うのだが、何か違いがあるのだろうか。

さて、ここまで何の問題もなく実装できているように見えるが、実際は些細な間違いで何時間も悩んでいる。

最初に書いたwords'は以下のような間違った結果を返した。

> words' "aaa bbb  ccc"
["aaa", "bbb", "b", "ccc", "cc"]

これはなぜかと言うと、処理した文字列を切り取る処理が以下のようになっていて、空白文字分を考慮していなかったからだ。

rest = drop (length word) s

しかし問題の原因を特定するのには苦労した。標準入出力が使えないので、最も原始的なprintfデバッグすらまだ使えないためだ。traceというものはあるが、問題はtraceの評価も実際に評価したときにしか行われず、現時点でHaskellのコードを評価する方法として、GHCiに食わせて表示させるということぐらいしか知らないため、traceの出力と本来の出力がごちゃまぜになって極めてわかりにくい。

もう一つハマった落とし穴は、dropWhileを実装していたときだ。以下のように間違ったコードを書いてしまった。

dropWhile' _ [] = []
dropWhile' f p@(x:xs) =
    if f x
    then dropWhile' xs
    else p

間違っている場所がわかるだろうか。私はわからない。GHCのエラーメッセージは型が違うということは知らせてくれるが、具体的にどこのコードが期待している型が違うのかということは教えてくれない。GHCが指し示す問題のある行は、"dropWhile' f p@(x:xs) ="だ。しかし、問題は"then dropWhile' xs"にあるのだ。エラーメッセージは、dropWhile'の型は(t -> Bool) -> [t] -> [t]だが、[t] -> [t]として使おうとしてエラーになっていることを教えてくれる。そこまで分かっているのならば、[t] -> [t]としてdropWhile'を使おうとしている箇所を教えてくれたっていいだろう。技術的にできるはずなのに、なぜか教えてくれない。

Haskellの実装であるGHCのエラーメッセージはあまりにも不親切すぎる。

2017-12-23

中古PC市場

ここ数年、私のメインのコンピューターは常に中古品だ。これにはいくつか理由がある。

中古品は製造元が購入に当たって同意を要求するかもしれない不自由な契約に同意しなくてもいい。契約は同意して初めて効力を持つ。私はメインで使う私の思想の延長線上にあるコンピューターが、不自由な契約で制限されることを好まない。

中古品は妥協ができる。私が新品のコンピューターを買うときは、妥協が出来ない。コンピューターに25万円だすのと30万円だすのとでは、私の中では誤差程度の違いしかない。しかし、中古品であれば妥協ができる。5万円の捨て値で買うコンピューターであれば、極端にこだわっても違いがないのだ。

中古品を探すのは面白い。中古品は市場にあるもので妥協しなければならないが、その特製は新品よりはるかにばらつきが大きい。

中古PC市場をみていると、私の考える価値と、古物商の考える価値がだいぶ食い違っていることがわかる。

どうやら、世の中のコンピューターにはMicrosoftの不自由で不便なWindowsというOSがプレインストールされていなければ売れないと考えているらしい。なので、古物商は、もしWindowsがインストールされていない、あるいはMSの屈辱的なライセンスキーが欠けているコンピューターを入手したときは、これにWindowwsをわざわざ入れて販売する。これにより約立たずなWindowsのライセンス料の分だけ中古PCの値段が上がる。残念なことだ。

また、PCが発売された時期が中古品の価格に大きく影響するらしい。例えば2年前に発売されたまことにゴミクズのような救いようのない性能の中古PCを高い価格をつけているのに、4年前のそれより遥かに性能が良い中古PCは、より古いという理由で価格が安かったりする。もちろん、古いということはそれだけ経年劣化している可能性もあることには注意しなければならない。

中古PC市場でよく目立つのはリース品だ。企業向けにリースされて返却された後の同一型番のコンピューターが大量に古物商に出回っている。元リース品は中古市場への供給が多いので価格が安めになりがちだ。

さて、私は中古PCを選ぶのに何を目安にしているのか。

まず何はともかくメモリ容量だ。この2017年にメモリはどんなに妥協しても最低でも8GBなければならず、16GBはほしい。

CPUにAtomを選んではならない。ただし、求めるものがとても小さくバッテリー時間であり、その他はどうでも良いのであれば、Atomでもよいだろう。

GPUにnvVIDIAを選んではならない。nVIDIAは不自由なソフトウェアを蔓延させることを目的とした企業だ。

ただ、次のメインPCは中古品ではなくて新品を買うかもしれない。というのも、最近Intelの悪意あるバックドアであるIntel MEを無効化する方法が見つかったため、Intel MEを無効化して出荷する自由志向のPCを販売するところが現れ始めたからだ。

2017-12-20

Haskellへの巡礼

C++の入門書を書くという目的のため、C++を一旦離れてHaskellを学ぶことにしてはや三日目。初日は環境構築で潰れ、二日目はリストを学ぶだけで終わった。

Haskellのリストは、単に順序のある値の集合だけではなく、[1..10]のように規則的な数列を作り出す機能も持っている。またList comprehensionsといって、リストからリストを生成する機能もある。

二日目はリスト処理を学ぶだけでだいぶ疲れた。何も難しいことをしている実感はないのだが、全く馴染みのない新しい概念を学ぶということはかくもつらく苦しいものだとは、久しく忘れていた。馴染みのない新しいことを学ぶのはつかれるということは覚えておかなければならない。

そして三日目はtupleを学んだ。

List comprehensionsはなかなか興味深い機能で、本質的にループであり、imperative languageでループを書いて実装する計算のうちの一部はList comprehensionsで実装できるようだ。

しかし、一部でしかない。そこがつらい。私にとってプログラミングとは、標準入出力と入力で得たものを処理して出力することなので、なにはともかくも標準入出力を扱いたいのだが、Haskellで標準入出力を自在に扱うためには覚えることがまだ色々とあるらしく、まだ手を出すことができないでいる。

四日目。型と型クラスを学び、関数とパターンマッチについて学んでいる。Haskellについて少しわかってきた。

しかし、List Comprehensionsでパターンマッチに失敗した要素は単に無視されるのがとても気に食わない。

xs = [ [1,2], [], [3,4] ]
 [a + b | [a,b] <- xs ]
[3,7]

Haskell 2010 Reportにも書いてあるので受け入れるしかないが。

3.11 p2: and if a match fails then that element of the list is simply skipped over.

まだHaskellの全体像をつかめていないのでHaskell 2010 Reportを本格的に読んでいないが、パターンマッチは3.17で規定されている。まだ知らない知識が多すぎて読めない。

7日目。Higher Order Functionsについて学んでいる。もうHaskellはだいぶ理解できてきた。試しにPandocのコードを読んでみたが、部分的に意味がわかるようになっていた。これならば後は全部学ぶだけだ。

さて、Imperativeな言語であるC++が専門の私がHaskellに思うこととしては、やはり速度だ。

まず最適化だが、sumのようなリストの合計値を計算するコードがあったとして、

accumulate [] = error "empty list."
accumulate [a] = a
accumulate (h:t) = h + accumulate t

このコードは毎回リストが空であるかどうかのパターンマッチが入るので非効率的であるが、リストが空でなければ、その後リストが空になることはないので、賢いコンパイラーならば、空のリストのパターンマッチを最初の一回だけにするように変換できるはずだ。

accumulate [] = error "empty list"

accumulate x = accumulate' x
    where   accumulate' [a] = a
            accumulate' (h:t) = h + accumulate' t

これをcall-pattern optimizationと呼び、Haskellではデフォルト無効で、-fspec-constrもしくは-O2で有効になる。デフォルトで有効になっているべき最も初歩的な最適化のように思える。

Haskellはとても賢いコンパイラーがimperativeな操作を行うように最適化を行えば早くなるだろうが、現状ではそうではない。例えばhaskell.orgのクイックソートの例はひどい。

https://wiki.haskell.org/Introduction#Quicksort_in_Haskell

このHaskellのクイックソートのコードは簡潔でアルゴリズムを学ぶのに最適だが、とても遅い。それに反論するために以下のページがある。

Introduction/Direct Translation - HaskellWiki

曰く、Haskellのナイーブなクイックソート例は遅いので、世の中にはHaskellの拘束なクイックソート例と称するものがいくつも出回っているが、どれもコンパイルできない。

そこで、HaskellでCのクイックソートと同等のコードが示してあるが、Cのコードより長く、Cのコードより読みにくく、そして依然としてCのコードより2倍は遅い。

もはや喜劇もいいところだ。

8日目。再びlistとlist comprehensionsについて考えている。Haskellのリストは、C++でいえば、配列であり、for文であり、std::iotaでもある。遅延評価されて適切に最適化されるのであれば速度も問題ないだろうが、しかしクイックソートの例はひどい。

pattern guardsの存在を知ったので、すべてのパターンマッチはcase式で書けるというHaskell 2010 Reportの記述が確認できた。

Haskell 2010 Reportは少しづつ読み始めているが、至るところで何の言及もなく使われているotherwiseキーワードがないのが気になった。GHCIでotherwise = 0と書いてみると何の問題もなく実行される。試しに:t otherwiseしてみると、どうやらotherwiseとは引数を取らずBoolを返す関数のようだ。なるほど・・・いや、それはもしかして・・・やはり単なるotherwise = Trueか。

9日目。ちょっと学ぶ速度が落ちてしまったがのんびりと入門書を読み続けている。ところで、たまたまPCを再起動したときに、ブート直後のGHCIの初回起動が信じられないほど遅いことに気がついた。プロンプトが表示されるまでに5秒はかかる。次回以降の起動は一瞬だ。一体なぜなのか。大規模なファイルシステムへのアクセスをしていてファイルシステムへのキャッシュの有無なのだろうか。そのコンピューターはM.2 SATA SSDを使っていたが、この仮設を確かめるために、よりストレージ性能の高いM.2 NVMe SSDを搭載した別のコンピューターをリブートしてGHCIを起動してみた。やはり初回起動はプロンプト表示までに2秒ぐらいかかる。

だいたい、言語の習得自体に問題がなくなってくると、こういう些細なところが気になってくる。

2017-12-19

nVidia、GeForceのデータセンターでの利用を禁止する

NVIDIAが規約変更によりGeForceのデータセンター利用を制限。大学などの研究活動にも大ブレーキ - WirelessWire News(ワイヤレスワイヤーニュース)

また清水亮がポエムを書いている。困るんだよね、名前の同じ人間にそういうことをされると私まで詩人だと思われてしまう。

nVidiaは確かに邪悪で不自由で存在自体が人道上の罪にあたる極悪企業であり、かのLinuxカーネルの最高開発者であるブリリアント・アッスホールの称号も名高いリーナス・トーバルズにも中指を突き立てられてFから始まるとてもここで書くことができないほどの醜悪極まりない侮辱の四文字言葉で罵られたほどの救いようのない時勢の読めない烏合の衆ではあるが、まさか自らの飯の種であるデータセンターへの利用を禁止するほどの寓話に出てくる金の卵を生む鶏を割くほどの阿呆ではないだろう。どれどれ、この私が直々にソースとやらを検証しことの真偽を確かめてやろう・・・マジじゃねぇか清水亮!

問題となっているのは、nVidiaの物理的な製品であるGeForceとTitanではなく、この製品を利用するためのドライバーのEULA、"License For Customer Use of NVIDIA GeForce Software"だ。

日本国版は以下のようになっている。

2.ライセンスの付与

2.1 付与に関する権利と制限。NVIDIA は本ライセンスをもって、お客様が所有する NVIDIA GeForce または Titan ブランドのハードウェア製品と共に使用するために本ソフトウェアをインストールし、使用する非独占、譲渡不可能のライセンスをお客様に付与します。ただし、以下の制約があります。

2.1.3 制限

データセンターへの導入の禁止。データセンターへの導入の目的では、本ソフトウェアのライセンスは付与されていません。ただし、データセンターにおけるブロックチェーン処理を行うことは許されます。

翻訳ミスかもしれぬので、アメリカ合衆国版も確認すると、以下のようになっている。

http://www.nvidia.com/content/DriverDownload-March2009/licence.php?lang=us&type=GeForce

2. GRANT OF LICENSE

2.1 Rights and Limitations of Grant. NVIDIA hereby grants Customer a non-exclusive, non-transferable license to install and use the SOFTWARE for use with NVIDIA GeForce or Titan branded hardware products owned by Customer, subject to the following:

2.1.3 Limitations.

No Datacenter Deployment. The SOFTWARE is not licensed for datacenter deployment, except that blockchain processing in a datacenter is permitted.

なんと、アメリカ語でも同一の内容で、翻訳のミスではない。この他の部分も念のために読んでみたが、この制約を上書きするような記述は見つけられなかった。

このライセンスはnVidiaのGeForceとTitan用のドライバーをダウンロードするときに同意を求められるもので、ライセンス本文中にもGeForceとTitanと書いてある。このドライバーがなければGeForceとTitanは実質的に使用不可能だ。そのドライバーがデータセンターにデプロイできないとあっては、シリコン製の電力効率の悪い暖房器具をデータセンターに配置するようなもので、何の意味もなくなる。

ブロックチェーンの処理のためであればデータセンターにデプロイできるというのは、その手の計算はAMDのGPUのほうが得意で市場を取っているためだろう。ようするにnVidiaという企業は一度市場シェアを獲得すれば足元を見てあぐらをかいて殿様商売をする邪悪で不自由な企業ということだ。はやく適切な自由市場による淘汰を受けてほしい。

RMSは自由なソフトウェアの定義としてまず第一に、自由ゼロ、「プログラムをいかなる目的においても望みどおりに実行する自由」を掲げた。これが一番最初にあるのは、最も重要だからだ。例えば現実の不自由ソフトウェアは、非エンタープライズ目的に限るとか、1台の物理的なコンピューターに限るとか、自社の提供する物理的なコンピューターに限るとか、様々な不平等で屈辱的で消費者の権利を不当に制限する契約を要求している。そのような不自由ソフトウェアの脅威を受け入れておきながら、今回のnVidiaの契約の妥当性に疑問を示す人間は論理的な思考ができない人間である。

2017-12-12

C++の入門書を書くためにHaskellを学ぶことにした

C++17の参考書、江添亮の詳説C++17はすでに書き上げて、来年の出版を待つばかりになっている。

https://github.com/EzoeRyou/cpp17book

次に書く本はC++の入門書にしようと思っているが、入門書を書く前に、少し時間をかけてHaskellを学ぼうと思っている。

なぜHaskellを学ぶのか。Pandocのためだ。

Pandoc

私の本は、Markdownで書いてPandocで各種フォーマットに変換している。アスキードワンゴでは、Pandocを使ってlatexに変換した上で、手作業で出力されたlatexを編集して組版している。つまり、私の参考書の執筆はPandocに支えられていると言ってよい。

さて、アスキードワンゴ編集部(ドワンゴ)は私が本を出版契約している出版社であり、かつ私が雇用契約している会社でもある。アスキードワンゴの編集者は私の編集者であり同僚でもある。そういったサザエさんの家系図なみに複雑な理由で私はアスキードワンゴの編集者の負担を減らすことにインセンティブがある。編集者はPandocに不満点があり、改良したいと思ってはいるが、Haskellなので手が出せずにいる。話を聞く限り、不満点の解消は、Pandocのコードが理解できれば、それほど難しい変更でもなさそうだ。問題は、PandocはHaskellで書かれているということだ。

Haskellというのは私が今までやってきたプログラミング言語とはだいぶパラダイムの異なる言語で、私はC++に関してはだいぶ理解したが、Haskellという点ではもはやプログラミング未経験者に近い。

さて、早速定評のあるHaskellの入門書、Learn Haskell for Great Goodも買い込み、届くのを待っている間はオンライン版を読みながらHaskellを学ぶとしよう。

Chapters - Learn You a Haskell for Great Good!

プログラミング言語を学ぶには、まずプログラミング言語の実行環境を用意することが重要だ。どうやら、Haskellの実行環境はHaskell Platformとやらで揃うらしい。UbuntuでHaskell Platformをいれるには、

sudo apt install haskell-platform

すればよい。

しかし、どうやらHaskell Platformはもうイケてないらしい。最近のクールなキッズは全員Stackとやらを用いるそうだ。

stackを使うには、シェルスクリプトを落として実行するか、あるいはapt install haskell-stackして、stack upgradeすればいいらしい。さっそくstack upgradeしたが、延々とライブラリのビルドを始めた。小一時間書けてビルドが終わった後に、stack ghciを実行したが、どうやらstack setupしていなかったのでまずghciのビルド済みバイナリのダウンロードが走り、たまたまWiFi経由でダウンロード速度が遅かったので、これまた時間がかかった。

なるほど、環境構築だけで一日が終わってしまった。やったこととしてはコマンドを数行叩くだけなのだが、GHC, Haskell Platform, StackといったHaskellの実行環境の歴史などを学ぶのにだいぶ時間を要した。

この経験から私は、環境構築の解説は丁寧に行うべきだという教訓を得た。C++の入門書の執筆に参考になる経験が、まだHaskellを一行も書かないうちから得られた。

さて、Haskellを学び始めるか。

ドワンゴ広告

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

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

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

江添ボドゲ会@12月17日

毎月恒例の江添自宅ボドゲ会を以下のとおりに開催します。

江添ボドゲ会@12月17日 - connpass

2017-12-11

来年出版予定の「江添亮の詳説C++17」の組版に使うTEXを公開

来年出版する予定のC++17の新機能を解説した参考書、書名は現在、「江添亮の詳説C++17」を予定している本の組版に使うTEXを公開した。

https://github.com/EzoeRyou/cpp17book

この参考書は今年2017年に9ヶ月ほどかけて書いていた本で、C++17に追加された新機能のほぼすべてを解説している。

この参考書は、アスキードワンゴから出版される予定だ。アスキードワンゴでは本の組版にTEXを使っている。

私は自由なライセンスの価値を信じるものであり、本も自由になるべきだと信じている。私の書いた本は自由なライセンスにできるとして、組版に使ったTEXも公開したい。TEXは明らかにソースコードに当たるものであり、ソースコードが公開できなければGPLv3ではライセンスできないのでCC-BY-SAなどを使わなければならない。私が執筆したC++11/14コア言語は、こういった理由でCC-BY-SAだった。

さて、今回の本では、TEXも公開できることになった。すでにmarkdownで書かれた本のソースコードは公開しているので、まだ出版前だがTEXも公開してしまうことになった。これで、今回の本「江添亮の詳説C++17」は自由なライセンスであるGPLv3でライセンスすることができる。

商業出版で使われる本のTEXを公開してどういう効果があるのか、私には予測できない。いい効果があると今後にもつながるのでうれしい。

アスキードワンゴの編集者が作成したTEXを眺めた感想としては、自動化が難しそうだと感じる。今回公開したTEXは私がmarkdownで書いて、pandocでTEXに変換し、それを編集者が手で編集することで作成されている。著者としては編集者の労力を減らすために自動化できるところは自動化したいのだが、編集済みのTEXをみると、結局、編集者が人力ですべてをチェックしなければならないことに変わりはない。

そして、PandocはHaskellで書かれているので、PandocをハックするためにはHaskellを学ばねばならない。Haskellを学ぶのは大変だ。

そういう話をしていたら、同僚のHaskell大好き人間からアスキードワンゴはHaskell本を出しているではないかとツッコミが入った。

Haskellによる関数プログラミングの思考法 - アスキードワンゴ

学ばねばならぬのか。

ドワンゴ広告

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

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

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

2017-12-06

C++標準化委員会の文書: P0790R0-P0799R0

P0790R0: library-operator-spaceship

operator <=>が標準ライブラリに与える影響について。

operator <=>によって生成される比較演算子は、ソースコードの書き換えと同等の効果を及ぼすのであって、実際の比較演算子ではないため、アドレスを取ることができない。これにより、opeartor <=>アドレスを取るユーザーコードは動かなくなる。これは互換性の問題となるが、そもそも比較演算子のアドレスを取るユーザーコードはまれだ。というのも、比較演算子はメンバー関数とフリー関数のいずれかで実装されるので、ユーザーコードでアドレスを取るにはどちらで実装されているか知らなければならない。また、一部の標準ライブラリは、a @ bのような式が妥当だとのみ規定されていて、operator @がどのように実装されているかを規定していない。その場合、ユーザーコードでアドレスを取った場合、そもそも未定義となる。なので、互換性の問題はまれにしか起こらないはずだ。

文書は、標準ライブラリで比較可能な型のうち、operator <=>に対応すべきではない型、変換関数を捨ててopeartor <=>に切り替えるべき型、既存の比較演算子に加えてoperator <=>を追加したほうがよい型、operator <=>を追加したほうがよい他の型をラップしている型、現在比較を提供していないがoperator <=>に対応したほうがよい型、operatgor <=>を追加したほうがよいCの型を列挙している。

valarrayというだいぶ忘れ去られた型も考慮している。

よく調べたものだ。

[PDF] P0791R0: Concepts are Adjectives, not Nouns

コンセプトCを使うtemplate < C X >を、template < C typename X >に変える提案。

template < C X >の意味はわからない。Xは型なのだろうか。値なのだろうか。CがコンセプトならばXは型だが、Cがintへのtypedef名ならば、Xは値だ。この問題は、内側のスコープの宣言で名前が隠れるともっとややこしくなる。


template < typename T > concept C = true ;

// Xは型
template < C X > struct S ;

namespace ns {

using C = int ;

// Xは値
template < C X > struct S ;
}

字面は同じtemplate < C X >なのに意味が変わってしまう。

この問題は、本来形容詞であるコンセプトを名詞として使っているから生じるのであって、形容詞的な文法にすればいい。すなわち、文法を以下のように変更する。

template < CopyConstructible typename T > struct S ;

この変更により、複数のコンセプトを簡単な文法で指定することも可能になる。

template < CopyConstructible LessThanCompareble tyepname T > struct S ;

なるほど、趣旨はわかるが文法が冗長になる。

P0792R0: function_ref: a non-owning reference to a Callable

Callableを所有しないラッパーfunction_refの追加。std::functionと違いメモリ確保をせず例外も投げない。

[PDF] P0793R0: SG5: Transactional Memory (TM) Meeting Minutes 2017/06/19-2017/10/09

Transactional Memoryの会議の議事録。

[PDF] P0794R0: SG14: Low Latency Meeting Minutes 2017/08/09-2017/10/11/

Low Latency会議の議事録。

[PDF] P0795R0: From Vulkan with love: a plea to reconsider the Module Keyword to be contextual

Vulkanより愛をこめて。

モジュールTSではmoduleというキーワードの追加を提案しているが、moduleという識別子はすでに多くの既存のソースコードで使われている。特にKhronos Groupの低級グラフィックAPIのVulkanでもmoduleを識別子として使っている。moduleは文脈依存キーワードにすべき。

[PDF] P0796R0: Supporting Heterogeneous & Distributed Computing Through Affinity

NUMAが当然となった現代では、メモリーアフィニティの重要性はいよいよ増すばかりだ。複数のスレッドによる並列処理を行ったとしても、そのメモリーが特定のひとつのスレッドだけで効率的にアクセスできるようになっている場合、並列処理はスケールしない。このようなメモリーとスレッドの関係をアフィニティと呼ぶ。

そこで、C++は標準でシステムのリソーストポロジーをクエリーし、メモリー領域の相対的なアフィニティをクエリー子、実行とメモリ領域を制限するようなアフィニティの設定機能を持つべきだ。

そんなの標準化できるのだろうか。

[PDF] P0797R0: Exception Handling in Parallel STL Algorithms

並列アルゴリズムを中断する方法の提案。

シーケンシャルアルゴリズムは例外を投げることによって中断できた。並列アルゴリズムも提案段階ではexception_ptrを複数もつexception_listを例外として投げることで中断できる予定だったが、この設計には色々と問題があったので廃止された。

この提案では、例外を格納するdisappointment_buffer<disappointment_type>を追加し、これを実行ポリシーオブジェクトで並列アルゴリズムに渡すことで例外リストを受け取る設計を提案している。

P0798R0: p0798r0: Monadic operations for std::optional

optionalにモナド操作として、and_then, or_else, mapを追加する提案。

画像から猫を抽出してより可愛くするコードを考える。

image get_cute_cat (const image& img) {
    return add_rainbow(
             make_smaller(
               make_eyes_sparkle(
                 add_bow_tie(
                   crop_to_cat(img))));
}

しかし、画像に猫が含まれていなかったらどうなるのだ。画像に蝶ネクタイを付加すべき場所が存在しなかったら、 猫が後ろを向いているので目を輝かすことができなかったら。それらの条件では、画像を返すことができない。

例外を使うというのも手だが、プログラムが画像を大量に処理するもので、画像に猫が含まれないことは例外的ではない場合、例外をコントロールフローに使うのはよろしくない。

そこでoptionalだ。

std::optional<image> get_cute_cat (const image& img) {
    auto cropped = crop_to_cat(img);
    if (!cropped) {
      return std::nullopt;
    }

    auto with_tie = add_bow_tie(*cropped);
    if (!with_tie) {
      return std::nullopt;
    }

    auto with_sparkles = make_eyes_sparkle(*with_tie);
    if (!with_sparkles) {
      return std::nullopt;
    }

    return add_rainbow(make_smaller(*with_sparkles));
}

しかし、こんなボイラープレートコードを書きたくない。

そこで、optionalのメンバーにmap, and_then, or_elseを追加すると、以下のように書ける。

std::optional<image> get_cute_cat (const image& img) {
    return crop_to_cat(img)
           .and_then(add_bow_tie)
           .and_then(make_eyes_sparkle)
           .map(make_smaller)
           .map(add_rainbow);
}

趣旨はわかるが現実の問題を解決するC++のコードが、そんなにきれいに引数をひとつ受け取ってひとつ返すような簡単なコードになるわけがなく、したがってこれを使おうとするとラムダ式で囲んだボイラープレートを書かねばならず、机上の空論に見える。

[PDF] P0799R0: Programming vulnerabilities for C++ (part of WG23 N0746)

C++標準規格の文面に脆弱性に対する忠告の文面を入れようという提案

ドワンゴ広告

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

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

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

2017-12-02

仮想通貨と税金についての国税庁の見解についての所感

http://www.nta.go.jp/shiraberu/zeiho-kaishaku/joho-zeikaishaku/shotoku/shinkoku/171127/01.pdf

国税庁が仮想通貨と税金について見解を出しているが、これの一部が私の直感とだいぶ異なる。

1から9までの例について、具体的な暗号通貨であるBitcoin, Ethereum, Bitcoin Cashと日本円を出して考える。その価値は特に実態とは関係がない。

1. 暗号通貨Bitcoinを1万円分買って、後に暗号通貨Bitcoinを2万円で売った。所得=収入-支出=2万円-1万円=1万円となり、1万円の所得が発生する。

わかる。

2. 暗号通貨Bitcoinを1万円分買って、後に暗号通貨Bitcoin全額を支払って2万円の商品を買った。2万円分の収入が発生した扱いになり、所得=収入-支出=2万円-1万円=1万円となり、1万円の所得が発生する。

わかる。

3. 暗号通貨Bitcoinを1万円分買って、後に暗号通貨Bitcoin全額を支払って暗号通貨Etheriumを2万円分買った。2万円の収入が発生した扱いになり、所得=収入-支出=2万円-1万円=1万円となり、1万円の所得が発生する。

理解に苦しむ。暗号通貨から暗号通貨のやり取りには日本円が一切関与していない。

有名画家から絵をもらったとして、その有名画家の絵は市場で1万円の取引実態があるので、1万円の所得が発生したというようなものだ。まだ現金が一切関与していないのに絵を入手しただけで所得になるのはおかしい。

4. これを真面目に計算するのはだるいので具体例を出すのが面倒だが、まあ理解できる

5. 暗号通貨Bitcoinを1万円分買った。後に暗号通貨Bitcoinがforkして、暗号通貨Bitcoin Cashができた。その結果、BitcoinとBitcoin Cashを両方持つことになった。所有するBitcoinの価値は1万円と変わらず、Bitcoin Cashの価値は1千円となった。この場合、1千円の収入が発生した扱いになり、支出は0円で、1千円の所得が発生する。

うーむ・・・結果的に利益が発生したわけではあるが、やはり現金が一切関与していないのに所得となるのはおかしい。

世界に2枚しかない有名画家の絵の1枚を所有しているとして、もう1枚が焼けてなくなったので、自分の持っている絵の価値が1万円分上がった。したがって1万円の所得が発生したというようなものだ。まだ現金が一切関与していないのに所得になるのはおかしい。

6. Bitcoinが雑所得以外に分類される場合について。

わかる。

7. Bitcoinが雑所得とみなされる場合の損失は雑所得のなかだけで通算。

わかる。

8. 暗号通貨にはFXのような投機としての特別措置はない。

実態はもはやFXに近い気がするが、本来の意図した状況ではない気がする。

9. 2万円分の暗号通貨Bitcoinをマイニングするためにコンピューターと電気代で1万円をかけた。収入が2万円、支出が1万円。所得=収入-支出=1万円の所得が発生する。

やはり具体的な現金との関与が一切ないのに所得扱いになるのは解せない。

Bitcoinは実態としては絵に近い。例えば私が有名画家で、私の書いた絵には必ず1万円を支払って購入したい人が列をなして並んでいるとして、私が直ちに売らない絵を描いた場合、私は絵を生み出したのでその場で売らなかったとしても1万円の所得が発生することになるというのと同じ奇妙さがある。