| # A few fun tricks right quick. Note that more tricks like this are over at: | |
| # https://medium.com/rubyinside/triple-equals-black-magic-d934936a6379 | |
| # | |
| # I'll probably condense this into a blog post later, but for now we'll have our fun. | |
| # 1 - "Pattern Matching" with case | |
| # We make a new lambda named M for brevity. Because it can be called with `[]` it | |
| # looks quite natural in flow with a case statement. | |
| # | |
| # Now then, we form a closure around our provided matchers, all of which are assumed | |
| # to be objects which respond to `===`, some in more interesting ways than others. | |
| # | |
| # For those who don't know what a closure is, it's what happens when a lambda is initialized. | |
| # It remembers the context of its initialization, meaning our "other" lambda returned from M | |
| # "remembers" what matchers are. | |
| # | |
| # One might notice the `:*`, which is symbolic of a match-all | |
| M = -> *matchers { | |
| -> other { | |
| matchers.each_with_index.all? { |m, i| m === other[i] || m == :* } | |
| } | |
| } | |
| # Let's give it a quick whirl shall we? | |
| # 1.1 - Regex match: Think tuples | |
| case ['Bob', 25] | |
| when M[/^B/, :*] then "It's Bob!" | |
| else "Well, guess not." | |
| end | |
| # 1.2 - Type match: Let's say we want to respond differently to different signatures, like a dispatch | |
| case ['10.0.0.1', 15] | |
| when M[String, Integer] then "It's an IP hit count" | |
| when M[String, Array] then "It's a group of IP logs" | |
| else "Dunno" | |
| end | |
| case ['10.0.0.1', %w(log log log log log)] | |
| when M[String, Integer] then "It's an IP hit count" | |
| when M[String, Array] then "It's a group of IP logs" | |
| else "Dunno" | |
| end | |
| # 1.3 - Lambda match: How about we go a bit further? Scala does something like this | |
| greater_than = -> a { -> b { b > a } } | |
| case ['Jaime', 24] | |
| when M[:*, greater_than[25]] then "Can be as old as Ruby" | |
| when M[:*, greater_than[20]] then "Can drink" | |
| when M[:*, greater_than[17]] then "Legal adult" | |
| else "Too young to tell" | |
| end | |
| # Closure lambdas can be incredibly incredibly useful for some applications. | |
| # | |
| # If you want to take it to an extreme: https://github.com/lazebny/ramda-ruby | |
| # | |
| # Read this as well: http://randycoulman.com/blog/2016/05/24/thinking-in-ramda-getting-started/ | |
| # | |
| # ...ah, but we're not done quite yet. I said tricks and tricks we shall have! Enter "Q" | |
| # 2 - "Query Matching" with case | |
| Q = -> **keyword_matchers { | |
| -> other { | |
| keyword_matchers.all? { |key, matcher| matcher === other[key] } | |
| } | |
| } | |
| Q[name: /^B/] === {name: 'Brandon'} | |
| # Let's get us some JSON to play with. JSON is fun! | |
| require 'json' | |
| require 'net/http' | |
| posts = JSON.parse( | |
| Net::HTTP.get(URI("https://jsonplaceholder.typicode.com/posts")),symbolize_names: true | |
| ) | |
| # 2.1 - A "select" sort of query | |
| # | |
| # I don't know about you, but I rather dislike writing this: | |
| posts.select { |post| post[:userId] == 1 } | |
| # How about Q? | |
| posts.select(&Q[userId: 1]) | |
| # Though don't take my word for it: | |
| posts.select(&Q[userId: 1]) == posts.select { |post| post[:userId] == 1 } | |
| # If you were _really_ feeling a bit evil, you _could_ switch `other[key]` to | |
| # something like `other.public_send(key)` :D | |
| Q2 = -> **keyword_matchers { | |
| -> other { | |
| keyword_matchers.all? { |key, matcher| matcher === other.public_send(key) } | |
| } | |
| } | |
| require 'ostruct' # because lazy admittedly | |
| post_objects = posts.map { |post| OpenStruct.new(post) } | |
| post_objects.select(&Q2[userId: 2]) | |
| # Now noted that Q doesn't deal with nested hashes. Could it? Sure, but that'd be some fun | |
| # writing I leave as an exercise to the unfortunate reader. | |
| # | |
| # Happy hacking! | |
| # - baweaver |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment
Hide comment
|
PS: If you want something a bit more dignified than random lambdas for class M
def initialize(*matchers)
@matchers = matchers
end
def self.[](*ms) new(*ms) end
def ===(other)
@matchers.each_with_index.all? { |m, i| m === other[i] || m == :* }
end
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
PS: If you want something a bit more dignified than random lambdas for
MandQ, you can abuseself.[]for new classes: