Juliaの構造体とジェネリクスについていろいろ勘違いしていた話

Juliaについて

最近、数値計算用のプログラミング言語、Juliaにはまっています。
Juliaは、動的型付けでありながら、ユーザ(プログラマ側)が型を明示したり、関数単位のJIT(Just-In-Time)コンパイルに伴う最適化によって高速に実行可能であるため、注目されています。

Juliaにおいても、多言語のジェネリクスのように静的型付け言語にあるようなサブタイプ、スーパータイプを指定することによる型の制約を設けることが可能です。 (2018年5月29日時点、Juliaのバージョンは0.6) 今回はJuliaの構造体とジェネリクス(?)に少々はまったので記しています。

コンストラクタにおける勘違い

まず、以下のようなコードを考えます。
ここで、TはAbstractFloatのサブタイプであることを意味しており、 Hoge{T}(_a::T) = new(_a) はコンストラクタの定義を表しています。 また、AbstractFloatは64bit浮動小数型であるFloat64、32bit浮動小数型であるFloat32のスーパータイプとなっています。

struct Hoge{T<: AbstractFloat}
      a::T
      Hoge(_a::T) = new(_a)
end

このコードを実行すると、型パラメータと制約を明記するよう、警告されます。 Juliaでは型パラメータTをそのまま利用できるわけではないようです。これは 構造体の定義外にもコンストラクタを定義できることから、構文を統一されるためのもの だと推測しています。
(Juliaのドキュメントでは、コンストラクタの定義位置が構造体定義の内外に応じて、Innner、Outerと区別されている)

WARNING: deprecated syntax "inner constructor Hoge(...) around C:\ProjectYukito\julia\famiconlike_sound_spectrum.jl:21".
Use "Hoge{T}(...) where T" instead.

そこで、次のように変更します。これにより、警告が無くなりました。

struct Hoge{T<:AbstractFloat}
    a::T
    Hoge{U}(_a::U) where U<:AbstractFloat = new(_a)
end

ジェネリクスにおける勘違い

次に、Hoge型の変数を宣言することを考えます。

hoge = Hoge(0.5)

一見問題ないように見えますが、 このコードは、以下のエラーのため実行できません。

ERROR: LoadError: MethodError: Cannot `convert` an object of type Float64 to an object of type Hoge
This may have arisen from a call to the constructor Hoge(...),
since type constructors fall back to convert methods.

=コンストラクタがconvert関数として扱われるが、convert関数がFloat64→Hogeの変換に対応していない(?)

コンストラクタが引数の型に対応していない場合、convert関数でエラーを出力することは型変換に関するJuliaのドキュメントにも記されています。

Conversion and Promotion · The Julia Language

問題は、 Float64に対してHogeのコンストラクタが対応していない点 です。
Float64はAbstractFloatのサブタイプであるはずなので、エラーが出力されることが解せませんでした。そこで、Float64であることを明示してみます。

hoge = Hoge{Float64}(0.5)

これにより、初めて正常に実行されました。 ちなみに、Float64の代わりにAbstractFloatのサブタイプであるFloat32を指定すると、同様に cannnot convert ... のエラーが出力されます。一方で、AbstractFloatのサブタイプでない型(UInt32など)を指定すると、型制約に伴うエラーが出力されます。

これらのことから、 Juliaでは、引数の型に応じて推論の結果型パラメータの値が定まるのではなく、型を明示する必要がある という結論に至っています。

まとめ

普段Rustばかり書いているせいか、Juliaのジェネリクスについて混乱してしまいました。

  • 内部コンストラクタ(Innner-Constructor)において、構造体の定義に用いる型パラメータは直接適用されない
  • 変数の宣言時、型パラメータは明示する必要がある(引数の型推論から確定するわけではない?)

WindowsでSource Code Pro をビルドしようと思うも一筋縄ではいかなかった話

最近、エディタをSpacemacsに乗り換えました。 Spacemacsのデフォルト設定では、フォントがSource Code Proなので、Source Code Proが入っていない場合は起動後にWarningが出ます。
おそらく代替フォントで描画されているかと思いますが、特に日本語の描画が大変厳しいようで、お世辞にも綺麗とは言えない状況でした。

そこで、Source Code Proを導入することとしました。

