Re: Pattern Matching in Ruby

303dd57f37d64288bb4f0336332a8882?s=47 k_tsj
May 28, 2016

Re: Pattern Matching in Ruby

303dd57f37d64288bb4f0336332a8882?s=128

k_tsj

May 28, 2016
Tweet

Transcript

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

    LTD.
  2. ࣗݾ঺հ • Twitter: @k_tsj • GitHub:k-tsj • E-mail: kazuki@callcc.net •

    Rubyίϛολ • power_assert gem։ൃऀ • TRICK 2015 Matz Lisp Awardड৆
  3. 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<<self)end;def-@;T[:-,self]end;alias == equal?;def call i;Scheme.t self,i end def to_a;[T[:*,self]]end;def c o;Array===o ?Scheme.l(o):o end;def each&b;b.(a) d&&d.each(&b)end},[],e=Struct.new(:t,:u){def[]=k,v;t[k]=v end;def[]i;t.fetch(i ){u[i]}end},e[_h={}],[];def Object.const_missing i;i end;class::Fixnum;undef[] def[]i;T[self,T[i],1]end end; module A;def call *a;a.empty?? T[self]: Scheme.t(self,a[0])end;def-@; T[:-,T[self]] end end;class::Array;def call a;T[self,T===a&&a.o ? a: T[a],1]end end;class::Symbol;def*i;T[ self,T[:*,T[i]]]end;include A ;def -i;Symbol===i ?:"#{self}-#{i}": T[self,T[:-,T[i]]]end end;at_exit{ R.drop(K[0]=$0==__FILE__ ?0:2).each{ |l|v l}};B=TOPLEVEL_BINDING;class:: String;include A end;m=eval('self', B);def m.method_missing i,*s;a,=s;s .empty?? i:T[i,T===a&&a.o ? a:T[a] ] end;class<<self;def e f,n;f.map{ |i|v i,n}[-1]end;def r x,f,n;->*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))
  4. TokyoRubyKaigi11 Speaker’s Award • RubyϋοΫͷ͖͔͚ͬͰ৆ • ੨໦ ๆ࿠͞Μ • Rubyιʔείʔυ׬શղઆ(RHG)Λ͸͡Ίɺ

    ֤छaamineϓϩμΫπ͔Βଟ͘ͷ͜ͱΛֶ͹ ͤͯ΋Βͬͨ
  5. “PATTERN MATCHING IN RUBY” • ࡳຈRubyձٞ 2012 • RubyͷύλʔϯϚον࣮૷Ͱ͋Δpattern- match

    gemͷػೳΛ঺հ
  6. ύλʔϯϚονͱ͸ • ଟॏ୅ೖͷڧԽ൛ͷΑ͏ͳ΋ͷ match([1, [2, 3]]) { with(_[a, _[b, c]])

    { } } a, (b, c) = [1, [2, 3]]
  7. ύλʔϯϚονͱ͸ • ಛʹ֊૚ߏ଄Λ࣋ͬͨσʔλͷॲཧͰ༗ޮ • ੺ࠇ໦ • ಛఆͷύλʔϯͷͱ͖ʹ໦Λճసͤͯ͞ਂ͞ΛҰఆʹอͭ Z Y B

    C D [ E Y B E Z D C [
  8. ύλʔϯϚονͱ͸ • ύλʔϯϚον͋Γ • ύλʔϯϚονͳ͠ 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
  9. pattern-matchͷఏڙ͢Δػೳ • ଞݴޠͰ࣮૷͞Ε͍ͯΔػೳͷϋΠϒϦου • Scala • ೚ҙͷந৅σʔλܕͱͷϚον • Scheme •

    and/or/not • ܁Γฦ͠ • Mathematica • ਖ਼نදݱ
  10. ೚ҙͷந৅σʔλܕͱͷϚον • ArrayΛArrayͱͯ͠Ϛον match([0]) do with(Array.(a)) { a } #=>

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

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

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

    } EMail.deconstruct("foo@example.com")
  14. ೚ҙͷந৅σʔλܕͱͷϚον • 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
  15. and(&)/or(|)/not(!) • ύλʔϯͷ૊Έ߹Θͤ • ಛʹand͸ม਺΁ͷ୅ೖͱ৚݅ൺֱΛ·ͱΊ ͯߦ͏ͨΊʹΑ͘ར༻͢Δ match([0, [1]]) do with(_[a

    & Integer, ! (_[2] | _[3])]) { a } #=> 0 end
  16. ܁Γฦ͠ match([[0, 1], [2, 3]]) { with(_[_[a, b], ___]) {

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

    Integer, *j]) { [i, j] #=> [["a", "b"], ["c", "d"]] } }
  18. ࣮ݧతͳػೳ(Hash) • JavaScriptͷDestructuring Assignmentྨࣅ match({a: 0, b: 1}) do with(Hash.(:a,

    b: Integer)) do a #=> 0 end end
  19. ࣮ݧతͳػೳ(<<) • ΦϒδΣΫτΛ෼ղͯ͠ม׵͔ͯ͠Βར༻͢ Δͱ͍͏සग़ύλʔϯΛָʹॻ͖͍ͨ a, b, c = str.scan(/re/) b

    = b.to_i match(str.scan(/re/)) { with(_[a, b << :to_i, c]) { } }
  20. ࣮ݧతͳػೳ(assert_pattern) • ΦϒδΣΫτͷߏ଄ΛνΣοΫ • σόοά࣌ʹར༻ ary.assert_pattern('_[Integer, /=/, ___]') # OK:

    ary = [0, "a=b", "c=d"] # NG: ary = [1, "x"]
  21. ར༻ྫ - power_assert gem - • ςετࣦഊ࣌ʹςετίʔυͷ֤ࣜΛग़ྗ͢ ΔͨΊͷϥΠϒϥϦ assert {

    ary.include?(3) } | | | false [0, 1, 2]
  22. ར༻ྫ - 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]]]]]]]]
  23. ར༻ྫ - 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 ...
  24. ར༻ྫ - 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
  25. ར༻ྫ - 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
  26. ઃܭͷϙΠϯτ • ಺෦DSLͱͯ͠ͷύλʔϯϚονػೳ • pattern-matchҎ֎ʹ΋ϥΠϒϥϦ͕͋Δ

  27. ઃܭͷϙΠϯτ • 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
  28. ઃܭͷϙΠϯτ • 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
  29. ઃܭͷϙΠϯτ • Rubyͷݴޠ࢓༷ʹύλʔϯϚονΛೖΕΔʹ ͋ͨͬͯͷproof of conceptʹͳΕΔ͜ͱΛ໨ ࢦ͢ • ೚ҙͷΦϒδΣΫτʹର͢ΔϚον •

    ࣮૷ͷૉ௚͞ΑΓݟͨ໨(ॻ͖΍͢͞)Λ༏ઌ
  30. ϚονॲཧͷશମͷྲྀΕ • Ϛον༻ͷπϦʔͷߏங • ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • Refinements • ϝλϓϩάϥϛϯά •

    πϦʔͱΦϒδΣΫτͷϚον • callcc • Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ϝλϓϩάϥϛϯά
  31. Ϛον༻ͷπϦʔͷߏங _[_[a, ___], 0] %FDPOTUSVDUPS "SSBZ %FDPOTUSVDUPS "SSBZ 7BSJBCMF B

    2VBOUJpFS 7BMVF 
  32. ϝλϓϩάϥϛϯά • ม਺ఆٛΛ
 ΤϛϡϨʔτ • BasicObject.new. instance_evalͱ method_missing ͷ૊Έ߹ΘͤͰ ະఆٛࣝผࢠͷ

    ৘ใΛर͏ match(obj) { with(a) { ... } } self #<BasicObject> self Context
  33. ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • .() • callϝιουݺͼग़͠ͷߏจ౶ҥ • Deconstructorͷੜ੒ʹར༻ prc = ->

    { 0 } prc.() match("foo@example.com") do with(EMail.("foo", domain)) { domain } end
  34. ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • 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
  35. ݟͨ໨ॏࢹͷจ๏ཁૉͷར༻ • ಥ͖٧Ίͨྫ • 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)))])))
  36. Refinements • ౰ॳ͸ϞϯΩʔύον͍͕ͯͨ͠#call͕Rack ͱিಥ • Refinements࠾༻ʹର͢Δݒ೦ • ࢓༷͕ྲྀಈత • JRuby

    • Φϓγϣφϧػೳͱͨ͠
  37. 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)
  38. πϦʔͱΦϒδΣΫτͷϚον %FDPOTUSVDUPS "SSBZ %FDPOTUSVDUPS "SSBZ 7BSJBCMF B 2VBOUJpFS 7BMVF 

    "SSBZ "SSBZ    match([[2, 1], 0]) { with(_[_[a, ___], 0]) {} }
  39. callcc • ϓϩάϥϜͷ࣮ߦঢ়ଶΛอଘ͓͍ͯͯ͠ޙͰ ࠶։Ͱ͖Δػೳ • ࣮ݧతʹόοΫτϥοΫΛαϙʔτ͢ΔͨΊ ʹར༻ • RubyਵҰͷࠇຐज़

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

  41. callccಋೖͷܦҢ • ౰ॳ͸Rubyͷଟॏ୅ ೖͱ߹Θͤͯύλʔϯ ଆͷ”*”͸1͚ͭͩΛ૝ ఆ • πϦʔΛ୯७ʹ࠶ى తʹτϥόʔεͯ͠ ॲཧ͢ΔΑ͏ʹ࣮૷

    • ਖ਼نදݱͷαϙʔτʹ ΑΓͦͷલఏ่͕Εͨ match([[0, 0], [0, 1]]) { with(_[_[*a, *_], _[*b, *_]], guard { a == b }) { a #=> [0] } }
  42. 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
  43. Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ม਺ͷΑ͏ʹΞΫηεͰ͖ΔΑ͏ʹ͢Δͨ ΊͷϝιουΛఆٛ • طଘͷϝιουͱͷিಥΛආ͚ΔͨΊʹઐ ༻ͷModuleΛ࡞ͬͯಛҟΫϥεʹprepend m = QuasiBindingModule.new

    do @stacks = ::Hash.new {|h, k| h[k] = [] } end obj.singleton_class.class_eval do prepend m end
  44. Ϛον݁Ռͷ”ม਺”΁ͷ୅ೖ • ΤϛϡϨʔγϣϯͷݶք • ϞϯΩʔύονͤ͟ΔΛಘͳ͍ • ϨΩγΧϧείʔϓʹͳΒͳ͍ • ҎԼͷProc͕ৗʹ0Λฦ͢Θ͚Ͱ͸ͳ͍ •

    ಛҟϝιου͕ఆٛͰ͖ͳ͍ΦϒδΣΫτ(nilͳͲ)Λ selfʹͯ͠ϚονͰ͖ͳ͍ match(0) { with(a) { -> { a } } }
  45. ·ͱΊ • ύλʔϯϚονͷRuby΁ͷಋೖʹ޲͚ͯ proof of conceptͱͳΔΑ͏ͳgemΛ࡞੒͠ ͨ • ҰޱʹύλʔϯϚονͱ͍ͬͯ΋ػೳʹ͸ό ϦΤʔγϣϯ͕͋Δ

    • Կ͕͋Ε͹ศརͳͷ͔ੋඇٞ࿦Λ