世界一やさしい海外移住の教科書
最近英会話を始めたりしたりして海外移住、海外で働くというのが幾分気になっているため読んでみた。1ページ当たりの情報量が少ないのですぐに読めました。大まかに情報を知るという意味では悪くなかったけど、そこの裏にあるであろう苦労があまりに見えなさすぎるのが気になった。個人的にはどれぐらい大変なことがあって時間かける必要があるというのが知りたかったのですがそういう本ではなかったです。「やさしい」と銘打っているのでそこはあまり触れずということなのかもしれないけど気になった。例えば英会話の習得にフィリピンなどへの短期言語留学が出てくるけど、調べるとうまくいかなかった話も少なからず出てきてそこまでお手軽ではないのではないかという気がした。
まあ海外移住をそこまでハードルの高すぎるものと考えすぎず, チャレンジすることが大事ということとして受け取っておきます。
F#を4ヶ月ほど学んでみて
今年に入って新しい言語を学ぶかということで F#の勉強を初めて 4ヶ月ほど経ったので現状のまとめ。 やったことは主に小さいプログラムを書いたり, LeetCodeの問題を 100問ほど解いたという程度(LeetCodeは現状 F#未対応なので対応してほしい)。
結論としては始めるのが楽で学ぶ価値のある言語だと考えています。
書いたもの
良い点
- セットアップが楽
- 1ファイルからコードを書ける
- いちいちプロジェクトを作らなくても .fsxファイルを作って書き始められるのが良い
- REPLベースの開発
- Java系言語に比べるとだいぶ軽い
- 気楽にやるにはすぐに REPLなど立ち上がってほしいので.
- 標準ライブラリの APIのシグネチャの統一感
- パイプライン演算子を使うことが意識された引数の並び。
- ライブラリの豊富さ
- .NETはライブラリが豊富
いまいちなところ
現状特に不満はないですが、しいてあげるなら
- 公式ドキュメントが複数ヶ所にある
- Ionide拡張の REPL連携
- Emacsだったらカーソル位置の関数を REPLに送るとか様々な単位で REPLに送れるのだけど, 行かリージョンかファイル全体かと大雑把な区切りしかない。特にカーソル位置の関数は欲しいかな
- formatter
- fantomasというのがあり, それを使えばいいのだけど無駄に空行があったり改行があったりあまり好きじゃない。まあ統一されているのが重要なので大したことはないのですが。
言語を学んで
普段は C++とか Rustとか C#を書いているのですが, immutable dataが基本でループでなく再帰を使うというのは良い刺激になっていると思います。 副作用を使えると一つのことにいろいろ詰め込んで書けてしまうけど, そうではなく副作用がないように分割して考えるということを意識するようになったように思えます。現状アルゴリズム的なものを中心に書いてきたので今後はアプリケーションなり実用的なものを書いていければなと思っています。
F#で外部コマンドを起動し, その標準出力結果を扱う
コード
open System.IO open System.Diagnostics let cmd = new Process() cmd.StartInfo.FileName <- "ls" cmd.StartInfo.Arguments <- "-l /usr/bin" cmd.StartInfo.UseShellExecute <- false cmd.StartInfo.RedirectStandardOutput <- true if cmd.Start() |> not then failwith "Failed to execute command" using (new StreamWriter("tmp.txt")) (fun writer -> cmd.OutputDataReceived.Add(fun e -> writer.WriteLine(e.Data)) cmd.BeginOutputReadLine() cmd.WaitForExit())
C#と同じではあるのだけど。ハマりどころとしては event(OutputDataReceived
)を使わないと出力が大きい場合 4096 byteしか読めないという問題があった.
ただ行単位かつ改行コードは stripされた状態でイベントハンドラーに渡ってくるので微妙な感じは少しある. 今は dotnetはクロスプラットフォームだけど
プロセスの扱いとかは Win32 APIにめちゃ引っ張られているなぁという感じがする.
F#で Priority Queueを使う
概要
競技プログラミング的なものを解いているときに, 優先度付きキューを使いたくなることがあるのでそのメモ
コード
.NETの標準ライブラリには存在しないので F#向けのコレクションライブラリを nugetから取ってくる. fsxで書いている場合は以下のような行を足す. projectがある場合は dotnet add
で依存関係を追加する
#r "nuget: FSharpx.Collections"
定義を見るとわかるが, キューの要素は comparison制約を満たす必要があり, System.IComparableを実装している必要がある. IComparableを実装する際は Equals, GetHashCodeも overrideする必要があるのでそれを行う.
以下に例を示す. 年齢順, 年齢が同じなら名前で並べるというもの. F#で独自の比較, 等価性のチェックを行う場合は該当の methodを override, 実装するだけでなく属性も指定する必要があるようである.
open FSharpx.Collections [<CustomEquality; CustomComparison>] type PersonData = { Name: string Age: int } override this.GetHashCode() = hash this override this.Equals other = match other with | :? PersonData as o -> this.Name = o.Name && this.Age = o.Age | _ -> false interface System.IComparable with member this.CompareTo other = match other with | :? PersonData as o -> if this.Age <> o.Age then compare this.Age o.Age else compare this.Name o.Name | _ -> failwith "cannot compare with different object"
全体の例は以下の通り.
#r "nuget: FSharpx.Collections" open FSharpx.Collections [<CustomEquality; CustomComparison>] type PersonData = { Name: string Age: int } override this.GetHashCode() = hash this override this.Equals other = match other with | :? PersonData as o -> this.Name = o.Name && this.Age = o.Age | _ -> false interface System.IComparable with member this.CompareTo other = match other with | :? PersonData as o -> if this.Age <> o.Age then compare this.Age o.Age else compare this.Name o.Name | _ -> failwith "cannot compare with different object" let q = PriorityQueue.empty true |> PriorityQueue.insert { Name = "Alice"; Age = 42 } |> PriorityQueue.insert { Name = "Bob"; Age = 50 } |> PriorityQueue.insert { Name = "Chris"; Age = 18 } |> PriorityQueue.insert { Name = "David"; Age = 73 } |> PriorityQueue.insert { Name = "Elly"; Age = 33 } |> PriorityQueue.insert { Name = "Yoshida"; Age = 73 } let (a, q1) = PriorityQueue.pop q printfn "poped=%A" a // Yoshida let (b, q2) = PriorityQueue.pop q1 printfn "poped=%A" b // David let (c, q3) = PriorityQueue.pop q2 printfn "poped=%A" c // Bob let (d, q4) = PriorityQueue.pop q3 printfn "poped=%A" d // Alice let (e, q5) = PriorityQueue.pop q4 printfn "poped=%A" e // Elly let (f, _) = PriorityQueue.pop q5 printfn "poped=%A" f // Chris
quadmath Perlでハマったところ備忘録
細々とメンテナンスしている Perlモジュールで quadmathが有効な Perlで失敗しているケースがいくつか見られたので対応していた. その対応に関する記録.
usequadmathとは
https://perldoc.perl.org/Config#usequadmath
数値の保持に可能であれば 4倍精度浮動小数点型である __float128を利用するコンフィグオプション. 設定を有効にしてビルドするためには libquadmathがインストールしておく必要がある.
名前だけ聞くと浮動小数点だけ影響があるのかと思うのだけど Perlような言語では整数とか浮動小数点はあいまいなので整数にも影響が出ておりテストがこけていた.
ハマった点
整数の精度が変わる
大きい整数リテラルを文字列化したときの精度が異なっており予期せぬテスト結果となっていた
#!/usr/bin/env perl print "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=", 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, "\n"; print "0x800000000000000000000000000000=", 0x800000000000000000000000000000, "\n";
上記のスクリプトを quadmathが有効な Perlとそうでない Perlで実行した結果は以下の通り異なるものとなる.
quadmathが有効
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=8.30767497365572420564879412675215e+34 0x800000000000000000000000000000=6.64613997892457936451903530140172e+35
quadmathが無効
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=8.30767497365572e+34 0x800000000000000000000000000000=6.64613997892458e+35
対応は管理の保守の問題などから quadmath Perlではテストを行わないことにした.
数値から文字列への変換が適切に行えない
https://github.com/gfx/Perl-Data-Util/blob/f8db84cf6fe5c526adfa76d047bfd715296477bf/t/18_is_value.t
の is_integerの浮動小数点数の場合は数値を文字列化してそれを解析して判定を行っているが, 変換に使われていた関数が snprintfだったため正しく変換ができていなかった. snprintf等の標準ライブラリの変換関数は __float128には対応しておらず, quadmath_snprintfを使わなくてはならない.
https://perldoc.perl.org/perlapi#my_snprintf
ifdefで分けることを考えたが Perlのソースコードを眺めていたところ, my_snprintfが quadmathを有効にしてビルドした場合内部で quadmath_snprintfを呼び出すような実装になっていたのでそちらを呼ぶように切り替えた。
modifierと specifierはべた書きしてしまうとポータビリティがなくなるので, Perlで定義されているマクロを使っています.
おわりに
Perlみたいな動的型言語で標準で使われる浮動小数点数の精度を変えるというのは影響は多すぎると思った. あとドキュメントがロクに書かれていないのも辛かった.
unzip file which contains symbolic link by zip-rs
zip-rs does not support symbolic link yet(https://github.com/zip-rs/zip/issues/77). However it is possible by using public APIs as below.
use std::fs; use std::io; fn main() { std::process::exit(real_main()); } fn real_main() -> i32 { let args: Vec<_> = std::env::args().collect(); if args.len() < 2 { println!("Usage: {} <filename>", args[0]); return 1; } let fname = std::path::Path::new(&*args[1]); let file = fs::File::open(&fname).unwrap(); let mut archive = zip::ZipArchive::new(file).unwrap(); for i in 0..archive.len() { let mut file = archive.by_index(i).unwrap(); let outpath = match file.enclosed_name() { Some(path) => path.to_owned(), None => continue, }; { let comment = file.comment(); if !comment.is_empty() { println!("File {} comment: {}", i, comment); } } if (&*file.name()).ends_with('/') { println!("File {} extracted to \"{}\"", i, outpath.display()); fs::create_dir_all(&outpath).unwrap(); } else { println!( "File {} extracted to \"{}\" ({} bytes)", i, outpath.display(), file.size() ); if let Some(p) = outpath.parent() { if !p.exists() { fs::create_dir_all(&p).unwrap(); } } let is_symlink = if cfg!(unix) { const S_IFLINK: u32 = 0o120000; file.unix_mode().unwrap() & S_IFLINK == S_IFLINK } else { false }; if is_symlink { #[cfg(unix)] { use std::io::Read; use std::os::unix::fs; let mut data = Vec::new(); file.read_to_end(&mut data).unwrap(); let original = String::from_utf8(data).unwrap(); fs::symlink(original, outpath).unwrap(); } } else { let mut outfile = fs::File::create(&outpath).unwrap(); io::copy(&mut file, &mut outfile).unwrap(); // Get and Set permissions #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = file.unix_mode() { fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); } } } } } return 0; }
Advent Of Code 2020を終えて
yosuke-furukawa.hatenablog.com
今年始めて参加した Advent of Code 2020年版を終えました
結果は上記のような感じ. 各問題 100位以内に解ければポイントがもらえるのですが, 100位なんて程遠すぎて結果は 0点. これは早くできたなと感じてもそんな問題はみんな早く解けているわけで自分が早く解けたときの方がむしろ順位は低かった印象. ポイントを取るにはまだまだ実力不足であることを感じました
Advent of Codeの特徴
- コードの提出はない, 答えだけ提出
- どんなに非効率なアルゴリズムでも答えが出ればいい. 微妙な問題サイズの場合はしばらく待てば解けてしまうこともあった
- 超早いマシンを持っていれば幾分有利かも
- 問題の入力が人によって違う
- 世界中の人が多く解いているので解説が多数ある
感想
- 初期の方は 1時間もかからず解けていたのであまり手応えがないって思っていたけど, 中盤からどんどん難しくなり休みをほとんど潰した日もあった
- 問題が(無駄に)ストーリーがあり読んで理解するのに時間がかかる
- 英語力が低いのが原因
- 最速の人の解答時間に私の問題理解速度が追いついていないレベル. 10分ぐらい読んでやっとわかってことが多々あった
- 入力のパースが面倒くさい
- 例があるのは助かる
- 例を見て問題をちゃんと理解できたということが多々あった
- テストケースとして使える
- しかし例があることで逆によくわからなくなることもあった(day 17の三次元, 四次元ライフゲーム)
問題の振り返り
全く解答が思いつかなかったという問題はありませんでしたが, 現実時間で解答を求められない問題がいくつかありそれらは解説を見た上で解答を作成しました. わからなかったのは以下の問題.
- day 10 part2
- 電源アダプタの並べ方が何通りあるかという問題. 愚直に求める方法しか思いつかず.
- day 23 part2
- ボール並び替えゲームを 1000万回やった後の並び方を求める問題. 愚直に求める方法しか思いつかず
- 問題的に時間がかかることは予見できていたのだけど, 見当はずれの効率化を探していていつまで経っても効率的な方法が思い浮かばなかった
私の解答がいまいちだった問題として * day 15 part02 * 非効率的な実装だったけど, 3分ぐらい動かしていたら答えが出たのでそれで提出. 考え直したい
このあたりは単純に力不足だなと感じた. 来年はできれば解説を一つも見ずに完走したい. あとやっぱり自分で考えに考えて解くってのはやっぱり快感が伴うのでそういう意味でも自分で解けたらと思いました
最後に
楽しい経験ができたのと, まだまだ自分が力不足だということが認識できました. 来年も参加して 1ポイントでも取れるように実力をつけていければと思います