Slide 1

Slide 1 text

RE: PATTERN MATCHING IN RUBY Kazuki Tsujimoto Nomura Research Institute, LTD.

Slide 2

Slide 2 text

ࣗݾ঺հ • Twitter: @k_tsj • GitHub:k-tsj • E-mail: [email protected] • Rubyίϛολ • power_assert gem։ൃऀ • TRICK 2015 Matz Lisp Awardड৆

Slide 3

Slide 3 text

Matz Lisp Award '(#|'.b;module Scheme;T,R,I,C,K=Struct.new(:a,:d,:o){include Enumerable# |#)#| def initialize x,y=(),o=0.!;super c(x),c(y),o;K.empty?&&(R.delete a;R.delete d R<*a{ e x.d.d,I[f ?Hash[f.zip a]:{},n]} end; def l v;v.reverse.inject(() ){|a,i|T[i,a]}end;def t s,i;T[s, Array===i ? T[i[0]]:T===i&&i.o ? i:T[i],1]end;def v x,n=C; case x when T;case x.a when :LAMBDA; r x,x.d.a,n when :LET;e={};x. d.a.each{|i|e[i.a]=v i.d.a,n}; e x.d.d,I[e,n]when :IF;v(v(x .d. a,n)?x.d.d.a: (y=x.d.d.d)?y.a : (),n)when :DEFINE;Symbol. ===(u= x.d.a)?(n[u]=v x.d.d.a,n): ( n.[]=(u.a,r(x,u.d,n)))when :QUOTE;d =x.d.a;d==false ?:FALSE:(d== true)?:TRUE:d when :COND ; while x= x.d; (break e x.a.d, n if :ELSE== x.a.a||v(x .a.a,n))end; else f,*r=x.map {|i| v i,n}; f.call(*r);end when Symbol;n .[] x else x end end end;%w(CONS a,b T[a,b] PAIR? o T===o - *s s.inject:- LENGTH l l.to_a.size APPLY f,*n,s f.(*(n+(s||[]).map.to_a)) SYMBOL? o Symbol===o READ * t=""*1;begin;gets&&eval(t<<($_!=$/?$_:x),B);rescue(Object);retry;end MAP p,l l(l.map(&p)) NUMBER? o Numeric===o STRING? o String===o NOT o 0.!.==o ERROR *s fail(s*"\s") EOF-OBJECT? o o==() DISPLAY o puts(o) SET-CDR! p,o p.d=o SET-CAR! p,o p.a=o NULL? o o==() EQ? a,b a.equal?b * *s s.inject:* MIN *s s.min LIST *s l(s) CAR p p.a CDR p p.d).each_slice(3){|t|eval'_h[:"%s"]=->%s{%s}'%t}end;# |# (DEFINE (FACT N) . ( (IF (EQ? N . (1)) . ( 1 [* N . ((FACT (- N . (1))))])))) (DISPLAY (FACT 6))

Slide 4

Slide 4 text

TokyoRubyKaigi11 Speaker’s Award • RubyϋοΫͷ͖͔͚ͬͰ৆ • ੨໦ ๆ࿠͞Μ • Rubyιʔείʔυ׬શղઆ(RHG)Λ͸͡Ίɺ ֤छaamineϓϩμΫπ͔Βଟ͘ͷ͜ͱΛֶ͹ ͤͯ΋Βͬͨ

Slide 5

Slide 5 text

“PATTERN MATCHING IN RUBY” • ࡳຈRubyձٞ 2012 • RubyͷύλʔϯϚον࣮૷Ͱ͋Δpattern- match gemͷػೳΛ঺հ

Slide 6

Slide 6 text

ύλʔϯϚονͱ͸ • ଟॏ୅ೖͷڧԽ൛ͷΑ͏ͳ΋ͷ match([1, [2, 3]]) { with(_[a, _[b, c]]) { } } a, (b, c) = [1, [2, 3]]

Slide 7

Slide 7 text

ύλʔϯϚονͱ͸ • ಛʹ֊૚ߏ଄Λ࣋ͬͨσʔλͷॲཧͰ༗ޮ • ੺ࠇ໦ • ಛఆͷύλʔϯͷͱ͖ʹ໦Λճసͤͯ͞ਂ͞ΛҰఆʹอͭ Z Y B C D [ E Y B E Z D C [

Slide 8

Slide 8 text

ύλʔϯϚονͱ͸ • ύλʔϯϚον͋Γ • ύλʔϯϚονͳ͠ match(l, v, r) { with(_[R.(R.(a, x, b), y, c), z, d]) { R[B[a, x, b], y, B[c, z, d]] } } case when l.kind_of?(R) && l.l.kind_of?(R); R[B[l.l.l, l.l.v, l.l.r], l.v, B[l.r, v, r]] end

Slide 9

Slide 9 text

pattern-matchͷఏڙ͢Δػೳ • ଞݴޠͰ࣮૷͞Ε͍ͯΔػೳͷϋΠϒϦου • Scala • ೚ҙͷந৅σʔλܕͱͷϚον • Scheme • and/or/not • ܁Γฦ͠ • Mathematica • ਖ਼نදݱ

Slide 10

Slide 10 text

೚ҙͷந৅σʔλܕͱͷϚον • ArrayΛArrayͱͯ͠Ϛον match([0]) do with(Array.(a)) { a } #=> 0 # or with(_[a]) { a } #=> 0 end

Slide 11

Slide 11 text

೚ҙͷந৅σʔλܕͱͷϚον • StringΛEMailͱΈͳͯ͠Ϛον match("[email protected]") { with(EMail.("foo", domain)) { domain } } #=> "example.com"

Slide 12

Slide 12 text

೚ҙͷந৅σʔλܕͱͷϚον • StringΛEMailͱΈͳͯ͠Ϛον match("invalid e-mail address") { with(EMail.("foo", domain)) { domain } } #=> NoMatchingPatternError match("[email protected]") { with(EMail.("foo", domain)) { domain } end #=> NoMatchingPatternError

Slide 13

Slide 13 text

೚ҙͷந৅σʔλܕͱͷϚον • StringΛEMailͱΈͳͯ͠Ϛον match("[email protected]") { with(EMail.("foo", domain)) { domain } } EMail.deconstruct("[email protected]")

Slide 14

Slide 14 text

೚ҙͷந৅σʔλܕͱͷϚον • StringΛEMailͱΈͳͯ͠Ϛον class EMail def self.deconstruct(value) parts = value.to_s.split(/@/) if parts.length == 2 parts else raise PatternMatch::PatternNotMatch end end end

Slide 15

Slide 15 text

and(&)/or(|)/not(!) • ύλʔϯͷ૊Έ߹Θͤ • ಛʹand͸ม਺΁ͷ୅ೖͱ৚݅ൺֱΛ·ͱΊ ͯߦ͏ͨΊʹΑ͘ར༻͢Δ match([0, [1]]) do with(_[a & Integer, ! (_[2] | _[3])]) { a } #=> 0 end

Slide 16

Slide 16 text

܁Γฦ͠ match([[0, 1], [2, 3]]) { with(_[_[a, b], ___]) { [a, b] #=> [[0, 2], [1, 3]] } # or with(_[*_[a, b]]) { [a, b] #=> [[0, 2], [1, 3]] } }

Slide 17

Slide 17 text

ਖ਼نදݱ • ਖ਼نදݱ૬౰ͷ܁Γฦ͠ͳͲ match(["a", "b", 0, "c", "d"]) { with(_[*i, Integer, *j]) { [i, j] #=> [["a", "b"], ["c", "d"]] } }

Slide 18

Slide 18 text

࣮ݧతͳػೳ(Hash) • JavaScriptͷDestructuring Assignmentྨࣅ match({a: 0, b: 1}) do with(Hash.(:a, b: Integer)) do a #=> 0 end end

Slide 19

Slide 19 text

࣮ݧతͳػೳ(<<) • ΦϒδΣΫτΛ෼ղͯ͠ม׵͔ͯ͠Βར༻͢ Δͱ͍͏සग़ύλʔϯΛָʹॻ͖͍ͨ a, b, c = str.scan(/re/) b = b.to_i match(str.scan(/re/)) { with(_[a, b << :to_i, c]) { } }

Slide 20

Slide 20 text

࣮ݧతͳػೳ(assert_pattern) • ΦϒδΣΫτͷߏ଄ΛνΣοΫ • σόοά࣌ʹར༻ ary.assert_pattern('_[Integer, /=/, ___]') # OK: ary = [0, "a=b", "c=d"] # NG: ary = [1, "x"]

Slide 21

Slide 21 text

ར༻ྫ - power_assert gem - • ςετࣦഊ࣌ʹςετίʔυͷ֤ࣜΛग़ྗ͢ ΔͨΊͷϥΠϒϥϦ assert { ary.include?(3) } | | | false [0, 1, 2]

Slide 22

Slide 22 text

ར༻ྫ - power_assert gem - • දࣔҐஔ(ܻ)Λಛఆ͢ΔͨΊʹɺRubyͷίʔ υΛRipperͰύʔε # assert { [0, 1, 2].find {|i| i.odd? } == 3 } [:program, [[:method_add_block, [:method_add_arg, [:fcall, [:@ident, "assert", [1, 0]]], []], [:brace_block, nil, [[:binary, [:method_add_block, [:call, [:array,ɹ[[:@int, "0", [1, 10]], [:@int, "1", [1, 13]], [:@int, "2", [1, 16]]]], :".", [:@ident, "find", [1, 19]]], [:brace_block, [:block_var, [:params, [[:@ident, "i", [1, 26]]], nil, nil, nil, nil, nil, nil], false], [[:call, [:var_ref, [:@ident, "i", [1, 29]]], :”.", [:@ident, "odd?", [1, 31]]]]]], :==, [:@int, "3", [1, 41]]]]]]]]

Slide 23

Slide 23 text

ར༻ྫ - power_assert gem - • ॳظ࣮૷Ͱpattern-matchΛར༻ match(sexp) do with(_[:program, _[_[:method_add_block, _[:method_add_arg, _[:fcall, _[:@ident, assertion_method.to_s, _]], _], _[Or(:brace_block, :do_block), _, ss]]]]) do ss.flat_map {|s| extract_idents(s) } end ...

Slide 24

Slide 24 text

ར༻ྫ - ICFP Programming Contest 2014 - • GitHub:NaCl-Ltd/yarunee-2014 • ίϯύΠϥΛ࣮૷ def compile(e, env, is_tail = false) match(e){ ... [ [:+, :ADD], [:-, :SUB], [:*, :MUL], [:/, :DIV], [:"=", :CEQ], [:>, :CGT], [:>=, :CGTE], ].each do |sym, opname| with(_[sym, ex, ey]){ compile(ex, env) + compile(ey, env) + Gcc.new([Op[opname]]) } end

Slide 25

Slide 25 text

ར༻ྫ - open-uri - • πϦʔҎ֎ͷར༻ྫ • ݱࡏͷ࣮૷ • ύλʔϯϚονΛ࢖ͬͨ৔߹ mode, _, rest = OpenURI.scan_open_optional_arguments(*rest) options = rest.shift if !rest.empty? && Hash === rest.first match(OpenURI.scan_open_optional_arguments(*rest)) do with(_[mode, _, _[options & Hash]]) do end end

Slide 26

Slide 26 text

ઃܭͷϙΠϯτ • ಺෦DSLͱͯ͠ͷύλʔϯϚονػೳ • pattern-matchҎ֎ʹ΋ϥΠϒϥϦ͕͋Δ

Slide 27

Slide 27 text

ઃܭͷϙΠϯτ • GitHub:hayeah/matchmaker • GitHub:jdantonio/functional-ruby c = Case.new { of(1) { :a } of(2) { :b } of(3) # { true } by default } c.match(1) defn(:greet, _) do |name| "Hello, #{name}!" end

Slide 28

Slide 28 text

ઃܭͷϙΠϯτ • GitHub:todesking/patm • GitHub:egison/egison-ruby class A define_matcher :match1 do|r| p = Patm r.on [:x, p._1, p._2] do|m| [m._1, m._2] end end end A.new.match1([:x, 1, 2]) match_all([1, 2, 3]) do with(List.(*_hs, _x, *_ts)) do [hs, x, ts] end end

Slide 29

Slide 29 text

ઃܭͷϙΠϯτ • Rubyͷݴޠ࢓༷ʹύλʔϯϚονΛೖΕΔʹ ͋ͨͬͯͷproof of conceptʹͳΕΔ͜ͱΛ໨ ࢦ͢ • ೚ҙͷΦϒδΣΫτʹର͢ΔϚον • ࣮૷ͷૉ௚͞ΑΓݟͨ໨(ॻ͖΍͢͞)Λ༏ઌ

Slide 30

Slide 30 text

ϚονॲཧͷશମͷྲྀΕ • Ϛον༻ͷπϦʔͷߏங • ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • Refinements • ϝλϓϩάϥϛϯά • πϦʔͱΦϒδΣΫτͷϚον • callcc • Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ϝλϓϩάϥϛϯά

Slide 31

Slide 31 text

Ϛον༻ͷπϦʔͷߏங _[_[a, ___], 0] %FDPOTUSVDUPS "SSBZ %FDPOTUSVDUPS "SSBZ 7BSJBCMF B 2VBOUJpFS 7BMVF

Slide 32

Slide 32 text

ϝλϓϩάϥϛϯά • ม਺ఆٛΛ
 ΤϛϡϨʔτ • BasicObject.new. instance_evalͱ method_missing ͷ૊Έ߹ΘͤͰ ະఆٛࣝผࢠͷ ৘ใΛर͏ match(obj) { with(a) { ... } } self # self Context

Slide 33

Slide 33 text

ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • .() • callϝιουݺͼग़͠ͷߏจ౶ҥ • Deconstructorͷੜ੒ʹར༻ prc = -> { 0 } prc.() match("[email protected]") do with(EMail.("foo", domain)) { domain } end

Slide 34

Slide 34 text

ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • splat(*) • to_aϝιουͰಈ࡞ΛΧελϚΠζ • “*a” Λ “a, ___” ಉ౳ʹѻ͏Α͏ʹ͍ͯ͠Δ class C def to_a; [0, 1]; end end p(*C.new) #=> [0, 1] class Pattern def to_a [self, PatternQuantifier.new(0, true)] end end

Slide 35

Slide 35 text

ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • ಥ͖٧Ίͨྫ • Matz Lisp Award (DEFINE (FACT N) . ( (IF (EQ? N . (1)) . ( 1 [* N . ((FACT (- N . (1))))])))) define(fact(n).call(IF(eq? n.call(1)).call(1[*n.call(fact(-n.call(1)))])))

Slide 36

Slide 36 text

Refinements • ౰ॳ͸ϞϯΩʔύον͍͕ͯͨ͠#call͕Rack ͱিಥ • Refinements࠾༻ʹର͢Δݒ೦ • ࢓༷͕ྲྀಈత • JRuby • Φϓγϣφϧػೳͱͨ͠

Slide 37

Slide 37 text

Refinements • ରԠ͸೉͘͠ͳ͍ • refineΛఆٛ + using͸ݺ΂Δ͚࣌ͩݺͿ • EOLͷ΋ͷ΋ؚΊͯ෯޿͍؀ڥͰಈ࡞ • 1.9.3/2.0.0-p648/2.1.10/2.2.5/2.3 module def refine(klass, &blk) klass.class_eval(&blk) end end using PatternMatch if respond_to?(:using, true)

Slide 38

Slide 38 text

πϦʔͱΦϒδΣΫτͷϚον %FDPOTUSVDUPS "SSBZ %FDPOTUSVDUPS "SSBZ 7BSJBCMF B 2VBOUJpFS 7BMVF "SSBZ "SSBZ match([[2, 1], 0]) { with(_[_[a, ___], 0]) {} }

Slide 39

Slide 39 text

callcc • ϓϩάϥϜͷ࣮ߦঢ়ଶΛอଘ͓͍ͯͯ͠ޙͰ ࠶։Ͱ͖Δػೳ • ࣮ݧతʹόοΫτϥοΫΛαϙʔτ͢ΔͨΊ ʹར༻ • RubyਵҰͷࠇຐज़

Slide 40

Slide 40 text

callcc࡟আΛ८Δ ίϛολؒͷ߈๷

Slide 41

Slide 41 text

callccಋೖͷܦҢ • ౰ॳ͸Rubyͷଟॏ୅ ೖͱ߹Θͤͯύλʔϯ ଆͷ”*”͸1͚ͭͩΛ૝ ఆ • πϦʔΛ୯७ʹ࠶ى తʹτϥόʔεͯ͠ ॲཧ͢ΔΑ͏ʹ࣮૷ • ਖ਼نදݱͷαϙʔτʹ ΑΓͦͷલఏ่͕Εͨ match([[0, 0], [0, 1]]) { with(_[_[*a, *_], _[*b, *_]], guard { a == b }) { a #=> [0] } }

Slide 42

Slide 42 text

callccಋೖͷܦҢ • ར༻ස౓Λߟ͑ͯ ָʹ࣮૷Ͱ͖Δ͜ ͱΛॏࢹ • ϝΠϯϩδοΫͷ มߋෆཁͰޙ෇͚ Ͱ࣮૷Ͱ͖ͨ • callccศར class Pattern module Backtrackable def match(vals) matched = super if root? and not matched and not choice_points.empty? restore_choice_point end matched end def repeating_match(vals, is_greedy) super do |vs, rest| cont = nil if callcc {|c| cont = c; yield vs, rest } save_choice_point(cont) true else false end end end end ... prepend Backtrackable end

Slide 43

Slide 43 text

Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ม਺ͷΑ͏ʹΞΫηεͰ͖ΔΑ͏ʹ͢Δͨ ΊͷϝιουΛఆٛ • طଘͷϝιουͱͷিಥΛආ͚ΔͨΊʹઐ ༻ͷModuleΛ࡞ͬͯಛҟΫϥεʹprepend m = QuasiBindingModule.new do @stacks = ::Hash.new {|h, k| h[k] = [] } end obj.singleton_class.class_eval do prepend m end

Slide 44

Slide 44 text

Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ΤϛϡϨʔγϣϯͷݶք • ϞϯΩʔύονͤ͟ΔΛಘͳ͍ • ϨΩγΧϧείʔϓʹͳΒͳ͍ • ҎԼͷProc͕ৗʹ0Λฦ͢Θ͚Ͱ͸ͳ͍ • ಛҟϝιου͕ఆٛͰ͖ͳ͍ΦϒδΣΫτ(nilͳͲ)Λ selfʹͯ͠ϚονͰ͖ͳ͍ match(0) { with(a) { -> { a } } }

Slide 45

Slide 45 text

·ͱΊ • ύλʔϯϚονͷRuby΁ͷಋೖʹ޲͚ͯ proof of conceptͱͳΔΑ͏ͳgemΛ࡞੒͠ ͨ • ҰޱʹύλʔϯϚονͱ͍ͬͯ΋ػೳʹ͸ό ϦΤʔγϣϯ͕͋Δ • Կ͕͋Ε͹ศརͳͷ͔ੋඇٞ࿦Λ