ここは俺の備忘録だ

少なくとも日本語での言及が少ない話をするつもりです

Created docker images to build a static binary for Haskell/GHC8

そろそろ現代人にならないとまずいのでDockerを覚えることに。 タスクとしてこの前話題になった「Haskell/GHCにおける正しいstatic binaryのビルド方法」を実践するイメージを作りました。 個人的な用事でGHC8のllvmバックエンド版も用意しています。

https://hub.docker.com/r/nnwww/haskell-stack-link/

Alpine-linuxのパッケージマネージャにおいてGHCはほとんど特定ユーザの好意によってメンテナンスされている雰囲気です。(GHC 8.0.2だけがある状態) 従ってもう少しOSのサイズを増やしてでもStackの推論が聞く環境に移すべきかもしれません。

GHC 8.0.2を使っている方はCIサーバなどにどうぞ。

(というか今の今までまともに静的バイナリをビルドする簡素な手段が広まってなかった事に驚きである…)

(注: GHC8が用いるライブラリにはlibgmp(LGPL)が含まれるため、静的バイナリを配布する際にはご注意下さい)

Nix上のhaskellをビルドする際、zlibが見つからずリンクエラーになる問題

経緯

これまでmacOSでhomebrewを使用していたが、ライブラリにおける環境変数の衝突にはいい加減飽き飽きしていた。

しかしサーバでも無いのにDockerというのは大げさだろう。パッケージマネージャレベルで複数バージョンを上手く切り替えてくれれば良いのだ。

そのためmacOS上でNixを使ってみようと思い立った。

問題

題名のまま。

ワークアラウンド

これが実はcabalとnixの(そしてどちらかと言えばcabal側の)問題であるのが重要なポイントだ。 nix開発者曰く、cabal側での内部の取り扱い方が特殊であるらしく、includeファイルとlibファイルを個別に認識させてやらないといけないらしい。 従って以下のissue commentの方法で解決できる。

Stack + nix error · Issue #2130 · commercialhaskell/stack · GitHub

しかし、nix拡張を使用するとnixを使っていない人向けに公開するのが面倒になる。 従ってdefault.nix(shell.nix)に記述しよう。GHC 8.0.2を使う stack --nix プロジェクトにて、zlibを用意する最小設定が以下になる。

{nixpkgs ? import <nixpkgs> { }, ghc ? nixpkgs.ghc}:

with nixpkgs;

haskell.lib.buildStackProject {
  name = "somesome";
  buildInputs = [ zlib.dev zlib.out pkgconfig ];
  ghc = haskell.compiler.ghc802;
 }

nixの所感

使って一週間だが正直分かりづらく、現状他人にはお勧めできない。

まず公式ドキュメントが中途半端というか、丁度欲しい所が載ってないことがちらほらあり、結構苦戦する。 例えば、まずnix本体のドキュメントでは nix-env -i でパッケージをインストールすると教えられる。 しかしnixを用いたstackの開発ではsystem-ghcが強制されるため、 開発だと nix-env -f "<nixpkgs>" -qaP -A を用いて haskell.compiler 下のコンパイラバリアントを選択しなければならない。 そういった記述はnixpkgs側、つまりリポジトリのマニュアルの言語毎の仕様を頭に入れる必要が生じる。

https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure

というか各種attribute setの名前空間は一体どうなっているんだろう?少なくとも早見表、できればnix自体に一覧表示や検索機能が欲しい所だ。

またdefault.nixを書く際、環境を導出する関数が言語毎に異なる。上述のように、haskellを使ったプロジェクトでstackを用いるなら buildStackProject を用いる。 そのため一括のリファレンスが欲しくなるが、これが現状何処にあるかわからないという問題もある。 (先程のnixpkgs manualにユースケース毎の代表的な方法は記述されているため、一応使えはするが…)

以上、始めようとした瞬間にマニュアルを隅々まで眺める作業が必要になるため、それなりにしんどい。 一方で、プロジェクトの名前空間では言語関係なく再現可能な環境を作成でき、 マシン全体の名前空間では雑な運用をしても不整合に対してロールバックできるのは魅力的だ。 そのため知見が溜まったら「ここを見ろ」集を作りたいのだが…果たして適応できるだろうか?

突っ込める所があれば色々教えていただけると非常に助かります。

runSTにおける関数合成の失敗—Higher Rank Typesにおける型推論の落とし穴

余談

2つの事柄を題に要約する言い回しとして「あるいは」以外を見つけないとブログの題があるいはだらけになってしまう…

本題

より高ランクな型を低いランクの型パラメータで表すことはできない。 従って通常のランク1であるような関数somethingについて、something . runSTのような関数合成は上記ツイートのようなエラーを起こす。 しかしそのままだとrunST $ doが使えず非常に不便であるため、GHC7から$について推論する特殊ルールが追加されている。

