読者です 読者をやめる 読者になる 読者になる

相対的鉈

日記

久々に競プロをやったら「あーこれとりあえずこうすれば動くけどたぶんでかいケースで落ちそうだなー」と思う問題に多数当たってめちゃくちゃ悔しい思いをしたのでまたアルゴリズム系の勉強をしようと思った。

家にあるアルゴリズム系の本はAOJ*1の本だけ。ちゃんと(?)購入当時に挫折済み。
なのでAOJ本をやり直すことにしたのが3日前くらい。

当時はたしか水たまりを計算する問題が解けなくて挫折したのだけど*2今解いてみたらあっさり終わって「いつの間にこんなに……」という気持ちになった。解説の内容をうろ覚えながら頭にいれてたのでソレが残ってただけだと思うけど。特に地力があがったわけでもないだろう。いや上がってないわけではないと思うけど別に競プロっぽいことやってないしな……。

普段はRuby使いだしRubyで書くかと思ってたのだけど、どうも便利メソッドが揃いすぎてて卑怯な気がしてきた。卑怯というか学習目的なら不適切というか。なのでC#でEnumerableとか封印して書くか、と思ったらAOJのランタイムがmonoでめんどいことになるのもなーと思ってうじうじ。そこでC++書けばいいじゃんって気持ちになってきたので最近書いてる。

「Goがなんとなく好き。Rubyは魔法で切れたか切れてないかよくわかんないけどスパスパパシパシ切れる感じでそれはそれでステキなんだけど、Goは鉈でオラァっていって切り回していく感じがあって好き。書いた分だけ動くっていうプログラミングの原点に立ち戻れる気がする」みたいなことを前にいった記憶があるのだけど、C++を前にするとGoは日本刀くらいの切れ味がありますね。ほんものの鉈の無骨さを目の当たりにしている。
たぶん僕の書いてるC++がマクロもtemplateもautoも使ってないのでだいぶ無骨だっていうのはあるのですが。というかたぶんほぼCで書いてる気がする。便利にC++書けたら嬉しいよなって気持ちもあるけどたぶんそこへの道のりがかなり長そうなので尻込みしちゃう。あくまでも競プロ用、みたいなね。適当なバッチかいてたときにpowershellの文法があやしいのと同じ。複雑なのはC#書くけど書き捨てはpowershellで。仕事でならRubyだけど勉強なら(無骨な)C++で。

元鉈現日本刀のGoは書いた分だけ動くといってもfor rangeとか配列とSliceとかが言語機能として備わっててやっぱモダンなんだよなーという感じ。C++を書いてGoへの想いを強くしている。
AOJでは選べないけどyukicoderはGoとかRustが選べた気がするので、yukicoderでこの問題を問いていけばアルゴリズムの勉強になるよという記事(あるいは本)の登場が待たれますね。

*1:サイト 具体的にはALDSコース

*2:見直したら超序盤で笑う。もうちょい頑張れ

mrubyだと32個以上の必須引数をとれない

技術系日記 Ruby

www.amazon.co.jp

別に普段mruby使ってるわけでもないんですが『言語のしくみ』を読んでたら気になったので検証。

本の記載

タイプ3 Ax(25ビット) op(7ビット)

命令タイプ3はオペランド雨を全部1つにして25ビットのオペランド(Ax)として取り扱います。タイプ3の命令は、OP_ENTERの一つだけです。
(中略)
OP_ENTERはAxに指定されたビットパターンを見て、メソッドの引数チェックをします。OP_ENTERは25ビットのうち23ビットを5/5/1/5/5/1/1に分割して引数指定と解釈します。各ビットの意味を表2に示します。

ビット | 内容  
 5  | 必須引数の数
(以下略)
表2 OP_ENTERの引数指定 より一部抜粋

全体は1-3 バーチャルマシン、mrubyの命令を見てみる より。

思ったこと

必須引数の数が5ビットで管理されてるってことは 25 = 32 で 0〜31 までしかないはず。
32個の引数をとったら困るんじゃね?

検証

環境はこんな感じ。

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
$ bin/mruby --version
mruby 1.2.0 (2015-11-17)

さて、引数32個も書くの嫌なのでさくっと生成します*1

