GHC 8.0.1/base-4.9.0.0の新機能まとめ
GHC 8.0.1は、最上位の桁が変わるだけあって、かなり新しい機能が追加されている。
base-4.9.0.0
めっちゃインスタンスが増えた
ghc/changelog.md at ghc-8.0 · ghc/ghc · GitHubを参照。あるべきインスタンスが存在することにより、孤児インスタンスを定義する必要がなくなるため、ぐっとストレスが減る。Monoid a => Monad ((,) a)
、Traversable ZipList
など、いくつかは私がやった。
Semigroup
ついにData.Semigroupが追加された。将来的にはこれはモノイドのスーパークラスになる。この変更によって、よりリーズナブルな定義ができるということも少なくないはずだ。
ベーシックな型が充実
Compose
, Product
, Sum
, NonEmpty
など、決して利用頻度が高くないとはいえ基礎的かつ重要な型が追加された。種多相になっているので型レベルプログラミングフリークにとっても嬉しい。
MonadFail
ついにfail
がMonad
から切り離された。もはやMonad
には一片の羞恥もない。
この変更のため、いくつかの警告や言語拡張が追加された*1。
Applicativeへの一般化
forever, filterM, mapAndUnzipM, zipWithM, zipWithM_, replicateM, replicateM_, traceM, traceShowM
がMonadからApplicativeへと一般化された。痒い所に手が届くようになるだろう。しかし、(*>)
が(>>)
より効率の悪い実装になっていると深刻なダメージになりうるため、Applicativeのインスタンスにはより関心を向けなければならない。
ApplicativeDo
ApplicativeDo
拡張を有効にすると、以下のコードをf <$> foo <*> bar
のようにしてくれる。
do x <- foo y <- bar return (f x y)
MonadよりApplicativeのほうが効率のよいような構造(クラシカルFRPにおけるBehaviorなど)では大いに役立つ。
Strict / StrictData
StrictData
を有効にすると、データ型のフィールドにデフォルトで正格フラグが付与される。アプリケーションを書くうえでフィールドを遅延評価させたい場面は少なく、いちいち!(……)
と書く手間がなくなるため非常に有用だ。これをずっと待ち焦がれていた。
Strict
を有効にすると、さらにありとあらゆる束縛が正格評価になる。やりすぎな気がしなくもないが、案外実用上は問題ないかもしれない。
TypeError
GHC.TypeLitsにGHC.TypeLits.TypeError
が追加された。type family TypeError (msg :: ErrorMessage) :: k
なる型族で、これを使うと型エラーを作れる。型レベルプログラミングがさらに楽しくなるだろう。
OverloadedLabels
IsLabel
というクラスが追加された。
class IsLabel (x :: Symbol) a where fromLabel :: Proxy# x -> a
OverloadedLabels
拡張を有効にすると、#foo
が(fromLabel @"x" @alpha proxy#)
(fromLabel (Proxy :: Proxy "x")
のようなもの)の構文糖衣になる。これにより多相なアクセサを定義できる可能性が生まれたが、非常に残念ながら型クラスの性質上van Laarhoven lens(lensパッケージのLens)にはできない。非常に残念だ。
GenericsのMeta
GHC.GenericsにMeta
という種が追加され、型定義のメタデータを表すM1
のパラメータとして与えられる。
data Meta = MetaData Symbol Symbol Symbol Bool | MetaCons Symbol FixityI Bool | MetaSel (Maybe Symbol) SourceUnpackedness SourceStrictness DecidedStrictness
Genericsでフィールドやコンストラクタ名を扱うのは骨の折れる仕事だが、これのおかげでいくらか楽になるに違いない。
種の同一性
種の同一性がきちんと扱われるようになった。これによりGADTの型レベルへの昇格や、種族の定義が可能になる。
型族のワイルドカード
type family Tail (xs :: [k]) :: [k] where Tail (x ': xs) = xs
と書かなければならなかったところを、
type family Tail (xs :: [k]) :: [k] where Tail (_ ': xs) = xs
と書けるようになる。ささいなことだが、型レベルプログラミングジャンキーにとってはナイスな改良だ。
単射なる型族
type family F x y = a | a -> x, a -> y
のようにして、型族が単射であることをあらかじめ定義できる。これにより、うまく型推論してくれる場面が増える。型レベルプログラミングフィーンドもこれでさらに活躍できる。
コールスタック
ImplicitParams拡張を有効にしたうえで、?callStack :: CallStack
という暗黙のパラメータが使えるようになる。しかしこれはいかがなものか……筆者としてはImplicitParameter自体かなり悪いアイデアだと思う。
コンパイル時間
型周りの大きな拡張により、コンパイル時間も追加されたようだ。エスプレッソを抽出したりラテアートを作る余裕もできるだろう。
比を最適化する
二つの負でない実数、を考える。比をある値に近づけたいといった条件が複数あり、それらを最適化したいとき、どうするのがよいだろうか。
序: 近道の階段
簡単な方法の一つとして考えられるのは、単純に比の差をとり、それらの平方の和を最適化の対象とするというものだ。
しかし、これは最適化の結果、しばしば、のどちらかが0にぶつかってしまう。これは目的関数として非常にいびつであり、直感的とも言いがたい。
破: バリアフリー化
0や1に近い比率は極端であり、望まれていない。境界に近づくほど目的関数が無限大に発散するようにできないだろうか。
そんなときに使えるのがロジットだ。ロジットは0より大きい1未満の実数を任意の実数に写像する関数である。
この関数を比に適用することで、極端な比にはそれなりに大きなペナルティが、なめらかでありながらも力強く課されるようになる。
とおくと以下のようにも表せる。
こうすれば、境界を気にすることなく最適化することができる。最適化される変数にとってもこれは心地の良いことだろう。
急: ノーマライゼーション
めでたしめでたし…と言いたいところだが、まだ気になる点はある。目的の比がもともと0や1に近ければ、は大きな絶対値を取る。そのため、その比にやたらと敏感になってしまう。差をロジットの導関数で割ってやることで、各比の感度を目的の値にかかわらず一定にできる。
まとめ
本記事では比を最適化するための方法について議論した。この考え方は多かれ少なかれ直感に基づいており、直感にうまく当てはまる表現を見つけることは有用だと感じた。
デシリアライザとスキーマ
盛大に遅れました…
最近思いついたネタで実用性の高そうなものを紹介。
binaryやcerealのようなライブラリはデータを密にシリアライズするが、その際にフィールド名や型などの情報は失われてしまう。かといってそれらを一つ一つすべて含めるとひどく効率の悪いフォーマットになってしまう。そこで、スキーマを分離できるような仕組みを作れないかと考えて作ったのがこのクラスだ。
{-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, UndecidableInstances #-} import Data.Binary class HasSchema a where type Schema a :: * toSerializer :: a -> Put toDeserializer :: Schema a -> Get a
HasSchema
クラスは、普通にシリアライズするメソッドtoSerializer
と、スキーマを使ってデシリアライズするtoDeserializer
を持っている。Schemaは型族なのでどんなものでも使える(一般にどうあるべきかよく考えていない)。
instance HasSchema a => HasSchema [a] where type Schema [a] = [Schema a] toSerializer = mapM toSerializer toDeserializer = mapM toDeserializer
同じスキーマの値が連続している場合、以下のTable
という型を使うと無駄がない。
newtype Table a = Table (V.Vector a) instance HasSchema a => HasSchema (Table a) where type Schema (Table a) = Schema a toSerializer (Table v) = put (V.length v) >> mapM toSerializer v toDeserializer sch = get >>= \n -> Table <$> V.fromListN n <$> replicateM n (toDeserializer sch)
このSchematic
というデータ型はスキーマとデータの対にすぎないが、これで包んでおくことで簡単にHasSchemaの力を引き出せる。
data Schematic a = Schematic !(Schema a) !a instance (HasSchema a, Binary (Schema a)) => Binary (Schematic a) where get = get >>= \sch -> Schematic sch <$> toDeserializer sch put (Schematic sch a) = put sch >> toSerializer a
今のところ、社内で使っているレコード型を簡単にシリアライズするために実験的に使っているが、より一般的で面白い使い方があるかもしれない。この記事では、実体のあるHasSchema
のインスタンス(instance HasSchema Int
など)は一切定義されていない。どう作っていくかは、これからの課題だ。
正格フラグ、バンパターン、正格版関数・データ構造
Haskellスペースリーク Advent Calendar 2015 9日目
Haskellerとて、時には厳しくならなければいけないこともある―― @fumieval, 2015
Haskellは遅延評価を基本としているため、場合によっては未評価の式が積もり非効率な状況に陥ることがある。これを防ぐため、部分的に正格評価にするための仕組みが用意されている。もちろんこれらは闇雲に使えばよいというものではない。使うべきポイントを把握し、これらを見逃さないようにしよう。
この記事では、それらの機能の正しい使い方、間違った使い方を紹介していこう。
カウンター・カウンターズ・サンクス
条件を満たす要素の個数とそうでない要素をそれぞれカウントするプログラムについて考える。アキュムレータ(ループの中で積み上げていく変数)は正格にしないといけないらしいので、BangPatterns拡張を使ってみた。どんなパターンでも、感嘆符をつけることによってあらかじめ評価を強制できるすごい拡張機能だ。
{-# LANGUAGE BangPatterns #-} data Counter = Counter Int Int count :: (a -> Bool) -> [a] -> Counter -> Counter count p (x:xs) !(Counter m n) | p x = count p xs (Counter (m + 1) n) | otherwise = count p xs (Counter m (n + 1))
残念ながらこれは意味がないどころか、スペースリークを解決できていない。というのも、countに渡しているのはCounter
のコンストラクタ、つまり評価する必要のない値である一方、Counterの中身にはサンクが溜まってしまっているからだ。コンストラクタではなく、中身にバンパターンをつけよう。
{-# LANGUAGE BangPatterns #-} data Counter = Counter Int Int count :: (a -> Bool) -> [a] -> Counter -> Counter count p (x:xs) (Counter !m !n) | p x = count p xs (Counter (m + 1) n) | otherwise = count p xs (Counter m (n + 1))
データ型のフィールドのほうに感嘆符をつけると、そのフィールド自体が正格になる。こちらは拡張いらずで、関数定義にいちいちバンパターンをつけなくてもよいので便利だ。
data Counter = Counter !Int !Int count :: (a -> Bool) -> [a] -> Counter -> Counter count p (x:xs) (Counter m n) | p x = count p xs (Counter (m + 1) n) | otherwise = count p xs (Counter m (n + 1))
遅延評価を含めた意味論にこだらわないのなら、すべてのデータ型のフィールドを正格にするのが無難な選択だろう。
data Maybe' a = Nothing' | Just' !a instance Functor Maybe' a where fmap f (Just' a) = Just' (f a) fmap _ Nothing' = Nothing'
この正格版Maybeは、fmapしても中身にサンクが溜まらないのでいいことずくめに見えるが、fmapが厳密には則を満たさなくなってしまうという欠点がある。fmap id (Just undefined)
はJust undefined
と等しいが、fmap id (Just' undefined)
はundefined
になってしまう。
もっとも、日常ではここまで気にしなければいけない場面は少ないので、ほとんどの場合は気にせず感嘆符をつけて大丈夫だろう。GHC 8.0からは、全フィールドをデフォルトで正格にするStrictData
という拡張が入るため、こちらを使おう。
Strictに早合点
Lazyだとトラブルを起こしやすいということで、「正格版」の関数やデータ構造を提供している場合もある。Data.Map.Strict
はその一例だ。
import Data.Map.Strict data AppState = AppState { ... buzz :: Map (Int, Int) Int ... }
しかし、Strictがついているからといって油断してはいけない。このAppState
におけるbuzz
を繰り返し更新するとスペースリークが発生する。実はモジュール名のStrictが意味するのは、挿入時などに要素(Int)が評価されるというだけで、Map全体は正格にはなってくれないのだ。やはりフィールドは正格にする必要がある。
import Data.Map.Strict data AppState = AppState { ... buzz :: !(Map (Int, Int) Int) ... }
関数適用を伴って更新する値は正格にしておこう。これさえ守っていればまずスペースリークは発生しない。
動物、とくにヒトと性について
我々脊椎動物は有性生殖をする。したがって、繁殖に寄与する性質が必然的に残り、そうでないものは消えてゆく。オスは精子、メスは卵を作り出すという非対称性があり、体つきや行動もそれに合わせるように決まるのは自然だ。一夫多妻制の種においては、オスはメスを取り合うためにより強靭で攻撃的な性質が要求される。日本では法律上一夫一妻制を取るが、その影響は強く残っているだろう。実際、オスとして生を受けた私も、コミュニティの中でそれを体感している。
生まれつきの体質か、育った環境のせいかは知らないが、幼いころは体が弱かった(3才のころ、両足飛びができなかったそうだ)。そのため、幼いころの私の趣味は読書、ままごとやお絵かきなど、他の男性とは大きく異なるものだった。「男のくせに」と罵られ、暴力を振るわれたりすることもあった。残念ながらこれは戦略として正しくて、後述する私の性質を考えても、私をこの時点で消しておくことは充分に合理的だ。周りに助けを求めることもあったが実際に改善された例は少ない。なぜなら私を攻撃するのは理に適っているからだ。一方で、女性からは好意を持たれることが多かった。乱暴で幼い男たちに比べ私はいくらか紳士的かつ女性的で、友達としてよい役割を果たせたのだと思う。破られたり壊されたりした私の持ち物を直そうとする彼女たちの優しさにはひたすら感心した。私は、純粋に害を及ぼすオスが嫌いで、利益をもたらすメスが好きだった。性をしっかり認識することは身を守るために必要だった。
第二次性徴を迎えるとやや事情は変わってくる。男性、女性はお互いを恋愛対象(繁殖対象という品のない言いかえもできる)として見るようになる。女性たちとは比較的友好的な関係を持っていたが、「モテない」「モテる」、つまり異性として魅力を感じるかどうかという評価軸ができたことも分かった。禁欲的というかそもそも欲がない私にはあまり関わりのないことだった。一方で、論理的な思考力と自信は強くなっていったため、教員と衝突することもあった。
高専(高等専門学校)に入学してからは性の意識はいくぶん変わっていった。高専は女性の人数が男性の1/5程度となぜか比較的少ないため、一夫多妻的な戦略に起因するような争いは少ないように見えた。別に男女間の仲が悪いわけではないが、同性同士で固まる傾向が強いと感じた。私が高専で経験した揉め事のほとんどは体制に起因するもので、クラスにおける学生同士の衝突はほとんど認識しなかった。おおよそ平和だったように思う。
就職してからは私的に女性と接することはほとんどなくなった。短時間だけ出勤する事務の方を除けば全員が男性で、たまに開催される勉強会でも男性の比率は高い(98%くらいだろうか)。これはプログラミングにおけるコミュニティは男性の比率が大きく、理由はわからないが私が主に使っているHaskellという言語の場合では特に高いためだ。プログラミングに要求される能力とオスメスの機能に関係があるようには見えないので、とても奇妙な話だ。個人的に女性が少ないことにそこまで不満を抱いてはいないが、男女が生まれる割合により近いほうが、我々にとって望ましいのではないかと思う。このような状況で、マイノリティの存在を非難したり、悪い方法で異常に取り沙汰するのはとても不道徳なことだ。
この話はおまけである。プログラミングは、主に人手を使わずに目的を達成するために行われている。より少ない人手(人数と記述量)で、より効率的に目的を達成するのがプログラミングの目標だ。また、プログラミングの生産性はコミュニケーションのコストのため人数に比例しないことが知られている。だとすれば繁殖は必須ではなく、むしろコストの増大要因となってしまう。もし繁殖が必要ならば自然とバランスがとれるはずだが、プログラミングの目的は繁殖とはむしろ逆なため、何らかの要因でバランスが崩れてもさほど非合理的ではないと考えている。
閑話休題、私は快楽主義者である。快楽を生み出す者を助け、邪魔する者には戦う姿勢を見せたい。より多くの快楽を、より効率的に生み出すのが私の究極的な目的である。
性愛の話はいつも人々の話の種だが、私は興味がない。社会通念にとらわれず、私の好きな方法を使ってひたすら楽しく生きていきたい。ただ、恋愛が発生しやすい環境にいないだけで、恋愛のようなものがあっても悪くはないのかもしれない。
就職しました
本日、Tsuru Capitalのポジションを得ました。
Tsuru Capitalはデリバティブの取引を行っている企業で、自動株取引の会社ではありません。取引に関わっている10人のメンバーのうち、創始者であるSimonを除く全員がHaskellerで、取引状況の分析や一部の取引の自動化など、あらゆるところにHaskellを使っているのが大きな特徴です。日本では数少ない、Haskellをメインに使っている企業の一つでもあります。
東京、シンガポール、バンクーバーにオフィスがあり、東京には私を含む5人の開発者と事務担当、Simonと愛犬テトがいます。
オフィスはオランダヒルズ森タワーRoPにあります。設備が非常に充実しており、東京に引っ越すまではオフィスに週数度の頻度で泊まっていました。風呂上がりにジンジャエールをラッパ飲みしながらサーバールームの熱風で体を乾かすと、すごく気持ちが良いです。 職場の雰囲気は非常にカジュアルで、テトと遊んだり時々昼寝したりすることもあります。エスプレッソと炭酸水が好きなだけ飲めるのもよいところです。
仕事
「Tsuru Capitalのインターンに応募してみよう」と思い応募した二日後に面接を行い、次の週から3カ月間インターンとして働くことになりました。面接ではマルチメディア関連のスキルについて話していたこともあり、最初のほうは主に取引状況を見るためのGUIの開発をしていましたが、最近は実際のトレードに関わるものも含めいろいろな部分に手を加えています。タスクは多種多様で、プログラムを書くのが楽しいです。私は抽象的なコードを扱うのが得意なので、そこをさらに活かせるよう頑張っています。
ソースコードについて
Tsuru Capitalの社内のコードはかなり高品質だ思います。lensやlinearなどの比較的先進的なライブラリも活用しています。尖ったリファクタリングにも寛容で、いつでも新鮮さを保っています。取引をする上ではパフォーマンスが損益に直接関わってくるため、チューニングは丹念になされていますが、もちろんHaskellerらしく抽象化は怠っていません。
とはいえ、今までストリームの処理にはレトロ感あふれるiterateeと社内ライブラリを組み合わせて使っており、お世辞にも保守しやすいとは言えないものでした。そこで、私は新たなライブラリを開発し、古いコードを徐々に置き換えています。一筋縄ではいきませんでしたが、コード量が削減されただけでなく、パフォーマンスの向上も見られました。具体的な話は来月の関数型ストリーム処理勉強会のために残しておきましょう。
私はHaskellが好きです。一番好きなプログラミング言語であるHaskellで仕事ができるのは非常に嬉しいことでもあります。開発を通じて得たノウハウをただ仕事に生かすだけではなく、これからも記事やオープンソースソフトウェアとして世界に発信していきます。
カリー化
鍋にオリーブオイルを入れる。
にんにくを細切りにし、入れる。しょうがを少しすりおろす。いつもの流れである。
玉ねぎの半分をみじん切りにし、鍋に入れ、しばらく炒める。
キャベツ、にんじん、ヒラタケ、残りの玉ねぎ、じゃがいも(皮ごと)を大き目に切り、蓋をしつつ少し間隔を置いて順に入れる。
しばらくしたあと、鶏肉を入れる。少量のクレイジーソルトとバターも入れた。
水は少しだけ加え、他は素材の水分に頼る。Vita Craftの性能に期待を寄せる。
市販のカレールウをある程度分割し、まぶすように入れる。6分程度待ち、途中で中身をひっくり返す。
火を止め、しばらく余熱を加えて完成だ。炊き立てのご飯と一緒に皿に盛り、チーズをトッピングして出来上がり。
作る前から分かっていたことだが、汁は少ない。日本で一般的なカレーとは異なる。
しかし、うまい。調理時間は短いが、肉や野菜にしっかりと火が通っており、それぞれのうまみが伝わってくる。芯ごと煮込んだ(蒸した?)キャベツは柔らかく、甘い。
男爵薯の舌触りもよい。外側についた濃い目の汁とのバランスは絶妙だ。
今夜はカレーのようなものを作った。料理は勢いでなんとかなってしまうものだ。