1. 2

    Can someone explain to me what the when statement in the example code does? Is it just like an advanced if?

    1. 3

      Putting the syntax here for continuity:

      res = [:ng, 500]
      case res
      when %p([:ng, status])
        p status
      end
      

      The idea is that any variable name that’s not bound would be bound as a local variable inside the when statement. In this case status is the match that would get assigned to 500.

      Note that there are several other suggestions for how to perform this task syntactically in the thread, and personally I’d lean against any shorthand that’s not immediately clear what it does.

      1. 1

        Thanks for that. Yeah it seems to me there should be a clearer way to express this.

    1. 2

      It’s worth noting that JS has a pattern matching feature under consideration as well. https://github.com/tc39/proposal-pattern-matching

      1. 4

        So, story time then as far as how this all started.

        Fog City Ruby, Pat Shaughnessy was talking about FP in Ruby and lamenting that it didn’t really have pattern matching. Me and @havenwood were playing with some syntax after with case statements to try and make a psuedo-system on it and ended up writing this gist as a “proof of concept”:

        https://gist.github.com/baweaver/611389c41c9005d025fb8e55448bf5f5

        Afterwards we’d joked about maybe making it a gem, but I hadn’t quite thought it through. I saw @maybekatz tweet about pattern matching in JS (beginning of the proposal) not too long afterwards and thought why not? So I decided to make Qo and see how far I could push it.

        Qo was meant far more as a “what if” and proof of concept than a “do this” exactly. I wanted to use it as a catalyst for discussion on how Ruby could have pattern matching while retaining some of its own natural look and feel. After it was released I pushed hard to get it in front of people and spread the idea around to see if it took hold.

        Matz has said before that good language feature proposals start as gems of some sort and evolve, so that was the entire goal. Get them talking, give them ideas, and try and qualify all the concerns and nuances around it.

        The part I didn’t quite expect was Zverok posting the issue. As soon as that dropped I basically took all the notes and thoughts I had and dumped them into the issue, including references to the TC39 proposal that really got me started on it.

        So the short version of all of this is no, the timing was not an accident. This was the best time to try and get the issue brought up and get real attention to it, and it looks like we’re having some degree of success with it so far.

        1. 1

          I’m glad there’s back-and-forth among the language communities. Thank you for sharing.

      1. 10

        Pattern matching is one of the only features I regularly feel is lacking in Ruby.
        There are other things I dislike or wish weren’t present, but the only missing feature is this.
        Combining conditionals and assignment just feels 10x better than having them separate.

        I even built a pattern matching library with some gross fun hacks on top of a specific namespace, thread-local singleton instance variables and caller_locations to allow for kinda-mostly pattern matching. I’m going to see if I can dig up the code, because aside from a first/rest pairing, I managed to get nested matching across Arrays and Hashes, and an Any object to match anything.
        Then I bumped into a Ruby Tapas video on the topic and stole the hubcaps to implement Erlang-like guard clauses.

        1. 6

          Have you looked at Qo at all? If so, any strong opinions about where it’s lacking? (Admittedly, it’s a giant hack, but it’s a pretty good giant hack.)

          1. 12

            Hello! Author of Qo here. Yeah, you’re right, it is a bit of a giant hack XD

            Anyways, I’d love to hear any ideas or suggestions on it. Typically I use the issue tracker to keep tabs on what I want to do with it, so feel free to jump in there as well.

            Admittedly I’m also the one who wrote a small-scale novel on pattern matching in the description to try and cover bases as I really really really want this feature, as evidenced by writing Qo in the first place.

            1. 3

              What do you think about the %p() or %m() syntaxes? I think they’re really ugly personally. Is it possible to make a spin on case which parses the when clauses as patterns without any special extra delimiters? You somewhat hit on that when talking about Scala style I think. Something like this maybe?

              match val 
              when [:constant, "constant", [1], variable_binding]
                variable_binding + 1
              else
                0
              end
              

              If you already covered that, apologies, I read your comments quickly last night and might have missed it.

              1. 4

                EDIT Codefied my idea here - https://bugs.ruby-lang.org/issues/14709#note-6

                match(value) do |m|
                  m.when(/name/, 42) { |name, age| Person.new(name, age) }
                  m.else { |object| raise "Can't convert!" }
                end
                

                An example practical usage:

                def get_url(url)
                  Net::HTTP.get_response(URI(url)).then(&match do |m|
                    m.when(Net::HTTPSuccess) { |response| response.body.size }
                    m.else { |response| raise response.message }
                  ))
                end
                

                Original:

                Not a problem, there are a lot of them (may Matz and Koichi forgive me)

                I hadn’t quite covered it yet. That’s always been the trick about this: what should it look like?

                Truthfully I don’t know quite yet, but I’m working on ideas. In regards to the %p and %m I would agree with some Reddit comments that they can tend slightly Perl-ish. I’d like a more fluent syntax if possible that reads intuitively, and I don’t think that quite does it.

                I had initially proposed this:

                new_value = match value
                  when %m(:_, 20..99) { |_, age| age + 1 }
                  else { |object| ... }
                end
                

                …which is quite similar to your suggestion. I’d almost consider switching the syntax a bit to something more like this:

                new_value = match value
                  when matches(:_, 20..99) { |_, age| age + 1 }
                  else matches(:_) { |object| ... }
                end
                

                When possible I would prefer very clear words as syntax over the percent shorthands. With this you could almost amend case to look for matches

                1. 1

                  Ahhh, I hadn’t seen how binding the match results as block arguments would be valuable, but your example of using === overloads like Class.=== and Regexp.=== have convinced me. I learned pattern matching in Erlang so I was thinking about the Erlang style mostly, and I didn’t think of how pattern matching would be most useful in Ruby. Blocks are a good way to reuse the case style matching that’s already well understood.

                2. 1

                  OOH I like this! You should counter-propose this syntax.

              2. 2

                I only just saw it via this proposal. Hope to find time to play with it today after work.