SlideShare a Scribd company logo
=== BLACK MAGIC
@KEYSTONELEMUR
Hey! Welcome to the speaker notes. For the most part I tend to use these as an expanded version of what I’m talking about so you, the reader, have more context about
what I’m talking about. That includes links, references, and a bit more detailed explanations than what I may have time for in the presentation.

That said, enjoy! It’ll be a fun ride.
WHO AM I?
@keystonelemur
Mostly going over who I am, what I do
WHO AM I?
My name is Brandon Weaver
I work at Square
I love Ruby, Javascript, and Lemurs
@keystonelemur
The Lemur part is a bit clearer with the fun fact that gets mentioned before I give this talk. Working on writing an intro book to Ruby using Lemurs to teach, it’s a great
time. Ah, side tracking.
HOW ===
WORKS
@keystonelemur
How about we get into it then? What is === and how in the world does it work?
HOW === WORKS
===
@keystonelemur
Well, if you’re coming from Javascript you’re in for a bit of a surprise, because it’s most certainly not a stricter equality operator. If anything, it’s substantially looser which
has earned it the name case equality or matching equality operator.
HOW === WORKS
/^f/ === 'foo'
@keystonelemur
You can use it explicitly, though most Rubyists would really prefer you don’t. Match is way clearer here and should be used as such. There are reasons to use ===
explicitly, sure, but easy on the magic syntax in app level code.
HOW === WORKS
def ===(v)
@keystonelemur
Really, like any of the operators, it’s just a method. That means it can be overridden, and it also means that if you get === backwards it’s not going to work like you think
it will like ==. It’s left-biased, so keep that in mind before you explicitly use it. I still mix them up, truth be told, so I cheat and wrap it in a method :D
HOW === WORKS
/^f/.===('foo')
@keystonelemur
See? Just a method call.
HOW === WORKS
class Integer
def ===(v)
self == v
end
end
@keystonelemur
Integer and most classes are boring, they just use it as an alias of ==. You can call === off of it but it won’t make a lick of difference.
HOW === WORKS
class Regexp
def ===(v)
match? v
end
end
@keystonelemur
Now regex? That’s a different story. Some classes redefine it to be somewhat of a set inclusion. For something to === a regex it’d have to “match” it, hence `match?`
being used there like that.
HOW === WORKS
Regexp = match?
Range = include?
Class = is_a?
Proc = call
@keystonelemur
Here are a few examples of classes that do interesting things to it. Noted that Proc is an odd bird. Most return exclusively boolean results, Proc just calls things. I’d
complain more about that not making much sense, but I like to leverage that to make Ruby do interesting things.

It’s not exhaustive, you should check into this article if you want that:

https://medium.com/rubyinside/triple-equals-black-magic-d934936a6379

I go into a lot more detail there. Really, it’s a good read overall after this talk.
HOW === WORKS
/^f/ === 'foo'
(1..10) === 1
String === 's'
-> a {a+1} === 2
@keystonelemur
Here’s a few examples of explicit calls. Again, Proc is an odd bird. That’s the same as `Proc#call` which means you get back 3. This is very useful, but definitely starts to
go the path of darker magics in Ruby. Ramda is a particularly interesting way to use that, see the article I linked to in the last slide notes.
WHERE’S ===
HIDING?
@keystonelemur
So if it’s so bad to use explicitly, why do we have it? Well turns out it gets used behind the scenes in more than a few things, which allows some very expressive code if
you know what it’s doing.
WHERE’S === HIDING?
case value
when String
'string'
when Integer
0
end
@keystonelemur
Every single branch of a case statement will use ===. That means you can pseudo-static type things if you’re really interested in that, though I wouldn’t suggest it for
dispatch as it can get slow.

Apparently the Stripe folks made a C++ static type library they’re demoing at RubyKaigi, but I don’t have links for that one yet.
WHERE’S === HIDING?
[1, 2, 3.0].grep(Integer)
# => [1, 2]
@keystonelemur
Grep will compare everything with === as well. Think of it as select, but with ===. grep_v is the inverse, primarily because command line flags like `-v` invert grep kinda
like select/reject.
WHERE’S === HIDING?
[1, 2, 3.0].all?(Integer)
# => false
@keystonelemur
In 2.5 all the predicate methods started responding to === type arguments as well. That means any, all, none, and one.
WHAT’S A
CLOSURE?
@keystonelemur
Before we get into later segments of this, be warned, a lot of black magic is coming and it might be a bit dense for beginners. I’ll do my best to make it digestible, but feel
free to ping me for questions later if it doesn’t make sense.
WHAT’S A CLOSURE?
adder = proc { |a|
proc { |b| a + b }
}
add3 = adder.call(3)
[1,2,3].map(&add3)
# => [4,5,6]
@keystonelemur
A closure is a function which remembers the context around where it was created. In this case we have a function, adder, which takes an argument ‘a’.
WHAT’S A CLOSURE?
adder = proc { |a|
proc { |b| a + b }
}
add3 = adder.call(3)
[1,2,3].map(&add3)
# => [4,5,6]
@keystonelemur
It returns a proc which takes an argument ‘b’. The interesting bit about closures here is that that function that got returned remembers what ‘a’ was when it was created.
WHAT’S A CLOSURE?
adder = proc { |a|
proc { |b| a + b }
}
add3 = adder.call(3)
[1,2,3].map(&add3)
# => [4,5,6]
@keystonelemur
So if we called it with 3, we now have a function returned waiting for one more argument that’ll add 3 to it.
WHAT’S A CLOSURE?
adder = proc { |a|
proc { |b| a + b }
}
add3 = adder.call(3)
[1,2,3].map(&add3)
# => [4,5,6]
@keystonelemur
Then we just pass it to something like map and it adds 3 to everything!
WHAT’S A CLOSURE?
adder = proc { |a|
proc { |b| a + b }
}
[1,2,3].map(&adder.call(3))
# => [4,5,6]
@keystonelemur
Though you could totally inline it as well. As long as map gets a function it’s perfectly happy to apply it to everything in that list.
EMULATING
PATTERN MATCHING
@keystonelemur
Got most of that? If not this article might help some: https://medium.com/@baweaver/reducing-enumerable-the-basics-fa042ce6806

It’s going to get a lot more dense from here, into some advanced territory. If it gets confusing, feel free to ping me and I’ll work on explaining it a bit better. I may even
write an article explaining some of the basics of Functional Programming later that covers this, so keep an eye on Medium for a few of those over the next few weeks.
EMULATING PATTERN MATCHING
query = proc { |*matchers|
proc { |target|
matchers.all? { |m| m === target }
}
}
[1,1.0,'s'].select(&query.call(
Integer, 1..10, :odd?.to_proc
))
# => [1]
@keystonelemur
Remembering what we know of closures, we can make a function which takes a number of arguments. Remember, a proc, like a method, can take multiple types of
arguments like varadic (star) or keyword (double star, hash args).
EMULATING PATTERN MATCHING
query = proc { |*matchers|
proc { |target|
matchers.all? { |m| m === target }
}
}
[1,1.0,'s'].select(&query.call(
Integer, 1..10, :odd?.to_proc
))
# => [1]
@keystonelemur
So it returns a function waiting for a target to “match” against, remembering what the matchers were when it was created.
EMULATING PATTERN MATCHING
query = proc { |*matchers|
proc { |target|
matchers.all? { |m| m === target }
}
}
[1,1.0,'s'].select(&query.call(
Integer, 1..10, :odd?.to_proc
))
# => [1]
@keystonelemur
Given those matchers, we check if all of them match the target. Notice, and this is important, that the matcher is on the LEFT side of the equation. If all the matchers we
pass “match” the target, we get back true.

We can’t use the === trick from 2.5 because we’re checking `all?` against a list of === respondent entities.
EMULATING PATTERN MATCHING
query = proc { |*matchers|
proc { |target|
matchers.all? { |m| m === target }
}
}
[1,1.0,'s'].select(&query.call(
Integer, 1..10, :odd?.to_proc
))
# => [1]
@keystonelemur
So we take a list of 1, 1.0, and the string ’s’ and we use our query with select. Our matchers are Integer, 1..10, and :odd? as a proc. That means we only want to select
values that match all three of those.

Notice I didn’t use &:odd? here. You can only do that once in a to_proc coercion, otherwise Ruby gets confused. I’ve run afoul of this one a few times.
EMULATING PATTERN MATCHING
query = proc { |*matchers|
proc { |target|
matchers.all? { |m| m === target }
}
}
[1,1.0,'s'].select(&query.call(
Integer, 1..10, :odd?.to_proc
))
# => [1]
@keystonelemur
1 is an Integer between 1 and 10 that happens to be odd, so only it passes.
EMULATING PATTERN MATCHING
people = [
{name: 'Foo', age: 42},
{name: 'Bar', age: 20},
{name: 'Baz', age: 30},
{name: 'Boo', age: 10},
]
@keystonelemur
So let’s say instead of just an array, we have an array of hashes to match against. Remember keyword arguments? They get to be real fun around here.
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target[k]
}
}
}
people.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [{name: 'Foo', age: 42}]
@keystonelemur
So instead of using *matchers, we use **matchers. That means we can pass keyword arguments instead. The problem with this is that you can only use Symbols here
which makes for a real pain when you have String keys of things. You can’t pass string keys as keyword arguments either.

There are ways around this by coercing things and double-checking values, but it becomes a huge performance hit after a while so keep that in mind. Hash with
indifferent access is even worse on performance, so another bit to keep in mind.
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target[k]
}
}
}
people.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [{name: 'Foo', age: 42}]
@keystonelemur
Anyways, ranting over, we again return a function awaiting a target
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target[k]
}
}
}
people.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [{name: 'Foo', age: 42}]
@keystonelemur
And instead of === against the target directly, we get the value of the target at the key provided, so we end up with name comparing ‘Foo’ to the regex /^Foo/ and so on.
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target[k]
}
}
}
people.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [{name: 'Foo', age: 42}]
@keystonelemur
So like the previous match, we can pass our “matchers” in and it’ll give us back values which “match” it.
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target[k]
}
}
}
people.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [{name: 'Foo', age: 42}]
@keystonelemur
In this case, only Foo matches.
EMULATING PATTERN MATCHING
people_objects = people.map { |v|
OpenStruct.new(v)
}
@keystonelemur
Though let’s say we have a collection of Objects instead. OpenStruct is a cheap way to make something that looks like a Person object with properties for name and age.
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target.public_send(k)
}
}
}
people_objects.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [OS(name: 'Foo', age: 42)]
@keystonelemur
The only difference between this and out Hash matcher is we use public_send instead of a key accessor. That means we can match against method calls as well, but we
want to use public_send to keep people from doing anything too particularly mischievous with it.