これもまたHaskellに数多ある知っていれば当然であるが、知らねば型エラーから類推するのは難しい一例と言える。 まぁともあれ、ランク2の型はSTのスコープ制限は勿論のこと、 AllowAmbiguousTypes拡張と組み合わせてfromIntegralした数値を使い回すのに非常に便利だったりする(申し訳程度の擁護)。

関数適用演算子$は空気の如く重要であるため、色々特別扱いを受けている。 例えばLevity Polymorphismが知られている。 Levity Polymorphismはhaskellの意味論でいうボトムに持ちあげられている型(Lifted)と持ちあげられていない型(UnLifted)の間で多相性を行う実装だ。 意味論についてはここで概観が読める。

Haskell/Denotational semantics - Wikibooks, open books for an open world 詳細と論文へのリンクはこちらを参照されたし。 stackoverflow.com

Haskell/GHCスクリプティング時の実行ファイルパス取得、あるいはGHCが公開するC言語APIは何処にあるか?

問題

runhaskell/runghcを用いることにより、Haskell/GHCではshebangを書き込んだスクリプティングが可能である。 特にStackの登場以降、依存性の解決も容易となり非常に気軽に扱えるようになった。

github.com

個人的にシェルスクリプトは避けていきたいため、これは渡りに船だ。しかし基準となる実行ファイルのパスが取得できないという問題があった。 System.EnvironmentのgetExecutablePath関数だとビルドツールの作業ディレクトリを見に行ってしまうのである。

解決策

FindBinパッケージのgetProgPath関数はスクリプティング時に実行ファイルのパスを取得し、コンパイルするプログラムであれば実行するバイナリのパスを取得する。一見その古さに驚くがlts-8.21(GHC 8.0.2)にて動作を確認した。

FindBin: Locate directory of original program

この実装が以下となる。

github.com

さて、ここでヘッダファイルの読み込み無しにforeign import ccallされているgetProgArgv関数らは一体何者だろうか? (なおgetProgArgvはSystem.Environmentでも使われている)

これらの正体はGHCghc/includes/RtsAPI.hにて公開している関数である。詳細は以下が詳しい。 この疑問はHaskell-jpのSlackにて@hiratara氏に答えをいただいた。

Commentary/SourceTree/Includes – GHC

実際のコードはgithubの方が見やすい。

github.com

そろそろ象牙の塔の外側が見えてきたようだ。

Haskell/GHCでプロジェクト上のデータファイルのパスを取得する

近況

GHCでWord2Vec(fasttextベース)を実装しており、詰まりと知見獲得を繰り返している。 新機能に関する言及は多いのに実際の開発知見となるとヒット率が目減りしてしまうのは苦しい所であるが、 現代ではStack OverflowやHaskell-jpという素晴らしい集まりがあり、ハードルも随分下がったと言えるだろう。 この詰まりはそのままネット上の情報の薄さでもあるため、これをブログに残していくことでコミュニティへの還元とする。

仮定

(間接的にでも)Cabalの使用を仮定する。現代的な環境であるStackを用いている人が殆どであるから問題無いだろう。

解決策

cabalのdata-filesフィールドを用いる。詳細は以下。

Cabal User Guide: Developing Cabal packages

data-filesフィールドにパスを記述するとPaths_<パッケージ名>モジュールにてgetDataFileName関数が使えるようになる。hpackでも同じフィールドが利用可能。 stackから入ったユーザ(私)はcabalで一回は躓くのでマニュアルはしっかり読んでおきたい。

Rustのreference/dereferenceまとめ

この記事は TUT Advent Calendar 2016 - Adventar 5日目の記事です.

3日目の記事: 5mmくらいわかる競馬講座~国庫から出金~(CC他 - 自分用

(@naruhodo2015さんへ: Rustは関数型プログラミング言語では無いですよ ><)

最近,友人et al.と適当な映像を垂れ流しながらもくもくと作業する会が不定期で開かれる様になりました.そこでRustを書いていた友人曰く「Rustのreference/dereferenceの対応とその演算子がわかりにくい」との事で,まとめてAdCに貼ることに.

f:id:Nnwww:20161205002404j:plain

(mut &になってるのは徹夜で脳死していた影響です (´・_・`))

一番わかりにくいのはLHSとRHSに現れる&の役割が異なる事です.LHSで登場する「パターン」では参照の付加をrefパターンで行えるようになっており, let ref x = ...等とすると右辺の評価結果に参照を付加した物をxとして束縛します.一方でdereferenceを行うのが&xパターンで,これはref xと比べれば自然な使われ方でしょう. ある値xの参照が入ってくるので,&xに分解されると考える訳です.

RHSで登場するのは単項演算子である事に注意します.するとこれがC/C++に由来するreference/dereferenceの記法である事が分かると思います.

構造体に結び付くメソッド構文の場合,参照を打ち消すようにdereferenceが行われます*1.実用では参照が付加される関数がどれなのか(iterなど),ライフタイムがある場合はどの引数に基づくのか*2,use after freeが起きる場合スコープをどう限定するかなどなどを気にしつつreference/dereferenceの対応を取っていく必要があります.コンパイラと協力しながらdata race安全なコードを詰めていくプログラミングがRustの醍醐味でありつらみでもあるわけですね.

filterのように,イテレータには二重に参照を付加して述語へ渡してくるものが幾つかありますが,この理由は分かっていません.詳しい型がいらっしゃったら是非コメント宜しくお願いします.

それでは,皆さん良いRustプログラミングを!

6日目は @jp3cyc さんの Ogaki Mini Maker Fairに行ってきた | jp3cyc's blog です.

*1:`Deref` による型強制

*2:HashMap等,使い回しが起きる所で重要になります.e.g. コンテナの値を取って→入れて→エラー (lifetimeとかの話) - Qiita

Rustでmpsc::Receiversを1スレッドかつ並行に待つ方法

イベントハンドリング等である程度込み入ってくると単一の膨れ上がったenumを分割し,それぞれのモジュールに分けたりする.すると複数のenumが一堂に会する場が何処かに発生する筈だ.

ただ.std::sync::mpsc::Receiverのrecvやiterブロッキングであるため,enum毎に用意した複数のReceiversで逐一止まってしまうのは効率が悪い.

ではReceivers毎にスレッドを立てれば良いかというと,これも所有権が持っていかれる等面倒が多いし,そもそも1つのReceiversを待つ為だけにそれぞれカーネルスレッドを立てるのは無駄な気がする.

というわけで以下は個人的に分散処理のOSSを眺めていた際提案されていたそういうモチベの解決策,のアルゴリズムの再現である.

これはこのままでは動かない.流れと実装しなければならないものの備忘録という事にしてほしい.

#[derive(Debug, Clone)]
enum EvCat {
    Net,
    UI,
}

#[derive(Debug)]
enum NetEv {
    Success,
    Failure,
}

#[derive(Debug)]
enum UiEv {
    ScreenTransition,
    Terminate,
}

struct EvSender<Cat, Subset> {
    ev_tx: mpsc::Sender<Subset>,
    cat: Cat,
    cat_tx: mpsc::Sender<Cat>,
}

fn main() {

    let (cat_tx, cat_rx) = std::sync::mpsc::channel();
    let (net_ev_tx, net_ev_rx) = std::sync::mpsc::channel();
    let (ui_ev_tx, ui_ev_rx) = std::sync::mpsc::channel();

    let ui_ev_sender = EvSender::<EvCat, UiEv>::new(ui_ev_tx, EvCat::UI, cat_tx.clone());

    let nw_ev_sender = EvSender::<EvCat, NetEv>::new(net_ev_tx, EvCat::Net, cat_tx);

    let joinh = thread::spawn(move || {
        for it in cat_rx.iter() {
            match it {
                EvCat::Net => {
                    if let Ok(net_ev) = net_ev_rx.try_recv() {
                        match net_ev {
                            NetEv::Success    => { /* Do Something */ },
                            NetEv::Failure => { /* Do Something */ },
                        }
                    }
                },
                EvCat::UI => {
                    if let Ok(ui_ev) = ui_ev_rx.try_recv() {
                        match ui_ev {
                            UiEv::Terminate       => break,
                            UiEv::ScreenTransition => { /* Do Something */ },
                        }
                    }
                }
            }
        }
    });

    nw_ev_sender.send(NetEv::Connected).unwrap();
    ui_ev_sender.send(UiEv::CreateDirectory).unwrap();
    ui_ev_sender.send(UiEv::Terminate).unwrap();
}

Receiverの try_recv 関数だけは唯一ノンブロッキングメソッドであり, 値が届いて無ければResult::Errを返してくれる.cat_rxのiterブロッキングは仕方ないのでスレッドで打ち消す.

EvSenderの実装だが,これはnew関数の値を保持するだけの構造体である. 使われているのは最後のsendの部分で(このページでは実装されていないので想像して欲しい),先のEvSenderに対して以下の様なimplになる.

fn send(&self, event: Subset) -> Result<(), /*error types*/> {
    if let Err(err) = self.ev_tx.send(event) {
        /*error handling*/
    }
    if let Err(err) = self.cat_tx.send(self.cat.clone()) {
        /*error handling*/
    }
    Ok(())
}

ev_txへ分割されたenumのサブセットをsendして様子を見た後,続けてcatを流す. すると理想的にはmatchする値が流れ着くのに続いてforのcat_rx.iter()が作動し,enumをハンドリングするという流れになる.

この方法ならばenumのサブセットを各モジュールに分散させてそれぞれ使っている場合でも, 全体の集合を定義しておく事で,まとめてハンドリングできるようになる. 今回の題材はenumによるイベントのハンドリングであったが,それに限らず複数のReceiversの集約として有用なモデルだと言えるだろう. これまで代数的データ型を使う機会は頻繁に有ったが,所有権を意識させられるとまた新しい用途や工夫が現れ中々面白いものである.