Source Code Pro(GitHub)にもあるように、 リリースページからダウンロードすればよい のですが、 github.com 見事にそのリンクの情報を見逃した私は、ソースコードからビルドすることにしました。

基本的にはREADMEにしたがって行えばよいのですが、私の環境(Windows10)ではbuild.cmdの実行時にエラーが発生しました。

syntax error at "}" missing ";" 

当該リポジトリのIssueにも同様の事例が報告されていませんでしたので、困惑していたのですが、別リポジトリのIssueを参考に解決することができました。 つまり、familyTables.feaの2行目のセミコロンを削除する ことで対処できました。

github.com

いやわざわざビルドしなくていいのに、と言われればそれまでなのですが。。。

Rustで数値シミュレーションをする際に使用しているライブラリ(クレート)の紹介

この記事はTUT Advent Calendar 2017の18日目の記事です。

概要

普段私はRustを使ってシミュレータを作成しているのですが、そこでお世話になっているライブラリ(クレート)を簡単に紹介します。
今回紹介するライブラリ(用途)は以下の2つです。

  • rayon (データ並列化)
  • statrs (様々な確率分布に従う乱数生成)

rayon

すでにいくつかの記事で紹介されていますが、rayonによって主に以下の処理を簡潔に行うことができます。両者とも、Work stealingと呼ばれる方法で実現されているそうです。

  • スレッドによる並列処理
  • 再帰関数の分割処理

スレッドによる並列処理

rayonでは、Parallel Iteratorの導入によって通常のイテレータに対する関数(mapなど)の処理を並列化することができます。D言語でいうstd.parallelismに似ているかもしれません。 ただし、Rustのシステムによって並列時の安全性は保証されています。つまり、標準ライブラリのスレッド同様、Sendトレイト、Syncトレイトの制約によってコンパイル時に検査が行われます。

iter関数やinto_iter関数が実装されている型は基本的にParallel Iteratorに変換することができ、以下のような対応関係を持ちます。

コード例は以下のとおりです。

extern crate rayon;
use rayon::prelude::*;

fn main() {
    (0..100)
    .into_par_iter()
    .map(|i| i * 2)
    .for_each(|x| println!("{}",x));
// 200未満の偶数が出力される
}

大変便利ですが、Parallel Iteratorに対してcollect::<Vec<_>>()のようなことはできず、 あらかじめ格納先のベクタをミュータブルとして宣言したのち、collect_into関数を用いて格納する必要があります。

コード例は以下のとおりです。

extern crate rayon;
use rayon::prelude::*;

fn main() {
    let mut even_vec = vec![0;100];
    (0..100)
    .into_par_iter()
    .map(|i| i * 2)
    .collect_into(&mut even_vec);
// 200未満の偶数がeven_vecに格納される
}

並列処理時のスレッド数は自動的に決定されますが、スレッド数を能動的に設定したい場合は、num_threads関数によって設定可能です。
また、実行時にスレッド数を設定したい場合は、環境変数RAYON_NUM_THREADSの値を設定してから実行することで対応できます。

再帰関数の分割処理

join関数によって、再帰関数を効率的に並列処理します。
rayonのGithubリポジトリの例が簡潔であるため、引用します。 コード例は、並列化したクイックソートのようです。

fn quick_sort<T:PartialOrd+Send>(v: &mut [T]) {
    if v.len() <= 1 {
        return;
    }

    let mid = partition(v);
    let (lo, hi) = v.split_at_mut(mid);
    rayon::join(|| quick_sort(lo), || quick_sort(hi));
}

リポジトリのREADMEによれば、

Note though that calling join is very different from just spawning two threads in terms of performance. 
This is because join does not guarantee that the two closures will run in parallel. 
If all of your CPUs are already busy with other work, Rayon will instead opt to run them sequentially. 
The call to join is designed to have very low overhead in that case, so that you can safely call it even with very small workloads (as in the example above).

とのことですので、join関数の引数のクロージャを、可能なら並列処理し、CPUの負荷が高い場合は逐次的に処理するよう、うまくスケジューリングされる(?)ようです。

statrs

statrsはRustにおける統計的な数値計算のためのクレートで、特に私はdistributionモジュールを頻繁に利用しています。distributionモジュールを利用することで、各種確率分布に従う乱数を容易に生成することができます。
ただし、rust statrsで検索すると検索エンジンが変に気を利かせて別の検索ワードを推奨してくるので注意です。

