( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

「関数型Ruby」という病(7) - Elixir's Pipe operator |> in Ruby

最近Elixirが人気ですよね。Erlang VM上でOTPの恩恵を受けながら簡潔な記法で並行処理を書ける言語ということで話題になっていますな? Elixirは関数型プログラミングのエッセンスを取り入れていると言われており、そのひとつにPipe演算子(|>) がある。

Pipe演算子(|>)とは何かというと、左辺の値を右辺の関数の第1引数に適用する演算子

iex> [1, [2], 3] |> List.flatten()
  [1,  2,  3]


上記のコードは、左辺の[1, [2], 3] を 右辺の List.fatten(list) の引数として渡す。 このPipe演算子は、Stream モジュールなどと合わせて利用するとデータが左から右へ流れている模様をコードとし視覚化することができるという利点があるっぽい(感じろ)。

iex(16)> f = fn a -> IO.puts "f(#{a}) : #{a+1}"; a ; end
#Function<6.90072148/1 in :erl_eval.expr/5>

iex(17)> g = fn a -> IO.puts "g(#{a}) : #{a*2}"; a * 2 ; end
#Function<6.90072148/1 in :erl_eval.expr/5>

iex(18)> 1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3)
f(1) : 2
g(1) : 2
f(2) : 3
g(2) : 4
f(3) : 4
g(3) : 6
[2, 4, 6]


1..10 |> Stream.map(f) |> Stream.map(g) |> Enum.take(3) というコードで、1から10のStreamに対してlazyに関数fとgを順番に適用しながら3つの要素を取り出すという様を素直に表現できていますね?(思え)

さて、そんな便利なパイプ演算子ですが、実は2年ほど前に作ったlambda_driver.gemに既に実装されていたりする。

Rubyでは中置演算子を独自に定義することはできないので、 Object クラスに |> というメソッドを生やすことで実現しよう(全角ェ)。 素朴な実装はこうだ

class Object
  def |>(f = nil)
  puts f
    if block_given?
      yield self
    else 
      f.call(self)
    end
  end

  alias_method "|>", :>=
end


さて、この全角の |> を使って、上記のElixirのコードをRubyで書いてみるとどうなるか?

irb(main):059:0> f = ->(a){ puts "f(#{a}) : #{a + 1}" ; a + 1}
=> #<Proc:0x007ffb8d8b2348@(irb):59 (lambda)>

irb(main):060:0> g = ->(a){ puts "g(#{a}) : #{a *2}" ; a * 2}
=> #<Proc:0x007ffb8d860a98@(irb):60 (lambda)>

irb(main):061:0>  (1..10).|>(&:lazy).|>{|x| x.map(&f) }.|>{|x| x.map(&g) }.|>{|x| x.take(3)}.|>(&:force)
f(1) : 2
g(2) : 4
f(2) : 3
g(3) : 6
f(3) : 4
g(4) : 8
=> [4, 6, 8]


なんというか、すごく……ダサいですね……。

というか、Rubyだったら素直にこう書いた方がいい

irb(main):143:0> (1..10).lazy.map(&f).map(&g).take(3).force
f(1) : 2
g(2) : 4
f(2) : 3
g(3) : 6
f(3) : 4
g(4) : 8
=> [4, 6, 8]


ごくごくまれに、左辺値がObjectとかでrevapplyを使いたくなることもなきにしもあらずだが、そういう場合でも大抵は Object#try で事足りる。

結論 : Rubyには必要ないのでは?

表参道.rb #4で「本当は怖い オープンクラスと Duck Typing」というLTをやった話

スライドです

f:id:yuroyoro:20150904130822p:plain
本当は怖いオープンクラスとDuckTyping - 表参道.rb #4

まぁたいした話じゃないんですが、マッドマックスの画像をスクリーンに大写しできたのでその点だけで個人的には満足しています

「型を讃えよ」

Rspecでfailするとデスメタルが流れるようにした

このような事があったので自動化した。
Mac限定。


こんな感じ。successだとレベルがアガる。

事前にbash-itunes というコマンドラインツールを入れておく。
iTunesを日本語で使ってる場合、patchを当てる必要がある。
こちらを山椒

コードはこれな。

class PlayItunesReporter
  attr_accessor :success_track, :failure_track
  def initialize(options = {})
    @success_track = options[:success]
    @failure_track = options[:failure]
  end

  def dump_summary(notification)
    return unless notification.examples.length > 0

    if notification.failed_examples.length == 0
      play!(success_track)
    else
      play!(failure_track)
    end
  end

  def play!(track)
    `itunes play "#{track}"`
  end
end


RSpec.configure do |config|

  itunes = PlayItunesReporter.new(success: "レベル・アップ", failure: 'Nemesis')
  config.reporter.register_listener itunes, :dump_summary
end

正直、曲は好きなの使えばいい。なんならこれでもいい

inspired by : コンパイル中に音楽を流せる sbt プラグインを作りました。 - tototoshi の日記

self.send(pred)がtrueならばselfを、そうでないならnilを返すメソッド

何をいっているのかというと、こういうことです


class Object
  def filter(&pred)
    (yield self) ? self : nil
  end
end

書いてみた。

foo".filter(&:present?) # => "foo"
"".filter(&:present?)   # =>  nil


ようは、 str.present? ? str : other みたいなやつを書きやすくするためのものです

str = "hoge" 
str.filter(&:present?) || "fuga" # => "hoge"

str = "" 
str.filter(&:present?) || "fuga" # => "fuga"