n = 32
args = (1..n).map { |e| "a#{e}" }
str = <<-EOS
def foo(#{args.join(', ')})
  puts true
end
foo(*Array.new(#{n}))
EOS

File.write('main.rb', str)

じゃあ検証してみましょう。rubyとmruby両方で実行します。

$ ruby generate.rb
$ cat main.rb
def foo(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32)
  puts true
end
foo(*Array.new(32))
$ ruby main.rb
true
$ bin/mruby main.rb
trace:
        [1] main.rb:1:in Object.foo
        [0] main.rb:4
ArgumentError: 'foo': wrong number of arguments (32 for 0)

たしかに引数チェックは5ビットで行われてるみたい。 32 for 0 だから0個の引数を期待してることがわかります。

31個だと両方共trueを出力します。 33個だと wrong number of arguments(33 for 1) になってることも確認。

じゃあ0個を期待してるらしいので実際0個にしたらどうなるの?というのが以下。

$ cat main.rb
def foo(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32)
  p a1 # 中身みたかったので変更
end
foo # 引数ゼロ個
$ ruby main.rb
main.rb:1:in `foo': wrong number of arguments (given 0, expected 32) (ArgumentError)
        from main.rb:4:in `<main>'
$ bin/mruby main.rb
nil

当然rubyでは引数の数が違ってエラー。mrubyだと通りました。やったね
ただし引数は全部nilなので特に意味はありません。

じゃあ引数の最大値は31個なのかというと違います。
表2を全部写すのがめんどかったので書いていませんが、オプション引数の数とかキーワード引数の数も管理されています。
キーワード引数はmruby 1.2.0では未対応なので使えません*2。しかしオプション引数ならば5ビットまで使えるのでもう少し頑張れそうです。

n = 31 # これで31 * 2 の62個まで
args = (1..n).map { |e| "a#{e}" }
args += (1..n).map { |e| "b#{e} = #{e}" }
str = <<-EOS
def foo(#{args.join(', ')})
  puts true
end
foo(*Array.new(#{n}))
EOS

File.write('main.rb', str)

これで実行するとちゃんと通るのでたぶん62個ですかね。

$ cat main.rb
def foo(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, b1 = 1, b2 = 2, b3 = 3, b4 = 4, b5 = 5, b6 = 6, b7 = 7, b8 = 8, b9 = 9, b10 = 10, b11 = 11, b12 = 12, b13 = 13, b14 = 14, b15 = 15, b16 = 16, b17 = 17, b18 = 18, b19 = 19, b20 = 20, b21 = 21, b22 = 22, b23 = 23, b24 = 24, b25 = 25, b26 = 26, b27 = 27, b28 = 28, b29 = 29, b30 = 30, b31 = 31)
  puts true
end
foo(*Array.new(31))
$ ruby main.rb
true
$ bin/mruby main.rb
true

ビット的には必須引数の数とオプション引数の数が隣あってるからオーバーフローしたりしないかなって思ったんですがしなかったです。まあそりゃそうだ

# generate.rbは上記のをn = 32に変更
$ bin/mruby main.rb
trace:
        [1] main.rb:1:in Object.foo
        [0] main.rb:4
ArgumentError: 'foo': wrong number of arguments (32 for 0)

rubyだと引数の最大値いくつなんだろ?というのも気になりますが、n = 2_000_000_000_000 でも大丈夫だったのでまあいいかと思って最大値は見てません ( ˘ω˘)

*1:時間的には変わんないというか遅い気もするけど気持ちの問題ですよ

*2:たぶん。表2中に未対応とあり、mirbでやってもSyntax Errorになったので

『オブジェクト指向設計実践ガイド』 実践編

Ruby 技術系日記

はじめに

この記事は『オブジェクト指向設計実践ガイド』の内容をもとにテストコード書いてたらよくわかんなくなったので助けてくれ、という内容です。だれか助けてください。コメントとかRe:記事とかのリアクションあると嬉しいです!

本の内容の抜粋

詳細は買って読んでね、ということでざっくりと。

www.amazon.co.jp

  • Dependency Injectionでテストしやすくしよう」
    • 依存オブジェクトの注入*1ってやつだよ
    • サンプルコード参照
  • 「送信コマンドメッセージはテストダブルを使うとよい」
    • コマンド: 副作用がある
    • 送信メッセージ: テスト対象のメソッドから呼ばれてる他のメソッド
    • 要は副作用があるなら副作用を呼び出せてるかはチェックすべきってこと

サンプルコード

サンプルコードの数値リテラルは適当な値なのでどんなギアだよって感じになってます。すみません。

# DependencyInjection 適用前
class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end
  
  def diameter
    rim * (tire * 2)
  end
end

class Gear
  attr_reader :ratio, :rim, :tire
  def initialize(ratio: nil, rim: nil, tire: nil)
    @ratio = ratio
    @rim = rim
    @tire = tire
  end

  def gear_inches
    ratio * Wheel.new(rim, tire).diameter
  end
end

Gear.new(ratio: 1, rim: 3, tire: 2) # 値はてけとー
# DependencyInjection 適用後
class Gear
  attr_reader :ratio, :wheel
  def initialize(ratio: nil, wheel: nil)
    @ratio = ratio
    @wheel = wheel
  end

  def gear_inches
    ratio * wheel.diameter
  end
end

Gear.new(ratio: 1, wheel: Wheel.new(3, 2)) # ここでDependency = Wheelを注入!

んで、これをやっておくとテストで Wheel クラスを参照しなくて済みます。『参照しなくて済む』というのは『GearのテストがWheelに依存しな』くなるということです。

# 旧
class GearTest < Minitest::Test
  # 受信メッセージのテスト
  def test_calculates_gear_iniches
    gear = Gear.new(ratio: 1, rim: 3, tire: 2)
    assert_equal(12, gear.gear_inches) # gear_inchesの中でWheel#diameterに依存
  end
end

# 新
class DiameterDouble # テストダブルの導入
  def diameter
    6 # 固定値で返すとバグらなくてあんしん
  end
end

class GearTest < Minitest::Test
  def test_calculates_gear_iniches
    gear = Gear.new(ratio: 1, wheel: DiameterDouble.new) # WheelじゃなくてDouble使う
    assert_equal(12, gear.gear_inches)
    # 実行してもWheelはどこにも必要とされてない = 依存していない ので変更につよい
  end
end

ほんとはこれ以降もダブルをどう扱うかとかが続くんですけどこのへんで。

実践してみた

タイトルが実践入門ですからね、実践していきます。
雰囲気を出すためにそれっぽいコードを書きます。記事をファイルに保存するような処理を考えましょう。
もらった文字列の1行目をタイトルとし、他を本文としてファイルに書き出す処理です。

vs クラスメソッドへの依存

DI前のコードがこちら。

module FileCreator
  def self.create(title, text)
    # いろんな処理とか
    File.write(title, text)
  end
end

class Article
  attr_reader :title, :body
  def initialize(body)
    @title, @body = body.split("\n", 2)
  end
  
  def save
    FileCreator.create(title, body)
  end
end

Article#save は明らかに FileCreator.craete に依存していますね。DIしましょう*2

DIする

DI版にしてみたのがこちら。

class Article
  attr_reader :title, :body, :creator
  def initialize(body, creator: FileCreator)
    @title, @body = body.split("\n", 2)
    @creator = creator
  end
  
  def save
    creator.create(title, body)
  end
end

でもこのコード、2点気になるところが。

  • creatorの値をほぼ変える予定がない場合にオプション引数取れる実装は、今後の変更に備えすぎてるように見える
    • KISSとかYAGNIとかそういう感じで
    • 変更する予定ないくらいに密結合になってるならテストでもそのままでいいような気がする
      • でも副作用は気になる……
    • 拡張性じゃなくてテスタビリティのための実装だと思えば仕方ない気もする……。
  • クラスをインスタンス変数にとるの、結構キモくないですか?
    • 「クラスもインスタンスもオブジェクトだよ。過度に区別すんな」という趣旨のことは同書の別の場面で言ってた気がする*3
      • あくまで「キモい」という感覚であって具体的に何が悪いのと言われるとよくわからない
        • Railsみたいな定数リロードがあると古いの残ったりはしそう
        • プロダクションでの実害は特にないと思う

DIはしないけど継承 + オーバーライドでなんとかしてみる

あるいはこういう感じにメソッドにしてみるとか。

class Article
  attr_reader :title, :body
  def initialize(body)
    @title, @body = body.split("\n", 2)
  end
  
  def save
    creator.create(title, body)
  end
  
  def creator
    FileCreator
  end
end

# テスト時にはオーバーライドしてどうにかする

class ArticleDouble < Article
  def creator
    Class.new { def self.create; end }
  end
end

うーん、まあ、ギリギリ……?テスト用のクラスができちゃうの、あんまり好きじゃないけど……。

スタブ使う

違うアプローチとして、アプリケーションコードをDIでよくするんじゃなくてスタブ使ってみるとか。

class ArticleTest < Minitest::Test
  def test_save
    article = Article.new(<<~EOS)
    たいとるでーす
    本文でーす
    2行目でーす
    EOS
    expected_args = [
      'たいとるでーす', 
      "本文でーす\n2行目でーす\n"
    ]
    creator = MiniTest::Mock.new.expect(:call, nil, expected_args)
    FileCreator.stub(:create, creator) do
      article.save
    end
    assert(creator.verify)
  end
end

これがすっきりしてていいけど、テスト側に思いっきり FileCreator って書かれてるのがうーん……。Dependency減らしたいんじゃなかったっけ?

まとまらないまとめ

  • クラス同士の依存が明らかな場合、スタブがよさそう
    • つまりFileCreator以外のcreatorが存在しそうにないとき
    • テストコードにクラス名がおもいっきり書かれるけど増えるかわかんないんだしいいんじゃないの
  • creatorが多数存在するとき
    • DIにするのがよさそう……なのか?
    • BaseArticleとFileArticleとDatabaseArticleと、みたいにして #creator をそれぞれ実装しちゃうのがよさそう
      • テストはArticleDoubleを作ってそこにスタブいれちゃう
      • オーバーライド版 + スタブ版って感じ
      • テスト用のクラスができちゃうのは仕方ない

あるいはなんかいい方法があるんでしょうか?実際のアプリケーションの成熟具合とも関連すると思いますがご意見お待ちしております 🙋


2017/02/06 04:24追記

クラスオブジェクトをインスタンス変数にもちたくない問題がー

これたぶん基本的には問題なくてArticleのインスタンスはFileCreatorよりも寿命が短いことが想定されるから。
DIにするのもそんなに高コストではないのでDI化しておいていいんじゃないのってのがいまの結論です

*1:依存性の注入って訳は好きじゃない

*2:書き終わってからFileCreator.createがtitleとbody取るっていうArticleに特化したものなのに名前が汎用的すぎるのでは、と思ったけど直してません

*3:UMLとかのあたりだったっけ?

2017年1月振り返り

月次振り返り

はやいものでもう2017年の1/12が終了したんですね*1。2016年振り返りで書いたように、定量的な指標を導入してみたのでそれを使って振り返りをやろうと思います。

定量的な指標というのはwakatimeで計測した時間です。wakatimeはコーディングしているときの時間を計測してくれるサービスです。
自分が最近何やってるかな、というのの振り返りにはもってこいですね。 プロジェクト名を書いても伝わらないので言語別の指標での振り返りを毎月やっていくことにします。

記録

2017年1月 コーディング時間: 3724分 言語比率 1〜5位

YAML         601 
Ruby         388
ERB          254
Go           233
JavaScript   201

雑感

記録から

えーと、アホみたいにYAMLをいじってますがこれはインフラをやっていたせいです。最近はAWSでDockerを走らせるには、というのをいろいろ試してます。CloudFormationの設定ファイルを書き、hakoの設定ファイルを書き、docker-compose.ymlを書き、circle.ymlを書き。試行錯誤をひたすら繰り返していたらこうなりました。得意言語はYAMLです(キリッ*2。ERBもひたすら多いですがこれも設定ファイルのテンプレートとしてERB使ってたからですね。 Rubyは、まあ普通にアプリケーション書いてたので妥当。
GoとJavaScriptは振り返り用のツールでGo + Vue.jsのwebアプリを作ったのでそれの影響ですね。

総コーディング時間が3724分 = 60時間ちょいなのは、なんか少ない気がする。
AWSの画面でイベント見つめたり、ドキュメント読んだり、どうするかミーティングしたりというのは平常より多めだった気がする。

あと日別で見ると完全にやる気を失っている日が何回かあって、なるほどという感じ。

f:id:hkdnet:20170201000344p:plain

こういうのをなくしていきたいのでアラートとかいれたほうがいいのかな。
「昨日n分しかコード書いてませんけど大丈夫ですか?」的な。

やったこと

ざっと思い出していきます

  • 振り返り用webアプリ書いた
  • google slides apiについて調べてある程度使えるようになった
  • cloudformationに詳しくなった
  • awsでのNW構築ができるようになった
  • hako + ECSあるいはEBを使ったデプロイができるようになった
    • hakoは割と難しいと思うのでなんか記事書きたい

こんなかなー。
やったことはほんとに忘れていくので週次くらいでサクサク書いてったほうがいいかもしれない。

*1:このフレーズを直近3日で使った人70%くらいいそう

*2:なお全然仕様把握してない

『ゼロから作るDeep Learning』を読んだ

読書 技術系日記

www.oreilly.co.jp

年末にやるかと思って買って、買った直後に3日ほど寝込んで結局昨日までかかってしまった。
といっても正月明けてから読んだのはたぶんトータル2時間程度で完全にサボってただけです。

内容は、ゼロから作るとあるだけあって、ほんとにゼロから作ったし、内容も初歩の初歩からでかなり助かった。大学入試以来数学から遠ざかっていたので懐かしさがある。ちなみにどうでもいいが大学入試の数学はセンターも二次もコケた記憶があるのであんまり思い出したくない。
と書いていたらそういえば経済学部では統計もミクロ経済も数式書いてたことを思いだしてきた。ウッ頭が……

1章のPython入門では、お決まりの環境構築から。
個人的にPythonには苦手意識があって、virtualenvだのなんだのというのがよくわかってない。
ただpyenv + condaでの環境構築はかなりスムーズにいって助かった。
numpyも初めて存在を知ったがこれは便利だなーという感じ。
本書に出てきた使い方以外にもたくさんありそうなので別途調べたほうがよさそう。

2〜6章は機械学習部分を作っていくぜーという感じ。
全結合レイヤのみで畳み込みは7章へ。
特に面白かったのはlearning rateの調整の話かな。
このへんはノートで板書をとっていたのだけど、途中でDropbox paperに変えた。
Tex記法で数式書けたりして楽しい。

8章はざっといろんな事例紹介という感じで読みとばしておしまい。
実際になにかやる段になったらもう一度読んだほうがいいかもしれない。

読み終わったので「よっしゃ、じゃあ俺も機械学習やってみっか」という気持ちではあるがMNISTのような使いやすい形になっているデータが世の中にどれくらいあるのだろうと思ってつらさを感じている。 まだCNNの感覚とかつかめてない気がするしChainerとかtensorflowとか聞いたことあるぜってやつらのチュートリアルに手をだすのがいいのかなー。

Slackのスレッド機能をどう使うか

日記

待望のSlackのThread機能きましたね。みなさんつかってますか。

Slackはチャットツールとして手軽でヨサがあったもののフロー型の情報でありその運用過程にはいろいろ問題がありました。
スレッド機能の登場は僕らの頭を悩ませていたそれらの課題をいくらか解決してくれるものと思われました。

  • 同時に複数の話題が流れたりするとどれへのリプなのかわからない
  • ちょっとタイミング逃すと返信しにくい
  • 議論が発散しがち*1

しかし、実際つかってみるとどーもなんか違う感じがあります……。

いや、デフォでスレッド内だけに発言されても……開くのめんどいし……。スレッドの開始位置どこだっけってなるし……。
なんかどうやってつかえばいいのかなあという感じでなかなか難しいなと個人的には思ってました。

そんなスレッド機能ですが今日試しに個人の作業ログとして残してみたらいい感じになりました。
こんな感じ(さっきやっつけで作ったイメージ図*2

f:id:hkdnet:20170126000122p:plain

いままで時間のログとか、やることリスト、やったことリストとかをSlackの分報内で実現しようとして流れて発散しがちだったんですが、これなら続くかも?と思ってます*3

スレッド機能をいい感じに運用できてるチームがありましたらどう使ってるか是非共有してほしいですね。
冒頭にも書きましたけど、みなさんつかってます?どうやって使うといい感じですか?

*1:ちなみに僕はこみ入った議論はチャットでは避けたほうがいい派です

*2:お仕事では分報という個人用チャンネルがあるのでそこでやってます。

*3:なお、過去そういって全然続かなかった例多数

RubyでEnumerableを条件Xを満たすものと満たさないものに排他的に分けたいんだけど

技術系日記 Ruby

どうするのがいいんでしょうか。
メモリとかパフォーマンスとかそういうのはあんまり気にしない前提です。

以下のサンプルコードでは対象のEnumerableは変数 arr に代入されているものとし、条件Xを満たすかどうかのメソッドは foo?(x) という名前であるとします*1

普通に書く

a = arr.select { |e| foo?(e) }
b = arr.select { |e| !foo?(e) }

排他的に分けたいのにブロックが2回書かれている。 条件に変更があった場合の変更漏れ、うっかり ! つけ忘れるなどのミスをやりそう。微妙。

selectとrejectする

a = arr.select { |e| foo?(e) }
b = arr.reject { |e| foo?(e) }

おんなじブロックを使うからまだミスが減る気はする。が、微差だろう。

引き算する

a = arr.select { |e| foo?(e) }
b = arr - a

排他的っぽい。割とよい

まとめてみる

tmp = arr.group_by { |e| foo?(e) }
a = tmp[true]
b = tmp[false]

hash[true] がそこはかとなくキモい気がする……。

調べてみる

a, b = arr.partition { |e| foo?(e) }

普通にあった*2

参考: https://docs.ruby-lang.org/ja/latest/class/Enumerable.html#I_PARTITION

結論

リファレンス見ろ

*1:どうでもいいけどpredicate methodと聞くと毎回predict思い出してついでにエッグベネディクトまで思い出してお腹がすく

*2:ちなみに記事書き始めてから見つけた