離散型分布、連続型分布問わず様々な分布に対応しており、以下の分布ごとに構造体が定義されています(非常に多い)。また、分布の期待値、分散を返す関数が各分布に実装されており、離散型確率分布に対しては確率質量関数の値、連続型確率分布に対しては確率密度関数の値を返す関数が実装されています。

さらに、sample関数(rand::Rngトレイトを実装する乱数生成器を引数)の実装によって、各分布に従う乱数を生成可能です。 rand::Rngトレイトを実装している乱数生成器であればよいため、StdRngやThreadRng、XorShiftなど様々なものに対応しています。

  • 離散型確率分布
    • ベルヌーイ分布
    • 二項分布
    • カテゴリカル分布(Categorical Distribution)
    • 離散一様分布
    • 幾何分布
    • 超幾何分布
    • 多項分布
    • ポワソン分布
  • 連続型確率分布
    • ベータ分布
    • コーシー分布
    • カイ分布
    • カイ2乗分布
    • ディリクレ分布
    • アーラン分布
    • 指数分布
    • F(Fisher-Snedecor)分布
    • ガンマ分布
    • 逆ガンマ分布
    • 対数正規分布
    • 正規分布
    • パレート分布
    • t分布
    • 三角分布
    • 連続一様分布
    • ワイブル分布

本記事ではdistributionモジュールにのみ着目しましたが、他のモジュールにてベータ関数やエラー関数などの複雑な関数の値を計算する関数も用意されているようです。

まとめ

rayonやstatrsは、数値シミュレーションをするうえで、大変有用です。並列処理により計算時間を削減することができ、randクレートでは対応していない様々な確率分布に従う乱数生成を行うことで様々なシミュレーションが可能です。
また、Rustはコンパイラが大変優秀なので、コンパイル時の検査のおかげでシミュレーション実行時のエラーがなく、重宝しています。その一方で、コンパイルを通すのは大変ですが。

私の身の回りでもRustが流行ればいいな、と思うこの頃です。

apt-get dist-upgradeで無線LANに接続できなくなった

概要

Raspbian jessie on Raspberry Pi (1st)の環境で、apt-get dist-upgradeを実行したら無線LANに接続できなくなったので、その解決(?)までの過程を記事に残します。

問題

dist-upgrade実行後に、

  • Raspberry Piが設定した固定IPではなくなる
  • そもそもwlan0にIPが割り振られなくなる

といった問題が発生しました。

解決まで

IPが降られなくなる

$ dmesg

でログを確認してみると、wpa_supplicantがCTRL-EVENT-DISCONNECTEDというエラーで異常終了している様子。

$ cat /etc/wpa_supplicant/wpa_supplicant.conf  

で設定を見てみると、先頭に下記の謎の設定が自動的に追加されていました。

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

この設定を削除して元のwpa_supplicant.confに戻すと、無線LANに接続できました。

固定IPでなくなる

dhcpcd.confを覗いてみると、設定が初期値に戻されていたので、以前と同様に設定を行うことで固定IPの設定も難なくできました。
dist-upgradeの途中で設定ファイル群を更新したことが原因のようです。

まとめ

dist-upgradeの途中で設定を更新するときは要注意。

TDD challenge #1 に参加してきました

概要

2017年8月18日に株式会社ミクシィ 渋谷本社にて開催されたTDD challengeに参加してきました。

このワークショップは、テスト駆動開発(TDD: Test Driven Development)の講義と、ペアプログラミングの形式をとった実習から構成されていて、講義と演習を交互に行うものでした。

当日の流れ

開始前

11:00より受付とのことだったので、あらかじめ場所を確認しておいて、定刻どおりに受付まで行いました。
JR渋谷駅のすぐ近くだったので、迷わずに済みました。

各種設定(演習用)

予め決められたペアで演習を行いました。パートナーの方と共通して使用できる言語がJavaしかなかったので、使用言語はJavaとしました。

全体としては、Javaを使用するペアが多数でしたが、他にもRubyPythonなどを使用するペアもあって、多種多様でした。
とはいえ、私たちのペアは両者ともにしばらくJavaを書いていなかったので、些か不安でした。

講義

なぜTDDが必要なのか、どのような状況でTDDがその威力を発揮するのか、TDDを始めるにあたって意識すべきことは何か、といった点に重点を置いた講義だったように感じました。

TDDでは、その名にあるとおり、

  1. テストを書く
  2. テストを失敗させる
  3. (テストを通すように)実装する

の順で開発を行います。リファクタリングは最後、つまりテストが通っている前提で行うべき、とのことでした。 特に、TDDにおいて意識すべきことやメリットが次節の演習で大きく効いていた印象を受けました。

演習

講義の合間合間に複数の課題が発表されて、要求された仕様を満たすようにペアでTDDによる実装を行います。

一見実装が楽に思える仕様でも、TDDを意識して、テストを先に完成させることに重点を置きました。 私たちのペアはJavaを使用したので、テストフレームワークとしてJUnitを採用しました。
両者ともにJUnitは未経験だったため、講師の方々に助けて頂きながら試行錯誤しました。

演習の前半ではつい実装の方に手を伸ばしてしまいそうになっていましたが、後半ではテストを書くことに多くの時間を割くようになっていました。
各課題ともに1~2時間程度(正確に測ったわけではないのでそれ以上の可能性もある)の時間が設けられていましたが、テストの作製に時間を要したこともあって、すべての課題を完成させることはできませんでした。

しかし、テストを作成するにあたって、テストがコード自体(クラスなど)の設計に直結しているように感じて、テストを完成させたあとの実装は想像以上に速く行うことができたように思いました。

その他

エンジニアの方々とともに昼食をいただきました。8月18日が「お米の日」(「米」を分解すると八 十八 になることから。米寿の由来と同様?)ということで、釜めしをいただきました。お茶漬け用の出汁もあり、二重の楽しみがあって大変おいしくいただきました。

食事中にはエンジニアの方と研究の話題や普段のコーディング、プログラミング言語全般など、様々なお話をさせていただいて、大変有意義な時間となりました。

まとめ

普段私はプロジェクト(もしくはオブジェクト指向としてのクラス)を設計するとき、必要なメソッド群を脳内で雑に実装を始める、といったことをしていたのですが、実際はそれはTDDに直結していて、そのまま置き換えられるように感じました。
TDDではテストがそのまま実装される関数群を網羅していて、かつテストケースとして期待する振る舞いを記述することから、むしろ仕様がテストに表れている印象を受けました。
期待される動作がテストに書かれているため、実装の際に混乱することを抑制することができ、実装をスムーズに行うことができたように思います。

言語によってテストフレームワークは異なりますが、幸い普段私が使用する言語は優秀なフレームワークが整っているようなので、今後TDDを活用していきたい、と強く感じたワークショップとなりました。

最後になりますが、ワークショップを通して丁寧に指導してくださった講師の皆様、本当にありがとうございました。 また、参加者の皆さん、お疲れ様でした。

Phoenix on Raspberry Pi の導入メモ

プログラミング言語ElixirのWebフレームワークである「Phoenix」をRaspberry Pi 1 に導入してみましたが、いたるところで詰まったのでメモしておきます。

# 目標 Raspberry Pi Model BにPhoenixを導入する。

今時何で初代を使っているのだろう。。

問題1

「Elixir Phoenix」などのワードで検索すると、多くの記事で以下のような導入方法が紹介されています。

qiita.com

この記事におけるPhoenixの導入方法を試みると、まずmix local.hexの実行の時点で失敗しました。エラー文の画像はありませんが、以下のような内容です。
Phoenixの導入のためにはhexは必須ではありませんが、mix archive ~の部分も同様のエラーで失敗します。

stackoverflow.com

エラー文から察するに、SSLが有効になっていないことが原因のようです。 opensslが入っていることを確認したうえでErlang VMの再導入を試みましたが、無駄のようです。

そこで、Erlang VMソースコードからビルドすることを試みます。Erlang VMをビルドしてSSLを有効にする方法は以下の記事を始めとして多くの記事で紹介されているように、kerlというバージョンマネージャを使用すれば容易に行うことができますが、コンパイルオプションが曲者でした。

qiita.com

紆余曲折あり、私の環境ではオプションを以下のようにすることでようやくSSLが有効になりました。

KERL_CONFIGURE_OPTIONS="--enable-dynamic-ssl-lib --with-ssl=/usr/bin/ --disable-hipe --enable-smp-support --enable-threads --enable-kernel-poll --disable-native-libs --without-javac"

このオプションでビルドした場合、初代Raspberry Piのスペックでは数時間から半日ほど時間を要しました。

問題2

Erlang VMソースコードからビルドし、Elixirの環境を導入します。 Elixirの環境は、

$ apt-get install elixir

で問題ありませんでした。
導入後、

$ mix phoenix.new phoenix_sample
$ cd phoenix_sample
$ mix phoenix.server

とすることで導入完了の予定でしたが、ここで別の問題が発生しました。mix phoenix.serverの実行中に行われるnpm installでエラーが発生し、失敗する、というものでした。

ここでnpmのバージョンを調べると、

$ npm --version

v0.14.21という、古すぎるバージョンであることが判明したため、以下の記事に則ってnpm(及びnode)のアップデートを行い、

qiita.com

再び

$ mix phoenix.server

とすることで解決できました。

導入成功

導入後、初回起動時はコンパイルが多く、ある程度時間を要しましたが、実際にブラウザからアクセスでき、同時にログ出力も確認されました。
ログの応答速度から察するに、初代Raspberry Piにとってはある程度の速度が出ている・・・のか?というところです。

f:id:je6bmq:20170131213245p:plain f:id:je6bmq:20170131213322p:plain

CTF for ビギナーズ Finalに参加しました(感想+Write up)

Outline

初CTF(at セキュリティ・キャンプ2016)以来、CTFに興味があったのでCTF for ビギナーズ FINALに参加してきました。

2016.seccon.jp

午前中から昼過ぎまでWeb、Forensics、Binaryについての講義が行われ、最後に参加者全員でCTFを行いました。 今回のCTFは初の400人規模のCTFとのことで、独特の雰囲気だったことを覚えています。。 このCTFは講義の復習+αのような内容で、難しくも楽しかったです。

私が解けた問題は以下の通りです。 f:id:je6bmq:20170130210532p:plain

Webの「もぐもぐ」問題、Forensicsの「あけてみよう」問題、Binaryの「HiddenFlag」問題は今思うとあと1歩で解くことができたような(気がする)問題だったので、悔しい限りです。
後でちゃんと解こうと思います。

以降、Write upです。

Write up

スクリーンショットの撮り忘れにより、(特にWebの)情報量が少ないことをお許しください。 また、フラグの形式はctf4b{文字列}です。

Web

Classical Injection

フォームに' OR 1=1を入力すると、フラグが表示されました。

(講義中で「もっとも単純なインジェクションの1つです」のような話があったような・・・?)

May the extensions be with you.

提示されたURLを開くと、「あなたは管理者(admin)ではない」(曖昧)といったメッセージが表示されており、問題から、ブラウザの拡張機能を使用すればよいのでは?と想像しました。

演習環境のブラウザ(Chrome)には、EditThisCookieという拡張機能があり、Cookieの値を参照、変更することができるため、使用してみると、admin(といった名前)のCookiefalseの値を持っていることが確認できました。

そこで、この値をtrueに変更し、リロードするとフラグが表示されました。

Forensics

みつけてみよう

与えられたpcapファイルをWiresharkで開いて、パケットの統計(Packet Hierarchy)を見ると、以下のようにHTTPでいくつかパケットが飛んでいることが分かりました。

f:id:je6bmq:20170130213953p:plain

これらのパケットを眺めていると、無数のctf4b{}の文字が。
そこで、Wiresharkのフィルタリング機能でframe matches "ctf4b"とすると、フラグが出てきました。
正規表現で検索するなど、より理論的な解法は多々あるかと思われる)

Binary

復習

2つのセクションのアセンブリの処理を追った結果得られる最終的なeaxの値を"_(アンダーバー)"で結合したものをフラグとする、といった問題でした。

1つ目のセクションではeaxの初期値を3として、8倍したあと、3を引き、さらに2を足す、といった処理をしていたので、最終的なeaxの値は23となったと思います。

2つ目のセクションでは、ループのインデックスが1から5まで増加する計5回のループ中に、毎回eaxに対してループインデックスが乗算されるような挙動に思えたため、5の階乗、つまり120が得られ、

ctf4b{23_120}

をsubmitした結果通ったと思います。
アセンブリのファイルが手元になく、曖昧さが大きいです)