よい名前が思い浮かばなかった(´・ω・`)

8/29追記

ActiveSupportにObject#presenceというのがあるそうだ。present?だけならこれで充分。自分は、任意のlambdaを渡したい場合があるのでこれも無駄にはならない、はず。


8/29さらに追記

というか、ずいぶん前に自分で既に書いてgemにしてあったし俺は一体何をやっているんだ……。
おそらく上位存在からの記憶操作が行われた可能性がある……!!

yuroyoro/lambda_driver · GitHub

rspecの--tagオプションを利用して任意のコマンドライン引数をspec側に渡すという邪悪なhack

今まで、rspecコマンドでは任意の引数を渡すことはできなかったので、環境変数経由で引き渡すという方法をとっていた。
( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

MY_OPT1=true rspec spec/my_spec.rb

環境変数で渡すのはダルいのでなんとかしたいと思い、`--tag`オプション経由で値を渡すというダーティなhackを書いた。

仕掛けは、helper.rbなどで、以下のように`Rspec.world.filter_manager.incusions`から引数を切り出してクラス変数に保持しておくようなアレをホゲる。

helper.rb

class MyOptions
  class << self
    OPTION_KEYS = [:my_opt1, :my_opt2]
    attr_accessor :options
    def parse(world)
      @options = world.filter_manager.inclusions.slice(*OPTION_KEYS)
    end
  end
end

RSpec.configure do |config|

  # filterをonに
  config.filter_run :focus => true
  config.run_all_when_everything_filtered = true

  # --tagから独自の引数を切り出して保持しておく
  MyOptions.parse(RSpec.world)
end

あとは、`MyOptions.options[:my_opt1]`のように参照できる。

my_options_spec.rb

require File.join(File.dirname(__FILE__), 'helper')

describe "MyOptions" do
  subject { MyOptions.options }

  it { should include(:my_opt1) }
  it { should include(:my_opt2) }
end


実行すると、`--tag`経由で引数が渡っていることが確認できる。

$ rspec spec/my_options_spec.rb -t my_opt1 -t my_opt2
Run options: include {:focus=>true, :my_opt1=>true, :my_opt2=>true}

All examples were filtered out; ignoring {:focus=>true, :my_opt1=>true, :my_opt2=>true}

MyOptions
  should include :my_opt1
  should include :my_opt2

Top 2 slowest examples (0.0029 seconds, 100.0% of total time):
  MyOptions should include :my_opt1
    0.00205 seconds ./spec/my_options_spec.rb:6
  MyOptions should include :my_opt2
    0.00085 seconds ./spec/my_options_spec.rb:7

Finished in 0.0034 seconds
2 examples, 0 failures


`--tag`渡さないとfailする。

$ rspec spec/my_options_spec.rb
Run options: include {:focus=>true}

All examples were filtered out; ignoring {:focus=>true}

MyOptions
  should include :my_opt1 (FAILED - 1)
  should include :my_opt2 (FAILED - 2)

Failures:

  1) MyOptions should include :my_opt1
     Failure/Error: it { should include(:my_opt1) }
       expected {} to include :my_opt1
       Diff:
       @@ -1,2 +1 @@
       -[:my_opt1]

     # ./spec/my_options_spec.rb:6:in `block (2 levels) in <top (required)>'

  2) MyOptions should include :my_opt2
     Failure/Error: it { should include(:my_opt2) }
       expected {} to include :my_opt2
       Diff:
       @@ -1,2 +1 @@
       -[:my_opt2]

     # ./spec/my_options_spec.rb:7:in `block (2 levels) in <top (required)>'

Top 2 slowest examples (0.00381 seconds, 100.0% of total time):
  MyOptions should include :my_opt1
    0.00282 seconds ./spec/my_options_spec.rb:6
  MyOptions should include :my_opt2
    0.00099 seconds ./spec/my_options_spec.rb:7

Finished in 0.00432 seconds
2 examples, 2 failures

Failed examples:

rspec ./spec/my_options_spec.rb:6 # MyOptions should include :my_opt1
rspec ./spec/my_options_spec.rb:7 # MyOptions should include :my_opt2

ぼくのかんがえたさいきょうのGit Repository Browser: Gitterb をRuby2.1.2/Rails4にupgradeしてDockerImage作った話

3年ほど前に、GitterbというGitリポジトリのコミットログを可視化するツールを作った。


このアプリケーションはRuby1.9/Rails3.2 で書かれていて、今となってはもう動かないので、Ruby2.1/Rails4へupgradeした。

デモサイトはこちら http://gitterb.yuroyoro.net/


依存しているGritというRubyからGitリポジトリをホゲるGemがRuby2系では動かないので、libgit2のRubyバインディングであるRuggedに移行している。

あと、せっかくなのでCentOS7で動くDockerImageを作った。Docker Hubにおいてある。

以下のようにdocker pullした後にrunすると、port3000でサンプルが起動する。

docker pull yuroyoro/gitterb
docker run -d -p 3000:3000 -t yuroyoro/gitterb


macでboot2dcoker使ってる人は、port forwardingしてくだされ。

ssh -N -L 3000:127.0.0.1:3000 docker@localhost -p 2022

( ꒪⌓꒪) あばばばばばばばば

オマエらはもっとObject#tryの便利さについて知るべき

  arr = [
    ["foo", "", "bar"], 
    nil,
  ].sample
  
  arr.try(:reject, &:blank?) #=> [“foo”, “bar”]
  • Object#tryはnil-safeなcallとして使える
  • blockを取るメソッドのsymbolを渡した場合に、第二引数にprocを渡しても動作する

activesupport.gem
tryと関数合成は本体に入れて欲しい