TaPL読書録 #6
前回:
今回は7章、8章。
7章は実装章である。個人的には小ステップの実装をするところまでで終わり。大ステップには変換しなかった。もうひとり書いてきた人のと小ステップ・大ステップでの差異をかるく比較して終わり。実装したのは2人だったが、「実装したからといって理解したとは限らない」のであった。
8章からは「型つき算術式」ということでようやく型がついた。長く苦しい戦いだった(マジ)
以前は「行き詰まり状態」というものを導入して評価をとめていたが、それらを実行前に静的に解釈して行き詰まり状態であることを検査したい。そのため型 Nat
, Bool
を導入する。
検査は「保守的」に行われることに注意。すなわち if 項 if t1 then t2 else t3
において、 t1
が「明らかに」真である場合にも t3
の型情報も用いる、という話である。ここでは t2
と t3
についてユニオンしたような型の導入は行わない1
補題8.2.2.「型付け関係の逆転」
逆転が成り立つのは、ある値が複数の型に解釈される可能性がないからである。すなわち 1
が型 Nat
であり、かつ型 Rational
でもあるような値が今の所存在しないことが前提となっている。これは定理 8.2.4. 「型の一意性」にて言及されている。
演習8.2.3.
「正しく片付けされた項」の「正しく」ってなんだっけ、というメモがあったが定義 8.2.1 に「項 t
にたいしてある型 T
が存在して t: T
となるとき t は型付け可能(または正しく型付けされている)という」と書いてあった。
解答してはだいたい自明で、筆者のメモでは「規則に従ってるので規則が正しく型付けされていること・保守的に評価することを考えたら自明」とある。はい。おそらく正式に書く場合には逆転補題についてそれぞれ追えばよい。
定理8.2.4「型の一意性」
余談として部分型(で合ってたか少し自信がないが)についての話が出てきた。以下のようなクラス、関数を考える。記法は独自文法であるが意味はおそらくとれるとおもう2。
class A x: int end class B x: int y: int end func f(a: A) -> int return a.x * 2 end
上記の関数 f
について、仮引数 a
として B
のインスタンスをいれることが可能なのか、という話。この場合 B ⊂ A
なので、使えてもいいよね、という話をした。
ここでダイヤモンド継承の話もした。ダイヤモンド継承をして同名メソッドがあったときには、シグネチャさえ同じならば型システム的にはおkになりそうだよねー、実装的にはどっちにとぶか決められなくてつらそうだよねーなど。
8.3.
型の安全性とは進行と保存である。
演習 8.3.4. スキップ
演習 8.3.5.
俺のメモでは pred 0
で行き詰まるので pred nv
の nv は「0 ではない numeric value」という型情報をもたないと型システムが健全じゃなくなるので無理、と書いてあった。
演習 8.3.6.
良問では?という話になった。
メモでは「型付けされていて、型が2つしかないから保存の定理の対偶から明らか」とあるが、実際のところは if true then 0 else false
みたいな項が反例となる。
演習 8.3.7.
大ステップにおける「最終的に値になる」という制約が強いのだなあ、ということを確認して終わり。
次回 chap9 全部。
読了: Clean Architecture 達人に学ぶソフトウェアの構造と設計
『Clean Architecture 達人に学ぶソフトウェアの構造と設計』を読み終えたので感想です。
なにか新しいことが書いているというわけではなく、ちゃんと依存関係について考えようねという本だと捉えている。ソフトウェア設計の原則を、クラスなどの細かい粒度からもっと粗いパッケージ・サービスの粒度まで適用して俯瞰することができた。
悪書だとは思わないが、特にめちゃくちゃよい本だとも思わなかった。普通のことを「丁寧に」書いているような印象。個人的には逆にくどく感じられ、ざっと流し読み、というスピードで読了した。
さて、本書では「依存関係を整理しろ」ということを具体例を挙げて話していた。しかし普段は Ruby で書くことが多く、パッケージ・あるいはクラスに対する依存が不明確になりがちである。例えば require
した場合に定数はグローバルにロードされるため、あるファイル内で何に依存しているのかということは人間としても機械としても(ツールを用いても)不明瞭になりがちである。
require 'foo' require 'bar' Baz.new
例えば上記スクリプトでは Baz
について foo
で require
したのか、 bar
で require
したのか、あるいはこのファイルを実行する側で require
/ 定義されたのかわからない。そもそも Baz がオープンクラスされている可能性もあり、上記すべてで定義されている可能性もある。
一方で Go などであれば、別パッケージで定義されたものはパッケージ名から引かなければならず、依存は明示される。ツールにやさしい1。
package main import "fmt" func main() { fmt.Println("Hello") // fmt パッケージへの依存が明確 }
これを考えると Ruby において依存関係の整理をやろうとするのは難しいような気がする。RoR がそういったフレームワークでないという話もあるが。なお、本書の中では「アーキテクチャまで決めるようなフレームワークに依存するな」という趣旨の主張も記載されていた2。
また、別の大きな主張として「システムとしての詳細に依存するな」というのも挙げられる。具体的にはDBとかである。これに関しても「せやな〜」という気持ちはある一方でDBやミドルウェアの特性にひどく依存したシステムというのもあるような気がしていて、それをパッケージとしてまとめて覆い隠すのは少しむずかしいような気もした。これに関してはちょうど go でなんやかんや書く機会があるので実践してみようと思う。
他には、マイクロサービスへの言及もあり「サービスとして切り出しただけで依存が切れると思ったら大間違いだぞ」という話があってほっこりした。一方で「週次ビルド」についての言及もあり、時代を感じさせるものでもあった。
Rails におけるパフォーマンス検証パターン: action 抜けた後が遅いケース
TL; DR
- アクション抜けたあとは action の callback, rendering, rack middleware の世界
- おもそうなものを特定して抜こう
- bullet は重いぞ
事象
新機能開発中に、大量データ突っ込むか〜と思って突っ込んで開いたらまあ全然返ってこない。
はあ、まあそういうこともありますわよねと思って見ていたら、実はアクションから抜けるところまではそこそこの速度で終わることがわかった。1
調査
とゆーことは action 後に呼ばれる処理があやしい。 after_action はざっと確認した感じあんまりなさそうだった。2
ていうかそもそも rails log でも rendering xx sec と出ているのでレンダリングも終わっていることがわかった。
というわけでレスポンスを書き終わったはずなのにまだナニカをする層、つまり middleware があやしいということがわかる。
以下を参考に middleware ごとの経過時間を見ておく
http://blog.mirakui.com/entry/2012/04/03/slow-middleware
元コードは alias_method_chain を使っているので prepend に書き直してみた
増加時間が長いやつを探す。今回は bullet が悪そうだった。業務アプリなのでログは載せないでおく。
とりあえず disable すればいいかなと思って設定変更したらはやくなったことを確認。
bullet は本番ではオフになるので特に問題にならないはずだなーというところまで考えて調査終了。
オフラインリアルタイムどう書くE27の参考問題の実装例(Rust)
オフラインリアルタイムどう書くE27の参考問題「灯りと鏡」の実装例です。オフサイトだし Rust やるかあとおもって書きました。
問題 : http://nabetani.sakura.ne.jp/hena/orde27ligmir/
実装リンク集 : https://qiita.com/Nabetani/items/0b2f459ec128c89948f4
10/6 のイベント : https://yhpg.doorkeeper.jp/events/79705
NOTE: コードは末尾にも載せています。
問題自体はそんなに難しくなくて、素直にやれば素直に解けると思います……といいつつハマった点を2つ。
1つは、反射したらなんか、時計回りにまわるもんだと思っててあれー?ってなってました。これはただのアホでたぶん俺だけだと思う。
もう1つは、無限ループですね。どう示すか迷って、とりあえず座標 + 方角で HashSet つくって、入ってるか確認して、とやりました。一回通した後に「いや、これ各マスについてbitでフラグ立てればええやん」と思い直して今の実装に。おかげで sort する手間が省けていいです。
完全に Rust に不慣れなせいでいくつか困っています。
- char どうやって変換するのがいいんだろ( from_digit でやってる
- usize こんなに使ってていいんか? index 指定するために使ってるけど
- consume 扱いになるとめんどいから参照で取得してるところがあるんだけどそれでいいのかわからない
- 参照でもったあとリテラルの参照と比較するような書き方をするハメになっていてほんまか?という気持ちが
あと書いてて思ったんですが borrow 難しいですね……。なんかの struct にメソッドはやして破壊的にーとかやるとだいたい死ぬ。俺が悪い気はしている。Rust においてどういうふうに書くのがきれいなのかよくわからないなあと思いながらやっています。
最初に通すまで 2.5h くらいかかってますが Rust のリファレンスめっちゃ見てたのでRuby でやったら勝てた気がする。オンサイト時はおとなしく Ruby を使います。
use std::fmt; use std::char; use std::iter::FromIterator; struct Field { f: [[Cell; 5]; 5], y_idx: (usize, usize), } #[derive(Eq, PartialEq, Debug, Clone)] enum Direction { N, E, S, W, } impl Direction { fn dir_flag(d: &Direction) -> i32 { match d { Direction::N => 1, Direction::E => 2, Direction::S => 4, Direction::W => 8, } } } struct RayResult { from: (usize, usize), dir: Direction, stopped: bool, passed: [[i32; 5]; 5], } fn coor_to_char(coor: (usize, usize)) -> char { let idx = coor.0 + coor.1 * 5; match char::from_digit(10 + idx as u32, 36) { Some(s) => return s, None => panic!("Cannot convert coor: ({}, {})", coor.0, coor.1), } } impl Field { fn set(&mut self, cell: Cell, x: usize, y: usize) { if cell == Cell::Y { self.y_idx = (x, y); } self.f[y][x] = cell; } fn start(&self) -> Vec<char> { let mut res = RayResult { from: self.y_idx, dir: Direction::N, stopped: false, passed: [ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], ], }; res.passed[self.y_idx.1][self.y_idx.0] = res.passed[self.y_idx.1][self.y_idx.0] | Direction::dir_flag(&Direction::N); let mut f = res.stopped; while !f { // println!("now: ({}, {})", res.from.0, res.from.1); res = self.process(res); f = res.stopped; } let mut vec = Vec::new(); for (y, r) in res.passed.iter().enumerate() { for (x, f) in r.iter().enumerate() { if f != &0 { let c = coor_to_char((x, y)); vec.push(c) } } } return vec } fn process(&self, mut res: RayResult) -> RayResult { let coor = match res.dir { Direction::N => (res.from.0 as i32, res.from.1 as i32 - 1), Direction::E => (res.from.0 as i32 + 1, res.from.1 as i32), Direction::S => (res.from.0 as i32, res.from.1 as i32 + 1), Direction::W => (res.from.0 as i32 - 1, res.from.1 as i32), }; let oc = self.pick(coor.0, coor.1); if oc.is_none() { return RayResult { from: res.from, dir: res.dir, stopped: true, passed: res.passed, } } let c = oc.unwrap(); let new_dir = Cell::reflect(c, res.dir); let new_x = coor.0 as usize; let new_y = coor.1 as usize; let flag = Direction::dir_flag(&new_dir); if res.passed[new_y][new_x] & flag != 0 { return RayResult { from: (new_x, new_y), dir: new_dir, stopped: true, passed: res.passed, } } if c != &Cell::X { res.passed[new_y][new_x] = res.passed[new_y][new_x] | flag; } return RayResult { from: (new_x, new_y), dir: new_dir, stopped: c == &Cell::X, passed: res.passed, } } fn pick(&self, x: i32, y: i32) -> Option<&Cell> { if x >= 0 && x < 5 && y >= 0 && y < 5 { return Some(&self.f[y as usize][x as usize]) } return None } } #[derive(PartialEq, Debug)] enum Cell { X, // x: 光を吸収するマス。 D, // .: なにもないマス。光を通す。 R, // 1: 「╱」という向きの鏡。 L, // 0: 「╲」という向きの鏡。 Y, // Y: あなた。 } impl fmt::Display for Cell { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Cell::X => write!(f, "X"), Cell::D => write!(f, "D"), Cell::R => write!(f, "R"), Cell::L => write!(f, "L"), Cell::Y => write!(f, "Y"), } } } impl Cell { fn reflect(c: &Cell, dir: Direction) -> Direction { if c == &Cell::R { // println!("reflect by {}", c); // R == / return match dir { Direction::N => Direction::E, Direction::E => Direction::N, Direction::S => Direction::W, Direction::W => Direction::S, } } else if c == &Cell::L { // println!("reflect by {}", c); // L == \ return match dir { Direction::N => Direction::W, Direction::E => Direction::S, Direction::S => Direction::E, Direction::W => Direction::N, } } else { dir } } } fn new_field() -> Field { Field { f: [ [Cell::Y, Cell::X, Cell::X, Cell::X, Cell::X], [Cell::X, Cell::X, Cell::X, Cell::X, Cell::X], [Cell::X, Cell::X, Cell::X, Cell::X, Cell::X], [Cell::X, Cell::X, Cell::X, Cell::X, Cell::X], [Cell::X, Cell::X, Cell::X, Cell::X, Cell::X], ], y_idx: (0, 0) } } fn solve(input: &str) -> String { let row_strs = input.split("/"); let mut field = new_field(); for (y, row_str) in row_strs.enumerate() { for (x, c) in row_str.chars().enumerate() { let cell = match c { 'x' => Cell::X, '.' => Cell::D, '1' => Cell::R, '0' => Cell::L, 'Y' => Cell::Y, _ => panic!("Invalid input: {}", input), }; field.set(cell, x, y); }; } let tmp = field.start(); return String::from_iter(tmp) } fn test(input: &str, expected: &str) { let actual = solve(input); if actual != expected { println!("ng\n actual: {} \nexpected: {}", actual, expected); } else { println!("ok!"); } } fn main() { test( "x...x/.1.0./..0../.Y.../0..x.", "ghilnqs" ); test( "..Y../...../...../...../.....", "c" ); test( "..x../..Y../...../...../.....", "h" ); test( "..Y.x/..1x0/11.../....0/1..1.", "c" ); test( "....1/....Y/...../...../.....", "ej" ); test( ".10../x.1../x.1x./.Y.1./...0.", "bcghlq" ); test( "0.x10/00..x/x0x.0/....0/...Y1", "deinsx" ); test( "1.01./01Y.1/..1.1/..10./0.0..", "abcfgh" ); test( "x..../x1x../0...0/....Y/.1..0", "klmnot" ); test( "...../..10./.1Y1./.01../.....", "hilmnqr" ); test( "...../..10./x.11./...../..Y..", "hilmnrw" ); test( "...../x.10x/...../x.Y1x/.....", "himnqrs" ); test( "..010/...Y1/..0../0.x../.....", "defghij" ); test( "1.0../...../.0x../Y.1x./..1..", "abcfhkp" ); test( "...../101../0.0../..Y../.....", "fgklmqrv" ); test( "1.0../00.../.x..0/0.Y1./...10", "abcfghmr" ); test( "x101./1..../.Y.x./..01./.00.1", "bcghlmrs" ); test( "x11../x.x../.0.01/..x../...Y.", "bcglmnsx" ); test( "..1.0/x0.x./0.0../x...Y/.10.1", "cdehjmnot" ); test( "..x.0/.0.../1..0x/1..1./Y.00.", "klmnpqrsu" ); test( "0.1.0/.0.xY/0...0/01..1/x00.x", "cdehjmrwx" ); test( "...0./.0.0./..101/...10/..01Y", "mnpqrstwxy" ); test( "10..0/.Y.0./0..1./....x/000..", "abfghiklmn" ); test( "10..1/...../.1010/110.1/x..Yx", "lmnopqrstx" ); test( "110../....1/x1..x/0.0.0/....Y", "bcghlmrsty" ); test( "x.101/1..../..001/010Yx/..1.1", "cdehijmnos" ); test( "x.111/x10../...0./00.1x/x.Y.1", "ghklmnqrsw" ); test( "11.../....0/11..1/1.1../.Y..1", "fghijlmnoqv" ); test( "...x1/.1.0./11.1./.01../Y..x.", "cghiklmnpqru" ); test( ".0.../110x./11..0/01.x./..Y.x", "ghklmnopqrtw" ); test( ".01.0/.110x/0...0/.01Y./x.1x.", "cdeghilmnqrs" ); test( ".1100/..1.0/1.11Y/0..1./.0..0", "hijklmnopqrs" ); test( "1..00/..11./.100./1..Y1/.....", "abcdfhikmnps" ); test( "1.0../.11x0/.00.x/Y.10./.10x0", "abcfghklmpqr" ); test( "11110/11.../.x.../.0111/0.Y0.", "deijnorstwxy" ); test( "...1./.1.0x/10..0/0Y.11/.0.x0", "ghiklmnopqrst" ); test( "...10/x111./0x.11/.0.../0.0Y.", "dehijmnorswxy" ); test( ".1x../.x1.0/0x.x./x11.1/x0Y.1", "hijmoqrstvwxy" ); test( "x.x../x110./1.1.0/0.Y.1/0.00x", "hiklmnopqrstx" ); test( "...0./11.00/10..x/..0.1/Y0.10", "ghiklmnpqsuvwx" ); test( ".110./....0/x..../.0001/11.Y.", "cdfghijmnorstx" ); test( "1.00./....1/.1.../0...0/0..1Y", "abcfhkmpqrstwy" ); test( ".1.01/..x../..100/..Y../...01", "bcdgilmnoqrstvxy" ); test( "1...0/Y..../...../...../0...1", "abcdefjkoptuvwxy" ); test( "x1..0/1..0./.Yx../0...1/.0.1.", "bcdefghijklnopqrstvwx" ); test( "1...0/.1.0./..1../..01./Y0..1", "abcdefghijklmnopqrstuvwxy" ); }
オフラインリアルタイムどう書く E26 の実装例(Ruby)
問題 : https://cedretaber.github.io/doukaku/e26/ 実装リンク集 : https://qiita.com/Nabetani/items/0bcabb80bdcbc9b2ff52
負けました。
とりあえず当日中に書いたのはこんなの。
縦n列目については n + 1 列目にあるやつをうごかせば揃うなーというのは手で動かしてて気づいたので、それで最下段以外は揃えておく。
このあと最下段が揃った後に、最下段にパターンがあるかなーと思ってそれをなんか後から直せないかなというのが回答方針です。が、パターン見つかるわけないよねーということで敗北。
想定解法まであと2ステップほどひらめきが必要だったので、まあ惜しくもないですね。はい。
この後想定解法を実装し終えたらまた追記します。
追記: 2018-09-03 00:00頃
解けました。
なんかしらんが x と y を入れ違えてたりでIFの重要性を感じますね……。Coordinate とか作っときゃよかった(´・ω・`)
見どころはたぶん最初の行の全探索のやり方です。パターン生成するのがめんどかったので to_s(4)
で4進数表記したものから生成してます。ほかは面白いとこないかと。
@commands = [] n.to_s(4).chars.reverse_each.with_index do |c, idx| cnt = c.to_i @commands += Array.new(cnt) { [idx + 1, 1] } end # 6 なら 12 なので右端から1つめを2回、2つめを1回 まわすとする
WIP: ast でメソッドコールを探すやつを作り始めました + designing data-intensive application を読んでいます
本日は2本立てでお送りします(特にネタがないので小ネタというか終わってないネタを2つ合わせて記事にしとくか……という記事です。
ast でメソッドコールを探すやつを作り始めました
ほしくない?と思って作っています
使い方は何かと言うとある特定のメソッドの呼び出しを一覧にしたいぞ、というのがもとの欲求です。grep でもいいんですが、せっかく AST parser あるんだから使えばいいじゃん、ということでやってます。まだ NODE でとれるだけです。
grep よりもいい点としてレシーバや引数の情報ももとに検索できることでしょうか。そういう要望があるのかすげーあやしいのですが。あ、でも第二引数がはいってるかどうかをみることでオプション引数がない状態で呼ばれてないか、という確認はできますね。
インターフェースとして今は ruby コード書いてるけどそうじゃなくてなんかいい感じのクエリかけたらいいなーと思いつつ、特にアテがない状況。
テスト環境としては mtsmfm さんの docker image を採用。毎日 trunk でビルドしてくれるのでまだ安定版の存在しない RubyVM::AST のテストにはいいイメージですね。
https://hub.docker.com/r/mtsmfm/ruby-trunk/
designing data-intensive application を読んでいます
読み始めていますがまだ chapter 2 とかです。平易な英語でとても読みやすいのですが単純に英語であるだけで読むのがつらいらしくて全然進みません。すみません。
chapter 1 では、reliable, scalable, maintainable なアプリケーションについて話しています。アプリケーションの良さの指標として前述の3つを挙げて、それぞれについて解説していました。chapter2 では、まあ雑にいうと relational model vs document model という話をしていて、結構面白いです。mysql の JSON 型すら全く使ったことがないのでなんか使うとよさそうなところで使ってみたいですね。
relational model におけるテーブル設計と制約などは少しはノウハウがあるのでなんとなーくわかるのですが、document model においてデータの健全性をどう保てばいいのかよくわかりません。その健全性についても read 時のチェックと write 時のチェックだ、と対比されていましたが正直業務データとか突っ込むには不安が……。個人でやっててもあんまり痛い目みなさそうだし、ダメージが少なそうなところに突っ込むしかないのかなあ……とどう導入しようかいろいろ検討中です。
僕が途中で挫折しないように、感想とかを言える仲間を募集していますので twitter とかでお気軽にどうぞ。
【PR】転職ドラフトで転職してから1年が過ぎたので体験を振り返る
【こちらの記事は転職ドラフト体験談投稿キャンペーンに参加しています】
もう転職して1年1ヶ月になりますね。1年も前なので転職ドラフト自体も変わってはいるのでしょうが、体験談を投稿するとアマギフをくれるというので頑張って書こうと思います。
当時の僕は2社目に転職して1年が過ぎた頃でした。技術的に閉鎖的だった1社目から転職しており、当時の会社は技術面では特に問題はない一方で賃金が低いということに対してうっすら不満がありました。そんな中で某 podcast で「転職ドラフトの提示額をもとに翌年の給与の交渉をした」という話を聞いて自分でも試してみたのがきっかけでした。
指名自体は13件頂いていたようです。ありがたや。2017年頭くらいの僕がどんなだったかというと過去記事を参照するのがよさそうです。
箇条書きスキルセットはこんなん
- Ruby + Rails 業務で1年くらい
- jsをバニラ、jQueryで3年、Reactとか雑にはかけるけど redux とかの設計は無理
- その他 docker, crystal, php, go とかの経験をちょこちょこ
あんまり経験なさそうだけど、ECS使うとかDeepLearning本一通り実装したとか、そういうある程度の技術トレンドは追ってた感じがありますね。転職する際に有利に働いたんじゃないかな。
指名をもらったあとは、通常の面接が始まる感じですね。面接の内容は、1回目の転職活動と比べて技術レベルとかの話にスッと入っていけた印象はあります。といっても1回目の転職活動はほげナビとかそういうの経由だった1ので比較してよいのかは議論の余地がありそうです。たとえば最近の m○ffers とかと比べてどうなのっていうと、うーんどうなんでしょうね。2
ちなみに m○ffers は登録時にレジュメの添削(人力)があって、そういうところは後発なのもあって頑張ってるなーと思いました。
この後は、転職ドラフト経由での転職おめでとう品をもらうというイベントがあるくらいで他の転職サイトと変わらないのではないでしょうか。転職後すぐの頃は「ある程度マッチングした状態で始まるから楽」とゆーてたのですが、実際に面接官をする立場になってみるとぶっちゃけどの採用経路で入ってきた人でもあんまり変わらない気がしてきました。
なので「転職ドラフト経由でいった会社の面接がやりやすい」と感じていたのは単純な勘違いか、転職ドラフトに指名参加する会社ならだいたい僕がやりやすいという話なのかもしれません。たぶん後者かなあ。
というわけでつらつら書き連ねましたがこんなところでしょうか。転職記念で Apple ギフトカードをいただきまして、それで買った iPad pro はいまでも技術書3を読むのによく使っています。よいサービスだと思っておりますので今後もうまく発展していくといいなとこっそり応援しています。
(1452文字)