Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Re: Pattern Matching in Ruby

Re: Pattern Matching in Ruby

k_tsj

May 28, 2016
Tweet

More Decks by k_tsj

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  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<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<*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))

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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]]
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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]]]]]]]]

    View Slide

  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
    ...

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  31. Ϛον༻ͷπϦʔͷߏங
    _[_[a, ___], 0]
    %FDPOTUSVDUPS
    "SSBZ

    %FDPOTUSVDUPS
    "SSBZ

    7BSJBCMF
    B

    2VBOUJpFS
    7BMVF


    View Slide

  32. ϝλϓϩάϥϛϯά
    • ม਺ఆٛΛ

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

    View Slide

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

    View Slide

  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

    View Slide

  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)))])))

    View Slide

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

    View Slide

  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)

    View Slide

  38. πϦʔͱΦϒδΣΫτͷϚον
    %FDPOTUSVDUPS
    "SSBZ

    %FDPOTUSVDUPS
    "SSBZ

    7BSJBCMF
    B

    2VBOUJpFS
    7BMVF


    "SSBZ
    "SSBZ


    match([[2, 1], 0]) {
    with(_[_[a, ___], 0]) {}
    }

    View Slide

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

    View Slide

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

    View Slide

  41. callccಋೖͷܦҢ
    • ౰ॳ͸Rubyͷଟॏ୅
    ೖͱ߹Θͤͯύλʔϯ
    ଆͷ”*”͸1͚ͭͩΛ૝

    • πϦʔΛ୯७ʹ࠶ى
    తʹτϥόʔεͯ͠
    ॲཧ͢ΔΑ͏ʹ࣮૷
    • ਖ਼نදݱͷαϙʔτʹ
    ΑΓͦͷલఏ่͕Εͨ
    match([[0, 0], [0, 1]]) {
    with(_[_[*a, *_], _[*b, *_]],
    guard { a == b }) {
    a #=> [0]
    }
    }

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide