RustでGithubのissueひっぱてくるCLIアプリを書いた
Ruby、Elixirときて完全に別軸の静的型付け低級言語としてRustで遊んでいる。
お正月に泣きながらRustのコンパイラといろいろしてた。
ユーザー名とレポジトリ名を下のように渡すと
github-issue rails rails
number |title |created_at | 38237 |Update caching_with_rails.md |2020-01-15T02:29:53Z | 38235 |Move advisory lock to it's own connection |2020-01-14T17:54:27Z | 38234 |Rails 6 : config.require_master_key=true doesn't raise an error if key does not exist |2020-01-14T15:49:12Z | 38229 |Prevent `has_one` `build_association` from `touch` parent record if the record isn't committed |2020-01-13T22:11:50Z | 38226 |Active Record unit tests fail with MySQL 8.0.19 |2020-01-13T14:59:02Z |
という感じで標準出力するアプリを書いた。プログラミングElixirの題材をRustで書き直したやつ。まだまだテストでのモックとかやるべきことはいっぱいある。
Rustつまづきポイント
そもそもコンパイラになんとか通してもらうために無理した書き方をしている自覚があり、しかもきれいにする方法がわからないので書くたびに自分の無力感が高まって泣いていた。
文字列型の種類が複数
Rustでのいわゆる文字列には、メモリ長が決定されている組み込みの&str
と、メモリ長が固定されていない標準ライブラリにあるString
型がある。String
型はヒープ(スレッド間で共有できるメモリ領域)に格納できて可変。 実際にコード内で
"string"
のように書くと&str型になるのでGithubからのレスポンスをStringとして扱う時に色々変換しなくてはならず辛かった。多分もっといいやり方があると思うので助けて。
Result型とOption型
つまづきポイントに入れてるけど同時にRustの優れてるポイント。多くの関数がこの2つのうちどちらかを返すことが多い。
OptionはNoneもしくは値を返すという型。 Noneはnullのようなもの。 返り値書くときにはSome(1)
やNone
という書き方をする。
Resultは例えばrun
みたいな関数があったとして
pub fn run(args: Vec<String>) -> Result<i32, Box<dyn Error>>
ようにエラーを返すかもしれないことを明記できる型。
返り値書くときにはOk(1)
やErr(ParseError)
という書き方をする。
これらの型で返された値はunwrap()
をするか
process(args)?
のように末尾に?
演算子をつけて例外やNoneが出たら関数全体としてエラーやNoneを返すということを明示することができる。しかしどんな形がベストなのかイマイチつかめない。ここではめったにエラー起きないだろうってところはunwrap()
でそれ以外は?
という感じで使えばいいのだろうか。
ちなみにNoneやErr()に対してunwrap()
するとpanicする。unwrap_or_else()
なんかもあるのでやりようはありそう。
所有権
もう全然スマートな解決方法がわからない。無理やりclone()
したりして逃れている。
rows.as_array().unwrap().to_owned()
こんなコードも書きながらもっといいやり方ないのかと泣いていた。
Rustのよかったところ
よかったところというとおこがましいという気持ちになるくらいには使いこなせてないんだけど書く
パターンマッチ
最高of最高
Result型
コードを読む側、使う側としてResult型はとても便利。エラーを?でキャッチできるしこの関数にエラー処理が任されているんだなというのがひと目で分かる。 そもそも型というものがコールドリーディング時に便利。
たぶんOptionとResultは慣れると余計なエラーやNullの処理挟まなくて良くなるので便利そう。
クロスコンパイル
最終的に機械語になって人に配布できるというのがRubyから入るとかなり画期的に感じる。 一応このアプリもタグ付けしたらGithubActionで複数OSでバイナリをビルドしてzipにしてリリースするということができた。(linux環境だと何故かOpenSSLがないと言われてリリースビルドが通らない。libssl-devを直接入れても再現するので困ってる)
cargo
Elixirのmixと同じで、ライブラリやテストが言語に組み込まれているのもモダンな言語感があってよかった。リンターもしっかり搭載。
ビルド通ったときの安心感
僕はCとかの低級言語はなにも触った事ない。けれどよく未定義動作の処理に困る話は聞く。その中でRustでコンパイラ通るということはメモリを贅沢につかってこそすれ危険に利用してないことなので安心感がある。
俺は今Rustというかっこいい言語で開発しているという謎の興奮
これが9割
まとめ
Rust本当になにもわからん。でも実践Rust入門は良書です。
2019年を振り返る
はじめてのふりかえり
1月
- エンジニアとして働いてなかった。2月に転職することがわかってたのでソワソワしていた気がする。新卒から一年と十ヶ月の短い期間だったけど迷惑かけまくったなぁと反省しています。
- 東京に引っ越した。
- 有給消化中にOSSにはじめてコミットした。嬉しくて書いた記事が思ったより反響があり、別記事内で言及してくれる人もいて嬉しかった。同時にQiitaの拡散力怖いと思ったのでメインはブログにしようと思った。 qiita.com
2月
- 業務委託として今の社で勤務を始めた。出社時間が自由すぎてカルチャーショックだったのは覚えている。自分のかいたコードがチームのリポジトリのmasterにマージされた時の感動は忘れられない。
- 読んだ本の中だとこれが印象に残っている。 sasa5740.hatenablog.com
3月
- 業務で一つリリースがあった。といっても僕はアプリケーションのコードを書いただけなのだが。
- 初めてエンジニアとして稼いだお金が口座に入った。こんなんでもらってええんかという気持ちになった。
- この頃から読んだ本をアウトプットしようと思って色々やった。
- Ruby2.7で追加されるパターンマッチに関してブログを書いたら実装した人から反応をもらえた。ブログ書くモチベの源泉となる体験だった。
4月
5月
- 仕事して本を読んでいた。
特に大規模サービス技術入門はすごい良書で、この後にプロセスとかメモリとかの勉強に触れようと思ったのはこの本のおかげだ。
6月
- SQL実践入門を読んだ。これも良書でもっと早く読んでおけば良かったなと思った。
7月
- 自分のPCをThinkPad X1 Carbonにした。Ubuntuのほうがコスパ良いじゃん!とイキってUbuntuにしたのだが、職場がMacBookの状態で更に別の環境を手入れするのは面倒だった。次はMacBook買うと思う。
とはいえUbuntuをいれていろいろトラブルシューティングしている内にUNIXコマンドの知見がたまったのでそれはそれで良かった。開発環境も設定さえちゃんとすれば不満なく使える。
- 新しいPCを勝ってモチベ爆上がりしたのでGemを書いた。Pryを頑張って読んだ気がする。面識ない人からPRをもらって飛び跳ねたのも覚えている。エイリアスで十分なことに気づき特に使ってない。
8月
- 引き続き別のGemを書いた。
rails console
コマンドの実装を頑張って読んで書いた。ローカルではしっかり動作するの確認したんだけど職場のDocker環境でうまく動かなくて困っている。あとRailsのバージョンへの依存も激しすぎる。名前はかなり気に入っている。
9月
- ポエム書いた
- この頃Ruby以外の言語もちゃんとやらなきゃなという気持ちが高まりElixirを学習し始めた。
Elixirについて社内LTで発表したよ - Tallman
tokyo.ex#13 elixir本体ソースコードもくもくリード会に参加してEnum.tally書いてみた - Tallman
10月
- 株式会社グロービスに正式にジョインした。
- 応用情報技術者試験を受けた。これも無事に受かったので嬉しい。
- ElixirへのPRがマージされた。大きめのOSSへのコミットはこれが初めてであり、ましてやプログラミング言語だったので相当嬉しかったことを覚えてる。強引感が否めなかったのでちょっと反省している。
11月
- Punditにコミットした。仕事での困りをOSSへの貢献に変えるというのは一度やってみたかったので良かった。今年一番のPRだったと思う。
- 社内でLearning GraphQLの読書会をやった。自分で始めた最初の勉強会だった。英語の本しかでてなかったりで最終的に参加者が二人になってしまい進め方を反省した。
12月
- 会社のアドベントカレンダーにPumaのノンブロッキングI/Oについて調べた記事を投稿した。今年一番頑張って調べて書いた記事かもしれない。
- 静的型付け言語もやりたいなぁと思って最近Rustを書いてる。とりあえず型ジェネリクスというやつを完全理解した。所有権とライフタイムで脳が爆発している、メモリ管理を学ぶのに良さそう。
- 会社でReal World Httpの読書会を始めた。これを写経しているだけでFindyで一人前のGopherと認定されるぞ!
まとめと来年の抱負
今年のはっきりとした成果は基本及び応用情報技術者の取得とElixirのコミットくらいだろうか。
2020年は
- なにかしら外部の勉強会で登壇
- 大学院に進学
の2つを達成したい。
JAISTの社会人コースの見学にいった。年始に願書をだそうと思っている。
僕はエンジニアとして一緒に働きたいと思っている人がいる。その人に少しでも近づけるようやっていきたい。
「エンジニアの知的生産術」を読んだ
読んだ理由
エンジニアとして働き始めたこの一年間、どうにも自分が闇雲に進んでいる感覚が拭えなかった。
目標にしている人はたくさんいるのだけれど、果たして自分は効率的に目的に向かっているのか?という疑問を感じるようになってきたので読んだ。
新しいことを学ぶには
情報収集、実践、応用はよく学習本であるやつという感想だったが、具体的に技術書を写経することについて述べられているのが面白かった。写経は効率が低いけれども写経しないと理解できないほどの問題に立ち向かわないとコンフォートゾーンから出れない、と書かれていた。最近自分もRustの技術書をヒィヒィ言いながら写経しているのだけれど、この本で少し勇気づけられた。
写経するときも頭を使って書き換えやコメントを利用しようとも書かれていた。このあたりは自分もよくやるのでこれからもやっていきたい。
逆にRubyの本を一から十まで写経するのも効率が良くなかったなという反省もあった。
やる気を出すには
この本ではタスクを一つに絞り、タスクを小さくするためにポモドーロ・テクニックを活用することが書かれていた。
タスクをマルチにするのは良くないのは自分も感じていて、同期的にタスクをこなすというのは腑に落ちた。
このブログもポモドーロ・テクニックを使って書いている。スマホを見ないと決めるのは大きな効果があった。
記憶を鍛えるには
この章で面白かったのは、一度読んで思い出してからさらにもう一度読むという「思い出し学習」が単に本を四回読んだだけの人より学習効果が高かったというものだ。
「思い出し」というのは記憶の穴を自己認識し危機感を得られる行為らしい。技術書を読んだあとに目次をみて内容を説明できるか?というぐらいなら真似しやすいだろうか。ブログを書くにのは二回目の通読のような気がするので、ブログを書く前に「思い出し」をやるといいかもしれない。
効率的に読むには
読書の目的は大雑把な地図、思考の道具というのが面白かった。自分の場合何しろ基礎体力が足りないと感じているので大雑把な地図がほしい。そのためにいろいろな分野をつまみ食いする。というのが大事なフェーズなんだろうと感じた。 とりあえずブログを書いて人に教えるを重点的に意識したほうが良さそう。
何を学ぶべきか?
戦略をもって学ぶのが良いということが書かれていた。 差別化戦略について語られていて、A要素だけでは人に勝てないならB要素もかけ合わせて卓越することが重要だと書かれていた。よくキャリア戦略なんかでも語られている考え方だと思う。「境界をまたぐ、複数の組織に属する」というのは本やOSSでも達成できることのような気もするけど、実際に体験するためには働いたり留学なり何かしら義務を持ったほうがいいのだろうか。
まとめ
現実の行動で変わったことは
- ポモドーロ・テクニックを使うようになった
- 広く片っ端からつまみぐいすることも悪くないと考える様になった。
- 本を読んだあとに目次をなめる行動をするようになった。
ぐらいだろうか、周りがすごいエンジニアばかりで差別化するというのがなかなか思いつかない。
「食事する哲学者」で学ぶデッドロック
最近 プログラミング言語Rustを読んでるのですが、並行処理のチュートリアルで「食事する哲学者」という問題を知りました。
昔々、裕福な慈善家が、5人の高名な哲学者が宿泊できるカレッジを寄付しました。それぞれの哲学者には思索活動にふさわしい部屋が与えられました; また共用のダイニングルームもあり、そこには丸いテーブルが置かれ、5人それぞれが専用で使うイス5脚で取り囲まれていました。 彼らはテーブルを反時計回りに座ります。哲学者の左側にはそれぞれ金のフォークが配され、 中央には大きなボウルに入ったスパゲッティが常に補充されていました。哲学者は大半の時間を思慮に費やすのですが; 空腹になった時は、ダイニングルームに出向き、自分専用のイスに座り、左側のフォークを取上げ、スパゲッティに突き刺します。 しかし、絡まり合ったスパゲッティを口元まで運ぶには2本目のフォークが必要でした。なので哲学者は自分の右側にあるフォークも使う必要がありました。 食べ終わったら両側のフォークを元に戻し、席から立ちあがって、思索活動を続けます。 もちろん、1本のフォークは同時に1人の哲学者しか使えません。他の哲学者が食事したければ、 フォークが再び戻されるまで待たねばなりません。
並行処理でデッドロックが起こりうる問題です。 そのまま各哲学者が同時に右のフォークをとるとこうなります
この状態になってしまうと1が左のフォークを取りたくても5が持っているので取れない、5も早くパスタをとってフォークをおろしたいけど4がフォークを持っているので取れない。…となり全員がお互いのフォークを下ろすのをおみあいしてしまう、いわゆるデッドロック状態になります。
プログラミング言語Rustでは5だけ最初に左のフォークを取らせて確実に誰かが両手にフォークを持てる状況を強制させていました。
この問題は
食事する哲学者の問題 - Wikipediaにも乗ってるけど色々デッドロックを起こさない方法があります。
セマフォって概念ここで初めて知りました。 dirty/cleanなんかはページングで使われてる気がする。
Rustにセマフォあったみたいだけど非推奨になっていた。MutexとCondvarでなんとかするのだろうか。
Punditにコミットした
結果的には破壊的変更をしてしまったのでPR送った経緯を書いておこうと思う。 もちろんこのままリリースされるかはわからないけど。
ちなみにPunditは以下のように明瞭な形でリソースに対しての行動に制約を設定できるようになるGemだ。
class PostPolicy attr_reader :user, :post def initialize(user, post) @user = user @post = post end def update? user.admin? or not post.published? end end # in controller def update @post = authorize Post.find(params[:id]) if @post.update(post_params) redirect_to @post else render :edit end end
書きやすく読みやすいコードが書けて良いGemだと思う。
PRを立てた経緯
業務でPunditを利用させてもらっていて、ちょっと面倒なところがあった。
https://github.com/varvet/pundit#policy-namespacing にもあるがこのようなコードだ。
module Admin class PostPolicy ~~ end end class AdminController < ApplicationController def authorize(record, query = nil) super([:admin, record], query) end end class Admin::PostController < AdminController def show post = Post.find(params[:id]) authorize(post) end end
「同じリソースに対してpolicy*1を名前空間で分けて複数設定する」というのはよく行うのだけれど、Punditは上記のように名前空間を配列で渡すとpolicyを推測して読み込んでくれる。
がしかし、この方法をつかうとせっかくクールなこの書き方が使えなくなってしまう。
def show @user = authorize User.find(params[:id]) end
authorize
は渡された引数をそのまま返すので上記のように綺麗にかけるのだが、名前空間を推測させるために配列を渡すとそのまま配列が帰ってきてしまうのである。
仕方がないので業務では
class AdminController < ApplicationController def authorize(record, query = nil) super([:admin, record], query) record end end
としてrecordが帰ってくるようにした。*2
recordが帰ってくるのが望ましいだろう、ということでせっかくのOSSだし修正の提案をしたのがPRを立てるまでの経緯だ。
PR立てた後
マージされるまでメンテナーの方に丁寧なレビューをもらい感謝しかない。こういってはなんだが自分としては無料で自分のコードみてもらって良いことしかなかった。
「OSSはやることが多くて大変」という言葉をOSSパッチ会でkoicさんもおっしゃっていたが、本当に尊敬できる方たちだと思う。
Punditという素晴らしいGemをメンテしていただきありがとうございます。
READMEの更新を忘れて後ですごすごPR立ててすいません
最近Elixirが楽しかったがRubyもやっぱり面白い。
「コーディングを支える技術」を読んだ
チームの人が読んでいて面白そうだったので、またElixirの諸々でプログラミング言語というものに関心があったので読んだ
コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)
- 作者: 西尾泰和
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (37件) を見る
感想
ifを始めとする制御構文が何故生まれたのかについてgotoから解説してあったり、関数、例外、コンテナ等いつも書いてるプログラミング言語には当たり前にある機能が何故あるのかだったり、型、オブジェクト指向、並行処理を発端から物語のように書いてある等、単純に読み物として面白い本だった。
配列、連結リストの違いや、辞書におけるハッシュテーブルと木構造は技術的にも知っておきたい認識だと思う。
文字列、文字コードの話ものっていて、文字列がClang以外長さの情報持ってるとか、Unicodeに至るまでの過程とか、当たり前*1(であろう)知識を補完できてありがたかった。
読みやすい、書きやすい、とはなんなのか、それを計算機に効率よく処理させるにはどうしたら良いのか、色んな人がその課題に取り組んでいる。
どんな人におすすめか
何種類かプログラミング言語を触ってみたけど周りが言ってる「文字コード」とか「スレッドセーフ」とか「型推論」とかいまいちついていけないなぁという人におすすめできると思う。
自分が書いてるコードに対して「これってそういう考え、歴史からできていたのか!」というのがふんわりわかってコードを書くのがまた少し楽しくなった。
Elixirにコントリビュートしてみよう!
最近ElixirにPRをだしてマージされました。
Elixir 1.10からEnum.frequencies/1
とEnum.frequencies_by/2
が多分生えます。
iex> Enum.frequencies(~w{ant buffalo ant ant buffalo dingo}) %{"ant" => 3, "buffalo" => 2, "dingo" => 1} iex> Enum.frequencies_by(~w{aaa aA bbb cc c}, &String.length/1) %{3 => 2, 2 => 2, 1 => 1}
着想はRubyのtallyからです。
Elixirは書いていてとても楽しい言語で,リンターやテスト等周辺のエコシステムも整っていて便利です。
みなさんもぜひElixirの魅力にはまってElixirにコントリビュートしてもらいたいということで記事を書きました。
今回はElixirという言語そのものの仕様ではなく、elixir-lang/elixirへの実際にPRした時の流れを書きたいと思います。
後世のElixirを担うそこのあなたに少しでも役にたてば幸いです。
elixir-lang/elixirの特徴
コントリビュートするという視点で見てelixir-lang/elixirの一番大きな特徴は実装がElixirで書かれているということです。Rubyの実装に貢献するにはC言語の知識が必要ですがElixirではElixirが書ければElixirが書けます!
elixir-lang/elixirに貢献したい!どうすれば?
公式のREADMEを読みましょう。 一番最新で正しい情報がのってます。とはいえそれだとこの記事終わってしまうので自分のやったことの流れを書きます。
- forkしてローカルにclone
- Enjoy Cording!
make compile
して変更したexファイルをコンパイルbin/elixir lib/elixir/test/elixir/変更したファイルのテスト.exs
でテストを実行mix format
でリンターを走らせます- commit & push!
1,2,6はどのOSSでも当然ですね。
2の作業中に実際にREPLを叩きたい場合は、変更したファイルをコンパイルしてbin/iex
で変更したコードが反映されたiexを起動することができます。
以下のように変更したファイルだけコンパイルすることもできます。
bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
6については、例えばrails/railsだと「squashして一つにまとめてね」といった注意点があったりしますがElixirというOSSには今の所ないみたいです。
僕はできなかったけどやるべきだったこと
新機能の追加はまずメーリスで提案しよう!
いきなりREADMEちゃんと読め案件なんですが、新機能のPRを出す前にElixir Core maling listで提案してなぜ役立つのかをコミュニティに説明しましょう。僕はこれを怠りいきなりPRたてて30分でcloseされました。 今回の自分の提案では「tallyの機能って他の言語であったりする?」「名前をこうするのはどうだろう」と様々な意見が出ていました。コミュニティの熱量も感じられますしメーリングリストは怖いところではないですよ!
ちゃんと議論が収束するまで待ちましょう
これが一番の反省点なんですが、メーリングリストに提案を出してすこし肯定意見が出始めたあたりで勇んでPRを再度立ててすぐにcloseされるということがありました。ちゃんと議論の収束やコミッターからの指示を待ちましょう。
パフォーマンスを意識したコードを書こう
最初に僕が出した実装はこれです。
def tally(enumerable, func \\ fn x -> x end) do group_by(enumerable, func) |> map(fn {key, val} -> {key, count(val)} end) |> Map.new() end
関数名とか可読性は別としてこの実装だとgroup_byを使っているので全ての要素に二回アクセスしていることになります。
PRを出して提案されたコードは以下
def frequencies_by(enumerable, key_fun \\ fn x -> x end) when is_function(key_fun) do Enum.reduce(enumerable, %{}, fn entry, acc -> key = key_fun.(entry) Map.update(acc, key, 1, &(&1 + 1)) end)
reduceは折りたたみ演算です。この実装なら各要素へのアクセスは一回ですみます。 全てのプログラミングにおいて当然のことですが、特にプログラミング言語や大きいOSSではパフォーマンスが重要だと思います。意識して書くと良いですね。
自分一人でPRをつくるわけではない
特に既にコミュニティがあるOSSに言えますが、PRは自分だけで完成するものではないです。
最終的に自分が出したPRも色々変更されています。 というか最終的な実装は僕が書いたとは言えない
最初から文句なしマージというのは他のPR見ていてもなかなかないと思います。(typo修正とかは別)
きちんとOSSとコミッターに敬意をはらったPRをだして反応に真摯に対応しましょう。
おわりに
後半はElixir関係なくなっちゃいましたが、ElixirにPR出すときの簡単な標識にでもなっていれば幸いです。
なんだか偉そうなこと書いたけどメンテナー側の人間ではないのでもしかしたら間違ってるかもしれない。とりあえずROMして雰囲気を掴むのが一番いいと思います。
もしElixirに興味を持たれた方は(日本語版は1.2時点のもので少々情報が古いですが)
プログラミングElixirという本がおすすめです。というか神本なのでぜひ買ってください。他言語からElixirを学ぶ人向けに書かれていて、ライブラリを作るハンズオンや非同期処理やメタプログラミング等盛りだくさんの内容です。
また今回PRを出すに当たり#tokyoexで学んだことにとても助けられました。大変勉強になる会なのでみなさんもぜひ足を運んでみてはいかがしょうか。