Rust ≒ Haskell
Rustの気になるあれ
rustの宣伝文句で気になってた事が有ったのでggしてみた。
cross compile
goでは簡単に異なるOS用のバイナリーが作成出来た。ほなら、Rustは?
こういうのが出来るそうだ。リナで開発して、それをWindowsへ持って行くと言う一方通行で、オイラーは十分と思われる。だって、Windowsって何かと不便だから、その上での開発は御免被りたい。
single binary
bench
ベンチマークも気になる所。どうやるか調べてみた。
Rust で n が m で割り切れるか判定(ベンチマーク)
stable版じゃなくて、+nightly版を使えとな。.rustupの容量が倍増するぞ。でも、先端を楽しめる。
こちらは、+nightly版を使わず、独自のクレートを使ってる。グラフが出て来て通っぽい。
install rust on debian(64Bit
) 辛抱たまらず、64Bit機にも入れてしまった。下記はstable版のみを入れた状態。
sakae@pen:~$ du -sh .cargo/ 159M .cargo/ sakae@pen:~$ du -sh .rustup/ 990M .rustup/
そして、rust-analyzerも入れて、emacsから使ってみた。
(use-package lsp-ui :ensure t :custom (lsp-ui-doc-enable nil)) ;;(lsp-ui-doc-mode)
fnとかにカーソルが乗ると、バックグランドが緑色で、説明が出てくる。非常に鬱陶しいので、前回のinit.elを少々変更した。最初は lsp-ui-doc-mode を無効にしてる。
C-c h で、メソッドの定義が参照出来るのは嬉しい事だ。C-c C-d で、dbg! してくれる。これも便利だ。see also: Rust のデバッグチートシート
cargo-miri ?
https://github.com/rust-lang/miri
うーん、インタープリター? Bug発見器? 微妙な立ち位置だな(オイラーに取ってはね)
plotters
https://github.com/38/plotters
miriを検索してて、たまたま見つけた奴。juliaのあれとそっくりな、データ表示器。
元ネタはこちらに有ったもの。こういうのを人のふんどしで相撲を取るとか言うんですかね。 自分でうじうじと探すより、楽でよさそう。
Option and Result
ちまちまと教本を読んでいたらEnumの節に、有名なやつが載ってた。
enum Option<T> { Some(T), None, }
ある型のものが有る、もしくは何も無いってのの表現。Haskell語で言うMaybeだな。
enum Result<T, E> { Ok(T), Err(E), }
成功か失敗かの表現。これもHaskell語に有ったな。Eitherか。LeftとRightに引っかけてつかってたな。
もうRustってHaskellじゃん。Haskellで苦労させられたな。外界から入ってきた不浄なデータを内部に持ち込ませてくれないんで、慣れるまで随分と苦労した。そしてすっかり忘れてしまった。忘れていない便利なやつに、zip君が居たな。rust語にも居るのかな? (有ったよ!)
スライス
rustの教本をちまちまと見ていたんだ。そしたらスライスが使えるよ、だってさ。簡単な実験をしてみた。
fn main() { let mut v: Vec<&str> = "21031505,123,64,55".split(',').collect(); v[0] = &v[0][0..6]; println!("{:?}", v); }
そして、その結果。
sakae@pen:/tmp/hoge$ cargo r Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/hoge` ["210315", "123", "64", "55"]
csv-read
上でやったスライスを実データに適用しようとすると、それは出来ないとか言われる。実データってのは、ファイルから読み込んだデータの事だ。足元掬われた気分。やりたい事ははっきりしてるのに、それが出来ないHaskellみたいだ。
で、どなたかが書かれていたコードを頂いてきた。
use std::fs::File; use std::io::{BufRead, BufReader}; pub fn csv_read(filename: &str) -> (Vec<i32>, Vec<i32>, Vec<i32>) { let f = File::open(filename).unwrap(); let buf = BufReader::new(f); let mut x: Vec<i32> = Vec::new(); let mut y: Vec<i32> = Vec::new(); let mut z: Vec<i32> = Vec::new(); for line in buf.lines() { let l: &str = &line.unwrap(); let p: Vec<&str> = l.trim().split(",").collect(); x.push(p[0].parse().unwrap()); y.push(p[1].parse().unwrap()); z.push(p[2].parse().unwrap()); } (x, y, z) } fn main() { let x = csv_read("aa.csv"); // let (dt, hi, lo) = x; // println!("{:#?}", hi); println!("{:?}", x.0); }
csvファイルから一行づつ読み込み、カンマで分解。それを数値に変換して、Vecにまとめている。必要なのは、計測日時と最高血圧と最低血圧。それぞれをVecにしてる。
main側では、タプルを分解してから使うか(コメントした部分)、haskellのタプルの作法、fst,sndみたいに、x.0のようにして取り出せばよい。
debian:bld$ cargo r Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/bld` [21030921, 21031005, 21031021, 21031105, 21031121]
other way
読み込んだデータを数値に変換しないで、そのまま取り込む時は、下記のようにするそうな。
use std::fs::File; use std::io::{BufRead, BufReader}; fn main() -> std::io::Result<()> { let mut data: Vec<Vec<String>> = Vec::new(); for line in BufReader::new(File::open("aa.csv")?).lines() { data.push(line?.split(',').map(String::from).collect::<Vec<_>>()); } println!("{:?}", data); Ok(()) }
味噌は、Vec<Vec<String>>の部分。今まで訳も分からずVec<Vec<&str>>してた。この&strは、参照なのでブロックを抜けると参照とライフタイムの関係で消滅してしまう(line)。そこで、Stringを使って所有とするとな。
debian:qq$ cargo r Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/qq` [["21030921", "122", "63", "59"], ["21031005", "126", "67", "50"], ["21031021", "129", "63", "56"], ["21031105", "134", "71", "47"], ["21031121", "131", "72", "64"]]
見易いように、ちと変形してる。
let cadr = &data[1][0]; println!("{:?}", cadr);
ちと変形して、上のVecからcadr相当をやってみる。
"21031005"
このデータの一部を取り出そうとすると、
error[E0277]: the size for values of type `str` cannot be known at compilation time --> src/main.rs:10:5 | 10 | println!("{:?}", cadr[0..6]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time |
確かに、コンパイラー君の言う通りなんだけど、抜け道は無いものか?
fn type_of<T>(_: T) -> &'static str { std::any::type_name::<T>() }
こんな型を調べる関数を追加してから、スライスしたのを調べると、&str になってた。スライス無しだとStringって事だった。
let cadr = String::from(&data[1][0][0..6]); println!("{:?}", cadr);
これ、Rust by Example のConversionの部に載ってた秘伝の方法。
"210310"
fromに対しての逆演算、intoも有るのね。
use std::convert::From; #[derive(Debug)] struct Number { value: i32, } impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } fn main() { let int = 5; // Try removing the type declaration let num: Number = int.into(); println!("My number is {:?}", num); }
下記が実行結果
My number is Number { value: 5 }
zip
上でzipが出てきたので、使い方を調べてみた。
use std::collections::HashMap; fn main() { let keys: Vec<String> = vec![String::from("ruby"), String::from("rust")]; let values: Vec<usize> = vec![1995, 2010]; let map: HashMap<_, _> = keys.iter().zip(values.iter()).collect(); println!("map -> {:#?}", map); }
keyとvalueの配列からハッシュを作る例。keyとvalueをそれぞれ取り出してきてhashを構築している。
map -> { "rust": 2010, "ruby": 1995, }
データは、言語の公開年。rustはまだひよっこだな。
fn main() { let a1 = [1, 2, 3]; let a2 = [4, 5, 6]; let a3 = [7, 8, 9]; let out = a1 .iter() .zip(a2.iter()) .zip(a3.iter()) .map(|(x, y, z)| x + y + z) .collect::<Vec<i32>>(); println!("{:?}", out); }
こういう事は出来ないのか。Haskellとは違うんだな(当たり前だろ!)。
error[E0308]: mismatched types --> src/main.rs:10:15 | 10 | .map(|(x, y, z)| x + y + z) | ^^^^^^^^- | | | | | expected due to this | expected a tuple with 2 elements, found one with 3 elements | = note: expected tuple `((&{integer}, &{integer}), &{integer})` found tuple `(_, _, _)`
一応Haskellでも確認してみる。
debian:~$ ghci GHCi, version 8.4.4: http://www.haskell.org/ghc/ :? for help Prelude> zip [1,2,3] [4,5,6] [(1,4),(2,5),(3,6)] Prelude> zip [1,2,3] [4,5,6] [7,8,9] <interactive>:2:1: error: • Couldn't match expected type ‘[Integer] -> t’ with actual type ‘[(Integer, Integer)]’ • The function ‘zip’ is applied to three arguments, but its type ‘[Integer] -> [Integer] -> [(Integer, Integer)]’ has only two In the expression: zip [1, 2, 3] [4, 5, 6] [7, 8, 9] In an equation for ‘it’: it = zip [1, 2, 3] [4, 5, 6] [7, 8, 9] • Relevant bindings include it :: t (bound at <interactive>:2:1) Prelude> :t zip zip :: [a] -> [b] -> [(a, b)]
ああ、Rustの挙動と一緒。まあ、Rustの方はmapで引っかかってきたけど。
gosh> (use scheme.list) gosh> (zip '(1 2 3) '(4 5 6)) ((1 4) (2 5) (3 6)) gosh> (zip '(1 2 3) '(4 5 6) '(7 8 9)) ((1 4 7) (2 5 8) (3 6 9))
zipが複数の引数を取れるってのは、goshの麻薬的な機能なんだな。これが漏れ出していたんか。世の中、そんなに甘く無いって事だな。
Rustも早くtypeofとかを実装して下さい。まて、そんなのインタープリターが動いてこその便利物だろう。それまでは、 zipの心 でも見て桶。
fn zip<U>(self, other: U) -> Zip<Self, <U as IntoIterator>::IntoIter> where U: IntoIterator, impl<A, B> Iterator for Zip<A, B> where A: Iterator, B: Iterator,
zipのソース
web上でsrcを展開すると、細かい物が見えて来るんだけど、手元に有るrustのソース一式上では、それが何処に位置してる? 折角の機会なので、探ってみる。
debian:rust$ find . -name '*.rs' | xargs grep ZipImpl ./library/core/src/iter/adapters/zip.rs: ZipImpl::new(a, b) ./library/core/src/iter/adapters/zip.rs: ZipImpl::next(self) ./library/core/src/iter/adapters/zip.rs: ZipImpl::size_hint(self) ./library/core/src/iter/adapters/zip.rs: ZipImpl::nth(self, n) ./library/core/src/iter/adapters/zip.rs: // SAFETY: `ZipImpl::__iterator_get_unchecked` has same safety ./library/core/src/iter/adapters/zip.rs: unsafe { ZipImpl::get_unchecked(self, idx) } ./library/core/src/iter/adapters/zip.rs: ZipImpl::next_back(self) ./library/core/src/iter/adapters/zip.rs:trait ZipImpl<A, B> { ./library/core/src/iter/adapters/zip.rs:impl<A, B> ZipImpl<A, B> for Zip<A, B> ./library/core/src/iter/adapters/zip.rs:impl<A, B> ZipImpl<A, B> for Zip<A, B>
随分とWebに掲載されてる物と違うな。Web上のものはすっきりしてる。見るならWebでってのが、正しいrust道と思われる。
それでもソースをぶらぶらは楽しいぞ。例えば、core/src/iter/traits/collect.rs
/// use std::iter::FromIterator; /// /// let five_fives = std::iter::repeat(5).take(5); /// /// let v = Vec::from_iter(five_fives); /// /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
頭についてるコメント記号 ///
は、cargo doc が使うドキュメント用って意味だ。
ここで一つ文句が有る。5,5って同じ数字を重ねてるけど、どっちがどっちに影響してるか、不明確(修行中の、オイラーの感想です)。ここは、repeat(5).take(4)とかにしてくれないかなあ(気配りが足りないぞ)。
イテレータ
みんな大好き。forの追放だ。これで関数言語ぽくなるぞ。 一連の要素をイテレータで処理する
これらをまとめて下さった方がおられる。
こういう風に説明されると、益々Haskellを思い出すな。
let vec = [1,2,3]; let iter = vec.iter(); let mapped = iter.map(|i| i*2) .inspect(|i| println!("i = {}", i)) .map(|i| i*0) .collect::<Vec<i32>>(); println!("{:?}", mapped);
mapの中でprintln!は使えないとか。で、その代わりにinspectを使えとな。
こんな記事も見つけた。
etc
https://github.com/uutils/coreutils/
unixのコア・ユーティリティをRustで書くプロジェクト。面白いな。
rust-lang-nursery / rust-cookbook (can read offline)