(Ok ok ok, fine, OpenStruct responds to both but that’s not the point of this slide)
EMULATING PATTERN MATCHING
query = proc { |**matchers|
proc { |target|
matchers.all? { |k,m|
m === target.public_send(k)
}
}
}
people_objects.select(&query.call(
name: /^Foo/, age: 40..50
))
# => [OS(name: 'Foo', age: 42)]
@keystonelemur
So amusingly the syntax is exactly the same on the outside, we just get back OpenStruct “people” instead!
CLASS
WRAPPERS
@keystonelemur
But really, you don’t need closures to pull this off, they just make for a bit of a quicker implementation. Know what else encapsulates state and lets you pass around
context? A class!
CLASS WRAPPERS
class Query
def initialize(*ms) @ms = ms end
def to_proc
proc { |v| self.call(v) }
end
def call(v)
@ms.all? { |m| m === v }
end
alias_method :===, :call
end
@keystonelemur
We’ll just assume veridic matchers for now. There are ways to take both and fork the query depending on which it got and what type of target it gets, but that’s an
exercise for the watcher / reader. ( ps - reading Qo’s source is cheating :P )

We just want to remember what the matchers are in this case. I use ms because slides are a bit small and I’d prefer to make the font larger for the people in the back.
CLASS WRAPPERS
class Query
def initialize(*ms) @ms = ms end
def to_proc
proc { |v| self.call(v) }
end
def call(v)
@ms.all? { |m| m === v }
end
alias_method :===, :call
end
@keystonelemur
Any time you see &something in Ruby, it’s saying call `to_proc` on whatever is after it. Notice that &:odd? syntax? That’s Symbol#to_proc. This is Query#to_proc.

We’re awaiting a target, v, and sending it to “call”
CLASS WRAPPERS
class Query
def initialize(*ms) @ms = ms end
def to_proc
proc { |v| self.call(v) }
end
def call(v)
@ms.all? { |m| m === v }
end
alias_method :===, :call
end
@keystonelemur
Call looks very familiar to our previous match logic. It remembers the matchers, and can run them all against a target value.

We can also alias it to === and [] to make it behave more like a proc if we want to.
CLASS WRAPPERS
class Query
def initialize(*ms) @ms = ms end
def to_proc
proc { |v| self.call(v) }
end
def call(v)
@ms.all? { |m| m === v }
end
alias_method :===, :call
end
@keystonelemur
That leaves us with a class that does about the same thing as the previous closure in a bit more of a Ruby-Oriented way. Why show you closures then? Because they
come in real handy whenever you start getting deeper into some of this stuff, and it’s a good tool to have on your belt.
CLASS WRAPPERS
[1,1.0,’s’].select(&Query.new(
Integer, 1..10, :odd.to_proc
))
@keystonelemur
So now we can just do this instead. A Query responds to to_proc which means it gives us back a proc waiting for a target.
CLASS WRAPPERS
case 1
when Query.new(Integer, 1..10)
# …
else
end
@keystonelemur
Remember that === alias? Case does. Now you can use queries pretty freely here.
IN THE WILD
@keystonelemur
Now how does this look when we apply it to the wild wild west of Rubyland? Well, turns out a lot of fun and the subject of a lot of the more advanced articles I’ve been
putting out recently. === is actually one of the foundational pieces to creating a pattern matching library in Ruby.
IN THE WILD - QO (PATTERN MATCHING)
people.map(&Qo.match { |m|
m.when(age: 13..19) { |person|
"#{person.name} is a teen"
}
m.else { |person|
"#{person.name} is #{person.age} years old"
}
})
people.select(&Qo.and(age: 10..19, name: /^F/))
@keystonelemur
Qo is what happens when I spend way too long with Scala and reading TC39 proposals after listening to Pat talk about pattern matching in Haskell. It started a few
months ago as a thought experiment, and now it exists.

The problem with a case statement is you can’t really do much with the value after you get it. It also doesn’t stack nicely with passing directly to map. With techniques
like you saw here, we can use a “query” as a guard statement for blocks of code to execute in sequence.
IN THE WILD - QO (PATTERN MATCHING)
people.map(&Qo.match { |m|
m.when(age: 13..19) { |person|
"#{person.name} is a teen"
}
m.else { |person|
"#{person.name} is #{person.age} years old”
}
})
people.select(&Qo.and(age: 10..19, name: /^F/))
@keystonelemur
Query objects tend to be used a bit more often though
IN THE WILD - SF (BLACK MAGIC)
(1..100).map(&Qo.match { |m|
m.when(Sf % 15 == 0) { 'FizzBuzz' }
m.when(Sf % 5 == 0) { 'Fizz' }
m.when(Sf % 3 == 0) { 'Buzz' }
m.else { |n| n }
})
@keystonelemur
Now I promised you black magic, so here’s the worst of it. Infix operators can be redefined, and Qo uses === for all matchers, meaning we can stack them to get
something that looks a lot like a Scala expression. Sf is super super black magic hackery, and should likely never see the light of production. That said, it was still really
really dang fun to make.
WRAPPING UP
@keystonelemur
Will write this later, or improv. Probably improv.
@keystonelemur
Yeah, it was improv. Blank slide of reflection, go!

More Related Content

What's hot

php string part 3
php string part 3php string part 3
php string part 3
monikadeshmane
 
Symfony2 and Doctrine2 Integration
Symfony2 and Doctrine2 IntegrationSymfony2 and Doctrine2 Integration
Symfony2 and Doctrine2 Integration
Jonathan Wage
 
Python
PythonPython
Rapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and RailsRapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and Rails
elliando dias
 
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative PracticeDeclarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Kevlin Henney
 
Active Support Core Extensions (1)
Active Support Core Extensions (1)Active Support Core Extensions (1)
Active Support Core Extensions (1)
RORLAB
 
Python-Tuples
Python-TuplesPython-Tuples
Python-Tuples
Krishna Nanda
 
A Taste of Python - Devdays Toronto 2009
A Taste of Python - Devdays Toronto 2009A Taste of Python - Devdays Toronto 2009
A Taste of Python - Devdays Toronto 2009
Jordan Baker
 
WordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPressWordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPress
Alena Holligan
 
08 functions
08 functions08 functions
08 functions
Hadley Wickham
 
Poly-paradigm Java
Poly-paradigm JavaPoly-paradigm Java
Poly-paradigm Java
Pavel Tcholakov
 
Alias
AliasAlias
Javascript Common Design Patterns
Javascript Common Design PatternsJavascript Common Design Patterns
Javascript Common Design Patterns
Pham Huy Tung
 
Twig Brief, Tips&Tricks
Twig Brief, Tips&TricksTwig Brief, Tips&Tricks
Twig Brief, Tips&Tricks
Andrei Burian
 
Kotlin
KotlinKotlin

What's hot (15)

php string part 3
php string part 3php string part 3
php string part 3
 
Symfony2 and Doctrine2 Integration
Symfony2 and Doctrine2 IntegrationSymfony2 and Doctrine2 Integration
Symfony2 and Doctrine2 Integration
 
Python
PythonPython
Python
 
Rapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and RailsRapid Development with Ruby/JRuby and Rails
Rapid Development with Ruby/JRuby and Rails
 
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative PracticeDeclarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
 
Active Support Core Extensions (1)
Active Support Core Extensions (1)Active Support Core Extensions (1)
Active Support Core Extensions (1)
 
Python-Tuples
Python-TuplesPython-Tuples
Python-Tuples
 
A Taste of Python - Devdays Toronto 2009
A Taste of Python - Devdays Toronto 2009A Taste of Python - Devdays Toronto 2009
A Taste of Python - Devdays Toronto 2009
 
WordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPressWordCamp Portland 2018: PHP for WordPress
WordCamp Portland 2018: PHP for WordPress
 
08 functions
08 functions08 functions
08 functions
 
Poly-paradigm Java
Poly-paradigm JavaPoly-paradigm Java
Poly-paradigm Java
 
Alias
AliasAlias
Alias
 
Javascript Common Design Patterns
Javascript Common Design PatternsJavascript Common Design Patterns
Javascript Common Design Patterns
 
Twig Brief, Tips&Tricks
Twig Brief, Tips&TricksTwig Brief, Tips&Tricks
Twig Brief, Tips&Tricks
 
Kotlin
KotlinKotlin
Kotlin
 

Similar to Fog City Ruby - Triple Equals Black Magic with speaker notes

Python quickstart for programmers: Python Kung Fu
Python quickstart for programmers: Python Kung FuPython quickstart for programmers: Python Kung Fu
Python quickstart for programmers: Python Kung Fu
climatewarrior
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming Language
Duda Dornelles
 
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares DornellesA linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
Tchelinux
 
Uses & Abuses of Mocks & Stubs
Uses & Abuses of Mocks & StubsUses & Abuses of Mocks & Stubs
Uses & Abuses of Mocks & Stubs
PatchSpace Ltd
 
Method::Signatures
Method::SignaturesMethod::Signatures
Method::Signatures
Michael Schwern
 
Making JavaScript Libraries More Approachable
Making JavaScript Libraries More ApproachableMaking JavaScript Libraries More Approachable
Making JavaScript Libraries More Approachable
Pamela Fox
 
Ruby Basics
Ruby BasicsRuby Basics
Ruby Basics
NagaLakshmi_N
 
Test First Teaching
Test First TeachingTest First Teaching
Test First Teaching
Sarah Allen
 
Fog City Ruby - Triple Equals Black Magic
Fog City Ruby - Triple Equals Black MagicFog City Ruby - Triple Equals Black Magic
Fog City Ruby - Triple Equals Black Magic
Brandon Weaver
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
Mark
 
jRuby: The best of both worlds
jRuby: The best of both worldsjRuby: The best of both worlds
jRuby: The best of both worlds
Christopher Spring
 
Ruby Gotchas
Ruby GotchasRuby Gotchas
Ruby Gotchas
Dave Aronson
 
An introduction to Ruby
An introduction to RubyAn introduction to Ruby
An introduction to Ruby
Wes Oldenbeuving
 
Fancy talk
Fancy talkFancy talk
Functional programming in ruby
Functional programming in rubyFunctional programming in ruby
Functional programming in ruby
Koen Handekyn
 
Spring has got me under it’s SpEL
Spring has got me under it’s SpELSpring has got me under it’s SpEL
Spring has got me under it’s SpEL
Eldad Dor
 
Testing Ruby with Rspec (a beginner's guide)
Testing Ruby with Rspec (a beginner's guide)Testing Ruby with Rspec (a beginner's guide)
Testing Ruby with Rspec (a beginner's guide)
Vysakh Sreenivasan
 
Python Homework Help
Python Homework HelpPython Homework Help
Python Homework Help
Python Homework Help
 
CPAP.com Introduction to Coding: Part 1
CPAP.com Introduction to Coding: Part 1CPAP.com Introduction to Coding: Part 1
CPAP.com Introduction to Coding: Part 1
johnnygoodman
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentation
adamcookeuk
 

Similar to Fog City Ruby - Triple Equals Black Magic with speaker notes (20)

Python quickstart for programmers: Python Kung Fu
Python quickstart for programmers: Python Kung FuPython quickstart for programmers: Python Kung Fu
Python quickstart for programmers: Python Kung Fu
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming Language
 
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares DornellesA linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
 
Uses & Abuses of Mocks & Stubs
Uses & Abuses of Mocks & StubsUses & Abuses of Mocks & Stubs
Uses & Abuses of Mocks & Stubs
 
Method::Signatures
Method::SignaturesMethod::Signatures
Method::Signatures
 
Making JavaScript Libraries More Approachable
Making JavaScript Libraries More ApproachableMaking JavaScript Libraries More Approachable
Making JavaScript Libraries More Approachable
 
Ruby Basics
Ruby BasicsRuby Basics
Ruby Basics
 
Test First Teaching
Test First TeachingTest First Teaching
Test First Teaching
 
Fog City Ruby - Triple Equals Black Magic
Fog City Ruby - Triple Equals Black MagicFog City Ruby - Triple Equals Black Magic
Fog City Ruby - Triple Equals Black Magic
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
jRuby: The best of both worlds
jRuby: The best of both worldsjRuby: The best of both worlds
jRuby: The best of both worlds
 
Ruby Gotchas
Ruby GotchasRuby Gotchas
Ruby Gotchas
 
An introduction to Ruby
An introduction to RubyAn introduction to Ruby
An introduction to Ruby
 
Fancy talk
Fancy talkFancy talk
Fancy talk
 
Functional programming in ruby
Functional programming in rubyFunctional programming in ruby
Functional programming in ruby
 
Spring has got me under it’s SpEL
Spring has got me under it’s SpELSpring has got me under it’s SpEL
Spring has got me under it’s SpEL
 
Testing Ruby with Rspec (a beginner's guide)
Testing Ruby with Rspec (a beginner's guide)Testing Ruby with Rspec (a beginner's guide)
Testing Ruby with Rspec (a beginner's guide)
 
Python Homework Help
Python Homework HelpPython Homework Help
Python Homework Help
 
CPAP.com Introduction to Coding: Part 1
CPAP.com Introduction to Coding: Part 1CPAP.com Introduction to Coding: Part 1
CPAP.com Introduction to Coding: Part 1
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentation
 

Recently uploaded

Biomedical Knowledge Graphs for Data Scientists and Bioinformaticians
Biomedical Knowledge Graphs for Data Scientists and BioinformaticiansBiomedical Knowledge Graphs for Data Scientists and Bioinformaticians
Biomedical Knowledge Graphs for Data Scientists and Bioinformaticians
Neo4j
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving
 
Discover the Unseen: Tailored Recommendation of Unwatched Content
Discover the Unseen: Tailored Recommendation of Unwatched ContentDiscover the Unseen: Tailored Recommendation of Unwatched Content
Discover the Unseen: Tailored Recommendation of Unwatched Content
ScyllaDB
 
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
Fwdays
 
Day 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio FundamentalsDay 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio Fundamentals
UiPathCommunity
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
Jason Yip
 
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Ukraine
 
Dandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity serverDandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity server
Antonios Katsarakis
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
Neo4j
 
JavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green MasterplanJavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green Masterplan
Miro Wengner
 
ScyllaDB Tablets: Rethinking Replication
ScyllaDB Tablets: Rethinking ReplicationScyllaDB Tablets: Rethinking Replication
ScyllaDB Tablets: Rethinking Replication
ScyllaDB
 
Must Know Postgres Extension for DBA and Developer during Migration
Must Know Postgres Extension for DBA and Developer during MigrationMust Know Postgres Extension for DBA and Developer during Migration
Must Know Postgres Extension for DBA and Developer during Migration
Mydbops
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
DianaGray10
 
From Natural Language to Structured Solr Queries using LLMs
From Natural Language to Structured Solr Queries using LLMsFrom Natural Language to Structured Solr Queries using LLMs
From Natural Language to Structured Solr Queries using LLMs
Sease
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
DianaGray10
 
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
Fwdays
 
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
zjhamm304
 
The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
operationspcvita
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
Javier Junquera
 
"Scaling RAG Applications to serve millions of users", Kevin Goedecke
"Scaling RAG Applications to serve millions of users",  Kevin Goedecke"Scaling RAG Applications to serve millions of users",  Kevin Goedecke
"Scaling RAG Applications to serve millions of users", Kevin Goedecke
Fwdays
 

Recently uploaded (20)

Biomedical Knowledge Graphs for Data Scientists and Bioinformaticians
Biomedical Knowledge Graphs for Data Scientists and BioinformaticiansBiomedical Knowledge Graphs for Data Scientists and Bioinformaticians
Biomedical Knowledge Graphs for Data Scientists and Bioinformaticians
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
 
Discover the Unseen: Tailored Recommendation of Unwatched Content
Discover the Unseen: Tailored Recommendation of Unwatched ContentDiscover the Unseen: Tailored Recommendation of Unwatched Content
Discover the Unseen: Tailored Recommendation of Unwatched Content
 
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
 
Day 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio FundamentalsDay 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio Fundamentals
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
 
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
 
Dandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity serverDandelion Hashtable: beyond billion requests per second on a commodity server
Dandelion Hashtable: beyond billion requests per second on a commodity server
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
 
JavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green MasterplanJavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green Masterplan
 
ScyllaDB Tablets: Rethinking Replication
ScyllaDB Tablets: Rethinking ReplicationScyllaDB Tablets: Rethinking Replication
ScyllaDB Tablets: Rethinking Replication
 
Must Know Postgres Extension for DBA and Developer during Migration
Must Know Postgres Extension for DBA and Developer during MigrationMust Know Postgres Extension for DBA and Developer during Migration
Must Know Postgres Extension for DBA and Developer during Migration
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
 
From Natural Language to Structured Solr Queries using LLMs
From Natural Language to Structured Solr Queries using LLMsFrom Natural Language to Structured Solr Queries using LLMs
From Natural Language to Structured Solr Queries using LLMs
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
 
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
 
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
 
The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
 
"Scaling RAG Applications to serve millions of users", Kevin Goedecke
"Scaling RAG Applications to serve millions of users",  Kevin Goedecke"Scaling RAG Applications to serve millions of users",  Kevin Goedecke
"Scaling RAG Applications to serve millions of users", Kevin Goedecke
 

Fog City Ruby - Triple Equals Black Magic with speaker notes

  • 1. === BLACK MAGIC @KEYSTONELEMUR Hey! Welcome to the speaker notes. For the most part I tend to use these as an expanded version of what I’m talking about so you, the reader, have more context about what I’m talking about. That includes links, references, and a bit more detailed explanations than what I may have time for in the presentation. That said, enjoy! It’ll be a fun ride.
  • 2. WHO AM I? @keystonelemur Mostly going over who I am, what I do
  • 3. WHO AM I? My name is Brandon Weaver I work at Square I love Ruby, Javascript, and Lemurs @keystonelemur The Lemur part is a bit clearer with the fun fact that gets mentioned before I give this talk. Working on writing an intro book to Ruby using Lemurs to teach, it’s a great time. Ah, side tracking.
  • 4. HOW === WORKS @keystonelemur How about we get into it then? What is === and how in the world does it work?
  • 5. HOW === WORKS === @keystonelemur Well, if you’re coming from Javascript you’re in for a bit of a surprise, because it’s most certainly not a stricter equality operator. If anything, it’s substantially looser which has earned it the name case equality or matching equality operator.
  • 6. HOW === WORKS /^f/ === 'foo' @keystonelemur You can use it explicitly, though most Rubyists would really prefer you don’t. Match is way clearer here and should be used as such. There are reasons to use === explicitly, sure, but easy on the magic syntax in app level code.
  • 7. HOW === WORKS def ===(v) @keystonelemur Really, like any of the operators, it’s just a method. That means it can be overridden, and it also means that if you get === backwards it’s not going to work like you think it will like ==. It’s left-biased, so keep that in mind before you explicitly use it. I still mix them up, truth be told, so I cheat and wrap it in a method :D
  • 9. HOW === WORKS class Integer def ===(v) self == v end end @keystonelemur Integer and most classes are boring, they just use it as an alias of ==. You can call === off of it but it won’t make a lick of difference.
  • 10. HOW === WORKS class Regexp def ===(v) match? v end end @keystonelemur Now regex? That’s a different story. Some classes redefine it to be somewhat of a set inclusion. For something to === a regex it’d have to “match” it, hence `match?` being used there like that.
  • 11. HOW === WORKS Regexp = match? Range = include? Class = is_a? Proc = call @keystonelemur Here are a few examples of classes that do interesting things to it. Noted that Proc is an odd bird. Most return exclusively boolean results, Proc just calls things. I’d complain more about that not making much sense, but I like to leverage that to make Ruby do interesting things. It’s not exhaustive, you should check into this article if you want that: https://medium.com/rubyinside/triple-equals-black-magic-d934936a6379 I go into a lot more detail there. Really, it’s a good read overall after this talk.
  • 12. HOW === WORKS /^f/ === 'foo' (1..10) === 1 String === 's' -> a {a+1} === 2 @keystonelemur Here’s a few examples of explicit calls. Again, Proc is an odd bird. That’s the same as `Proc#call` which means you get back 3. This is very useful, but definitely starts to go the path of darker magics in Ruby. Ramda is a particularly interesting way to use that, see the article I linked to in the last slide notes.
  • 13. WHERE’S === HIDING? @keystonelemur So if it’s so bad to use explicitly, why do we have it? Well turns out it gets used behind the scenes in more than a few things, which allows some very expressive code if you know what it’s doing.
  • 14. WHERE’S === HIDING? case value when String 'string' when Integer 0 end @keystonelemur Every single branch of a case statement will use ===. That means you can pseudo-static type things if you’re really interested in that, though I wouldn’t suggest it for dispatch as it can get slow. Apparently the Stripe folks made a C++ static type library they’re demoing at RubyKaigi, but I don’t have links for that one yet.
  • 15. WHERE’S === HIDING? [1, 2, 3.0].grep(Integer) # => [1, 2] @keystonelemur Grep will compare everything with === as well. Think of it as select, but with ===. grep_v is the inverse, primarily because command line flags like `-v` invert grep kinda like select/reject.
  • 16. WHERE’S === HIDING? [1, 2, 3.0].all?(Integer) # => false @keystonelemur In 2.5 all the predicate methods started responding to === type arguments as well. That means any, all, none, and one.
  • 17. WHAT’S A CLOSURE? @keystonelemur Before we get into later segments of this, be warned, a lot of black magic is coming and it might be a bit dense for beginners. I’ll do my best to make it digestible, but feel free to ping me for questions later if it doesn’t make sense.
  • 18. WHAT’S A CLOSURE? adder = proc { |a| proc { |b| a + b } } add3 = adder.call(3) [1,2,3].map(&add3) # => [4,5,6] @keystonelemur A closure is a function which remembers the context around where it was created. In this case we have a function, adder, which takes an argument ‘a’.
  • 19. WHAT’S A CLOSURE? adder = proc { |a| proc { |b| a + b } } add3 = adder.call(3) [1,2,3].map(&add3) # => [4,5,6] @keystonelemur It returns a proc which takes an argument ‘b’. The interesting bit about closures here is that that function that got returned remembers what ‘a’ was when it was created.
  • 20. WHAT’S A CLOSURE? adder = proc { |a| proc { |b| a + b } } add3 = adder.call(3) [1,2,3].map(&add3) # => [4,5,6] @keystonelemur So if we called it with 3, we now have a function returned waiting for one more argument that’ll add 3 to it.
  • 21. WHAT’S A CLOSURE? adder = proc { |a| proc { |b| a + b } } add3 = adder.call(3) [1,2,3].map(&add3) # => [4,5,6] @keystonelemur Then we just pass it to something like map and it adds 3 to everything!
  • 22. WHAT’S A CLOSURE? adder = proc { |a| proc { |b| a + b } } [1,2,3].map(&adder.call(3)) # => [4,5,6] @keystonelemur Though you could totally inline it as well. As long as map gets a function it’s perfectly happy to apply it to everything in that list.
  • 23. EMULATING PATTERN MATCHING @keystonelemur Got most of that? If not this article might help some: https://medium.com/@baweaver/reducing-enumerable-the-basics-fa042ce6806 It’s going to get a lot more dense from here, into some advanced territory. If it gets confusing, feel free to ping me and I’ll work on explaining it a bit better. I may even write an article explaining some of the basics of Functional Programming later that covers this, so keep an eye on Medium for a few of those over the next few weeks.
  • 24. EMULATING PATTERN MATCHING query = proc { |*matchers| proc { |target| matchers.all? { |m| m === target } } } [1,1.0,'s'].select(&query.call( Integer, 1..10, :odd?.to_proc )) # => [1] @keystonelemur Remembering what we know of closures, we can make a function which takes a number of arguments. Remember, a proc, like a method, can take multiple types of arguments like varadic (star) or keyword (double star, hash args).
  • 25. EMULATING PATTERN MATCHING query = proc { |*matchers| proc { |target| matchers.all? { |m| m === target } } } [1,1.0,'s'].select(&query.call( Integer, 1..10, :odd?.to_proc )) # => [1] @keystonelemur So it returns a function waiting for a target to “match” against, remembering what the matchers were when it was created.
  • 26. EMULATING PATTERN MATCHING query = proc { |*matchers| proc { |target| matchers.all? { |m| m === target } } } [1,1.0,'s'].select(&query.call( Integer, 1..10, :odd?.to_proc )) # => [1] @keystonelemur Given those matchers, we check if all of them match the target. Notice, and this is important, that the matcher is on the LEFT side of the equation. If all the matchers we pass “match” the target, we get back true. We can’t use the === trick from 2.5 because we’re checking `all?` against a list of === respondent entities.
  • 27. EMULATING PATTERN MATCHING query = proc { |*matchers| proc { |target| matchers.all? { |m| m === target } } } [1,1.0,'s'].select(&query.call( Integer, 1..10, :odd?.to_proc )) # => [1] @keystonelemur So we take a list of 1, 1.0, and the string ’s’ and we use our query with select. Our matchers are Integer, 1..10, and :odd? as a proc. That means we only want to select values that match all three of those. Notice I didn’t use &:odd? here. You can only do that once in a to_proc coercion, otherwise Ruby gets confused. I’ve run afoul of this one a few times.
  • 28. EMULATING PATTERN MATCHING query = proc { |*matchers| proc { |target| matchers.all? { |m| m === target } } } [1,1.0,'s'].select(&query.call( Integer, 1..10, :odd?.to_proc )) # => [1] @keystonelemur 1 is an Integer between 1 and 10 that happens to be odd, so only it passes.
  • 29. EMULATING PATTERN MATCHING people = [ {name: 'Foo', age: 42}, {name: 'Bar', age: 20}, {name: 'Baz', age: 30}, {name: 'Boo', age: 10}, ] @keystonelemur So let’s say instead of just an array, we have an array of hashes to match against. Remember keyword arguments? They get to be real fun around here.
  • 30. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target[k] } } } people.select(&query.call( name: /^Foo/, age: 40..50 )) # => [{name: 'Foo', age: 42}] @keystonelemur So instead of using *matchers, we use **matchers. That means we can pass keyword arguments instead. The problem with this is that you can only use Symbols here which makes for a real pain when you have String keys of things. You can’t pass string keys as keyword arguments either. There are ways around this by coercing things and double-checking values, but it becomes a huge performance hit after a while so keep that in mind. Hash with indifferent access is even worse on performance, so another bit to keep in mind.
  • 31. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target[k] } } } people.select(&query.call( name: /^Foo/, age: 40..50 )) # => [{name: 'Foo', age: 42}] @keystonelemur Anyways, ranting over, we again return a function awaiting a target
  • 32. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target[k] } } } people.select(&query.call( name: /^Foo/, age: 40..50 )) # => [{name: 'Foo', age: 42}] @keystonelemur And instead of === against the target directly, we get the value of the target at the key provided, so we end up with name comparing ‘Foo’ to the regex /^Foo/ and so on.
  • 33. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target[k] } } } people.select(&query.call( name: /^Foo/, age: 40..50 )) # => [{name: 'Foo', age: 42}] @keystonelemur So like the previous match, we can pass our “matchers” in and it’ll give us back values which “match” it.
  • 34. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target[k] } } } people.select(&query.call( name: /^Foo/, age: 40..50 )) # => [{name: 'Foo', age: 42}] @keystonelemur In this case, only Foo matches.
  • 35. EMULATING PATTERN MATCHING people_objects = people.map { |v| OpenStruct.new(v) } @keystonelemur Though let’s say we have a collection of Objects instead. OpenStruct is a cheap way to make something that looks like a Person object with properties for name and age.
  • 36. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target.public_send(k) } } } people_objects.select(&query.call( name: /^Foo/, age: 40..50 )) # => [OS(name: 'Foo', age: 42)] @keystonelemur The only difference between this and out Hash matcher is we use public_send instead of a key accessor. That means we can match against method calls as well, but we want to use public_send to keep people from doing anything too particularly mischievous with it. (Ok ok ok, fine, OpenStruct responds to both but that’s not the point of this slide)
  • 37. EMULATING PATTERN MATCHING query = proc { |**matchers| proc { |target| matchers.all? { |k,m| m === target.public_send(k) } } } people_objects.select(&query.call( name: /^Foo/, age: 40..50 )) # => [OS(name: 'Foo', age: 42)] @keystonelemur So amusingly the syntax is exactly the same on the outside, we just get back OpenStruct “people” instead!
  • 38. CLASS WRAPPERS @keystonelemur But really, you don’t need closures to pull this off, they just make for a bit of a quicker implementation. Know what else encapsulates state and lets you pass around context? A class!
  • 39. CLASS WRAPPERS class Query def initialize(*ms) @ms = ms end def to_proc proc { |v| self.call(v) } end def call(v) @ms.all? { |m| m === v } end alias_method :===, :call end @keystonelemur We’ll just assume veridic matchers for now. There are ways to take both and fork the query depending on which it got and what type of target it gets, but that’s an exercise for the watcher / reader. ( ps - reading Qo’s source is cheating :P ) We just want to remember what the matchers are in this case. I use ms because slides are a bit small and I’d prefer to make the font larger for the people in the back.
  • 40. CLASS WRAPPERS class Query def initialize(*ms) @ms = ms end def to_proc proc { |v| self.call(v) } end def call(v) @ms.all? { |m| m === v } end alias_method :===, :call end @keystonelemur Any time you see &something in Ruby, it’s saying call `to_proc` on whatever is after it. Notice that &:odd? syntax? That’s Symbol#to_proc. This is Query#to_proc. We’re awaiting a target, v, and sending it to “call”
  • 41. CLASS WRAPPERS class Query def initialize(*ms) @ms = ms end def to_proc proc { |v| self.call(v) } end def call(v) @ms.all? { |m| m === v } end alias_method :===, :call end @keystonelemur Call looks very familiar to our previous match logic. It remembers the matchers, and can run them all against a target value. We can also alias it to === and [] to make it behave more like a proc if we want to.
  • 42. CLASS WRAPPERS class Query def initialize(*ms) @ms = ms end def to_proc proc { |v| self.call(v) } end def call(v) @ms.all? { |m| m === v } end alias_method :===, :call end @keystonelemur That leaves us with a class that does about the same thing as the previous closure in a bit more of a Ruby-Oriented way. Why show you closures then? Because they come in real handy whenever you start getting deeper into some of this stuff, and it’s a good tool to have on your belt.
  • 43. CLASS WRAPPERS [1,1.0,’s’].select(&Query.new( Integer, 1..10, :odd.to_proc )) @keystonelemur So now we can just do this instead. A Query responds to to_proc which means it gives us back a proc waiting for a target.
  • 44. CLASS WRAPPERS case 1 when Query.new(Integer, 1..10) # … else end @keystonelemur Remember that === alias? Case does. Now you can use queries pretty freely here.
  • 45. IN THE WILD @keystonelemur Now how does this look when we apply it to the wild wild west of Rubyland? Well, turns out a lot of fun and the subject of a lot of the more advanced articles I’ve been putting out recently. === is actually one of the foundational pieces to creating a pattern matching library in Ruby.
  • 46. IN THE WILD - QO (PATTERN MATCHING) people.map(&Qo.match { |m| m.when(age: 13..19) { |person| "#{person.name} is a teen" } m.else { |person| "#{person.name} is #{person.age} years old" } }) people.select(&Qo.and(age: 10..19, name: /^F/)) @keystonelemur Qo is what happens when I spend way too long with Scala and reading TC39 proposals after listening to Pat talk about pattern matching in Haskell. It started a few months ago as a thought experiment, and now it exists. The problem with a case statement is you can’t really do much with the value after you get it. It also doesn’t stack nicely with passing directly to map. With techniques like you saw here, we can use a “query” as a guard statement for blocks of code to execute in sequence.
  • 47. IN THE WILD - QO (PATTERN MATCHING) people.map(&Qo.match { |m| m.when(age: 13..19) { |person| "#{person.name} is a teen" } m.else { |person| "#{person.name} is #{person.age} years old” } }) people.select(&Qo.and(age: 10..19, name: /^F/)) @keystonelemur Query objects tend to be used a bit more often though
  • 48. IN THE WILD - SF (BLACK MAGIC) (1..100).map(&Qo.match { |m| m.when(Sf % 15 == 0) { 'FizzBuzz' } m.when(Sf % 5 == 0) { 'Fizz' } m.when(Sf % 3 == 0) { 'Buzz' } m.else { |n| n } }) @keystonelemur Now I promised you black magic, so here’s the worst of it. Infix operators can be redefined, and Qo uses === for all matchers, meaning we can stack them to get something that looks a lot like a Scala expression. Sf is super super black magic hackery, and should likely never see the light of production. That said, it was still really really dang fun to make.
  • 49. WRAPPING UP @keystonelemur Will write this later, or improv. Probably improv.
  • 50. @keystonelemur Yeah, it was improv. Blank slide of reflection, go!