Created
February 14, 2018 07:50
-
-
Save baweaver/611389c41c9005d025fb8e55448bf5f5 to your computer and use it in GitHub Desktop.
Having fun with M and Q
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
It should be noted that you really probably want Qo instead if you actually want to use these ideas:
https://www.github.com/baweaver/qo
Qo is effectively the result of this gist taken to extremes in a more succinct Ruby-like API.
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
M
andQ
, you can abuseself.[]
for new classes: