Add some ruby references
This commit is contained in:
@@ -9,6 +9,18 @@ references:
|
|||||||
url: https://www.sitepoint.com/history-ruby/
|
url: https://www.sitepoint.com/history-ruby/
|
||||||
date: 2023-11-16T14:54:03Z
|
date: 2023-11-16T14:54:03Z
|
||||||
file: www-sitepoint-com-6vwmef.txt
|
file: www-sitepoint-com-6vwmef.txt
|
||||||
|
- title: "In Ruby, Everything is Evaluated - Mufid's Code blog"
|
||||||
|
url: https://mufid.github.io/blog/2016/ruby-class-evaluation/
|
||||||
|
date: 2024-01-31T15:55:28Z
|
||||||
|
file: mufid-github-io-2czqvc.txt
|
||||||
|
- title: "Decorating Ruby - Part Two - Method Added Decoration - DEV Community"
|
||||||
|
url: https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
date: 2024-01-31T15:55:49Z
|
||||||
|
file: dev-to-pywrcp.txt
|
||||||
|
- title: "JavaScript Needs Blocks"
|
||||||
|
url: https://yehudakatz.com/2012/01/10/javascript-needs-blocks/
|
||||||
|
date: 2024-01-31T15:55:29Z
|
||||||
|
file: yehudakatz-com-sacizu.txt
|
||||||
- title: "When Should You NOT Use Rails?"
|
- title: "When Should You NOT Use Rails?"
|
||||||
url: https://codefol.io/posts/when-should-you-not-use-rails/
|
url: https://codefol.io/posts/when-should-you-not-use-rails/
|
||||||
date: 2023-11-04T17:36:55Z
|
date: 2023-11-04T17:36:55Z
|
||||||
|
|||||||
799
static/archive/dev-to-pywrcp.txt
Normal file
799
static/archive/dev-to-pywrcp.txt
Normal file
@@ -0,0 +1,799 @@
|
|||||||
|
[1]Skip to content
|
||||||
|
[3] DEV Community
|
||||||
|
[4][ ]
|
||||||
|
[6]
|
||||||
|
[7] Log in [8] Create account
|
||||||
|
|
||||||
|
DEV Community
|
||||||
|
|
||||||
|
● Add reaction
|
||||||
|
[sp] Like [mu] Unicorn [ex] Exploding Head [ra] Raised Hands [fi] Fire
|
||||||
|
Jump to Comments Save
|
||||||
|
Copy link
|
||||||
|
Copied to Clipboard
|
||||||
|
[20] Share to Twitter [21] Share to LinkedIn [22] Share to Reddit [23] Share to
|
||||||
|
Hacker News [24] Share to Facebook [25] Share to Mastodon
|
||||||
|
[26]Share Post via... [27]Report Abuse
|
||||||
|
[28] Cover image for Decorating Ruby - Part Two - Method Added Decoration
|
||||||
|
[29]Brandon Weaver
|
||||||
|
[30]Brandon Weaver
|
||||||
|
|
||||||
|
Posted on Aug 18, 2019 • Updated on Jan 21, 2021
|
||||||
|
|
||||||
|
● ● ● ● ●
|
||||||
|
|
||||||
|
Decorating Ruby - Part Two - Method Added Decoration
|
||||||
|
|
||||||
|
[31]#ruby
|
||||||
|
|
||||||
|
[32]Decorating Ruby (3 Part Series)
|
||||||
|
|
||||||
|
[33] 1 Decorating Ruby - Part One - Symbol Method Decoration [34] 2 Decorating
|
||||||
|
Ruby - Part Two - Method Added Decoration [35] 3 Decorating Ruby - Part Three -
|
||||||
|
Prepending Decoration
|
||||||
|
|
||||||
|
One precursor of me writing an article is if I keep forgetting how something's
|
||||||
|
done, causing me to write a reference to look back on for later. This is one
|
||||||
|
such article.
|
||||||
|
|
||||||
|
[36] What's in Store for Today?
|
||||||
|
|
||||||
|
We'll be looking at the next type of decoration, which involves intercepting
|
||||||
|
method_added to make a more fluent interface.
|
||||||
|
|
||||||
|
[37]The "Dark Lord" Crimson with Metaprogramming magic
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
|
||||||
|
• [38]Part One - Symbol Method Decoration
|
||||||
|
• [39]Part Two - Method Added Decoration
|
||||||
|
• [40]Part Three - Prepending Decoration
|
||||||
|
|
||||||
|
[41]<< Previous | [42]Next >>
|
||||||
|
|
||||||
|
[43] What Does Method Added Decoration Look Like?
|
||||||
|
|
||||||
|
You've seen the Symbol Method variant:
|
||||||
|
|
||||||
|
private def something; end
|
||||||
|
|
||||||
|
Readers that were paying very close attention in the last article may have
|
||||||
|
noticed when I said that I preferred that style of declaring private methods in
|
||||||
|
Ruby, but this was after the way that can be debatably considered more popular
|
||||||
|
and widely used in the community:
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def something; end
|
||||||
|
def something_else; end
|
||||||
|
|
||||||
|
Using private like this means that every method defined after will be
|
||||||
|
considered private. We know how the first one works, but what about the second?
|
||||||
|
There's no way it's using method names because it catches both of those methods
|
||||||
|
and doesn't change the definition syntax.
|
||||||
|
|
||||||
|
That's what we'll be looking into and learning today, and let me tell you it's
|
||||||
|
a metaprogramming trip.
|
||||||
|
|
||||||
|
[44] Making Our Own Method Added Decoration
|
||||||
|
|
||||||
|
As with the last article we're going to need to learn about a few tools before
|
||||||
|
we'll be ready to implement this one.
|
||||||
|
|
||||||
|
[45] Module Inclusion
|
||||||
|
|
||||||
|
Ruby uses Module inclusion as a way to extend classes with additional behavior,
|
||||||
|
sometimes requiring an interface to be met before it can do so. Enumerable is
|
||||||
|
one of the most common, and requires an each implementation to work:
|
||||||
|
|
||||||
|
class Collection
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
def initialize(*items)
|
||||||
|
@items = items
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(&fn)
|
||||||
|
return @items.to_enum unless block_given?
|
||||||
|
@items.each { |item| fn.call(item) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
(yield could be used here instead, but is less explicit and can be confusing to
|
||||||
|
teach.)
|
||||||
|
|
||||||
|
By defining that one method we've given our class the ability to do all types
|
||||||
|
of amazing things like map, select, and more.
|
||||||
|
|
||||||
|
Through those few lines we've added a lot of functionality to a class. Here's
|
||||||
|
the interesting part about Ruby: it also provides hooks to let Enumerable know
|
||||||
|
it was included, including what included it.
|
||||||
|
|
||||||
|
[46] Feeling Included
|
||||||
|
|
||||||
|
Let's say we have our own module, [47]Affable, which gives us a method to say
|
||||||
|
"hi":
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def greeting
|
||||||
|
"It's so very lovely to see you today!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
My, it is quite an [48]Affable module, now isn't it?
|
||||||
|
|
||||||
|
We could even go as far as to make a particularly Affable lemur:
|
||||||
|
|
||||||
|
class Lemur
|
||||||
|
include Affable
|
||||||
|
def initialize(name) @name = name; end
|
||||||
|
end
|
||||||
|
|
||||||
|
Lemur.new("Indigo").greeting
|
||||||
|
=> "It's so very lovely to see you today!"
|
||||||
|
|
||||||
|
What a classy lemur, yes.
|
||||||
|
|
||||||
|
[49] Hook, Line, and Sinker
|
||||||
|
|
||||||
|
Let's say that we wanted to tell what particular animal was Affable. We can use
|
||||||
|
included to see just that:
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
puts "#{klass.name} has become extra Affable!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
If we were to re-include that module:
|
||||||
|
|
||||||
|
class Lemur
|
||||||
|
include Affable
|
||||||
|
def initialize(name) @name = name; end
|
||||||
|
end
|
||||||
|
|
||||||
|
# STDOUT: Lemur has become extra Affable!
|
||||||
|
# => :initialize
|
||||||
|
|
||||||
|
Right classy. Oh, right, speaking of classy...
|
||||||
|
|
||||||
|
[50] Extra Classy Indeed
|
||||||
|
|
||||||
|
So we can hook inclusion of a module, great! Why do we care?
|
||||||
|
|
||||||
|
What if we wanted to both include methods into a class as well as extend its
|
||||||
|
behavior?
|
||||||
|
|
||||||
|
With just include it will apply all the behavior to instances of a class. With
|
||||||
|
just extend it will apply all the behavior to the class itself. We can't do
|
||||||
|
both.
|
||||||
|
|
||||||
|
...ok ok, it's Ruby, you caught me, we can totally do both.
|
||||||
|
|
||||||
|
As it turns out, include and extend are just methods on a class. We could just
|
||||||
|
Lemur.extend(ExtraBehavior) if we wanted to, or we could use our fun little
|
||||||
|
hooks from earlier.
|
||||||
|
|
||||||
|
A common convention for using this technique is a sub-module called
|
||||||
|
ClassMethods, like so:
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def affable?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
This allows us to inject behavior directly into the class as well as other
|
||||||
|
behavior we want to include in instances.
|
||||||
|
|
||||||
|
Part of me thinks this is so I don't have to remember the difference between
|
||||||
|
include and extend, but I always remember that and don't have to spend 20
|
||||||
|
minutes flipping between the two and prepend to see which one actually works,
|
||||||
|
absolutely not.
|
||||||
|
|
||||||
|
Now remember the title about Method Added being the technique for today? Oh
|
||||||
|
yes, there's a hook for that as well, but first we need to indicate that
|
||||||
|
something needs to be hooked in the first place.
|
||||||
|
|
||||||
|
[51] Raise Your Flag
|
||||||
|
|
||||||
|
We can intercept a method being added, but how do we know which method should
|
||||||
|
be intercepted? We'd need to add a flag to let that hook know it's time to
|
||||||
|
start intercepting in full force.
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def extra_affable
|
||||||
|
@extra_affable = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
If you remember private, this could be the flag to indicate that every method
|
||||||
|
afterwards should be private:
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def something; end
|
||||||
|
def something_else; end
|
||||||
|
|
||||||
|
Same idea here, and once a flag is raised it can also be taken down to make
|
||||||
|
sure later methods aren't impacted as well. We keep hinting at hooking method
|
||||||
|
added, so let's go ahead and do just that.
|
||||||
|
|
||||||
|
[52] Method Added
|
||||||
|
|
||||||
|
Now that we have our flag, we have enough to hook into method_added:
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def extra_affable
|
||||||
|
@extra_affable = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_added(method_name)
|
||||||
|
return unless @extra_affable
|
||||||
|
|
||||||
|
@extra_affable = false
|
||||||
|
# ...
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
We can use our flag to ignore method_added unless said flag is set. After we
|
||||||
|
check that, we can take down the flag to make sure additional methods defined
|
||||||
|
after aren't affected as well. For private this doesn't happen, but we want to
|
||||||
|
be polite. It is and Affable module after all.
|
||||||
|
|
||||||
|
[53] Politely Aliasing
|
||||||
|
|
||||||
|
Speaking of politeness, it's not precisely kind to just overwrite a method
|
||||||
|
without giving a way to call it as it was. We can use alias_method to get a new
|
||||||
|
name to the method before we overwrite it:
|
||||||
|
|
||||||
|
def method_added(method_name)
|
||||||
|
return unless @extra_affable
|
||||||
|
|
||||||
|
@extra_affable = false
|
||||||
|
|
||||||
|
original_method_name = "#{method_name}_without_affability".to_sym
|
||||||
|
alias_method original_method_name, method_name
|
||||||
|
end
|
||||||
|
|
||||||
|
This means that we can access the original method through this name.
|
||||||
|
|
||||||
|
[54] Wrap Battle
|
||||||
|
|
||||||
|
So we have the original method aliased, our hook in place, let's get to
|
||||||
|
overwriting that method then! As with the last tutorial we can use
|
||||||
|
define_method to do this:
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def extra_affable
|
||||||
|
@extra_affable = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_added(method_name)
|
||||||
|
return unless @extra_affable
|
||||||
|
|
||||||
|
@extra_affable = false
|
||||||
|
|
||||||
|
original_method_name = "#{method_name}_without_affability".to_sym
|
||||||
|
alias_method original_method_name, method_name
|
||||||
|
|
||||||
|
define_method(method_name) do |*args, &fn|
|
||||||
|
original_result = send(original_method_name, *args, &fn)
|
||||||
|
|
||||||
|
"#{original_result} Very lovely indeed!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Overwriting our original class again:
|
||||||
|
|
||||||
|
class Lemur
|
||||||
|
include Affable
|
||||||
|
|
||||||
|
def initialize(name) @name = name; end
|
||||||
|
|
||||||
|
extra_affable
|
||||||
|
|
||||||
|
def farewell
|
||||||
|
"Farewell! It was lovely to chat."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
We can give it a try:
|
||||||
|
|
||||||
|
Lemur.new("Indigo").farewell
|
||||||
|
=> "Farewell! It was lovely to chat. Very lovely indeed!"
|
||||||
|
|
||||||
|
[55] send Help!
|
||||||
|
|
||||||
|
Wait wait wait wait, send? Didn't we use method last time?
|
||||||
|
|
||||||
|
We did, but remember that method_added is a class method that does not have the
|
||||||
|
context of an instance of the class, or in other words it has no idea where the
|
||||||
|
farewell method is located.
|
||||||
|
|
||||||
|
send lets us treat this as an instance again by sending the method name
|
||||||
|
directly. Now we could use method inside of here as well, but that can be a bit
|
||||||
|
more expensive.
|
||||||
|
|
||||||
|
Only the contents inside define_method's block are executed in the context of
|
||||||
|
the instance.
|
||||||
|
|
||||||
|
[56] executive Functions
|
||||||
|
|
||||||
|
If we wanted to, we could have our special method take blocks which execute in
|
||||||
|
the context of an instance as well, and this is an extra special bonus trick
|
||||||
|
for this post.
|
||||||
|
|
||||||
|
Say that we made extra_affable also take a block that allows us to manipulate
|
||||||
|
the original value and still execute in the context of the instance:
|
||||||
|
|
||||||
|
class Lemur
|
||||||
|
include Affable
|
||||||
|
|
||||||
|
def initialize(name) @name = name; end
|
||||||
|
|
||||||
|
extra_affable { |original|
|
||||||
|
"#{@name}: #{original} Very lovely indeed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
def farewell
|
||||||
|
"Farewell! It was lovely to chat."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
With normal blocks, this will evaluate in the context of the class, but we want
|
||||||
|
it to evaluate in the context of the instance instead. That's what we have
|
||||||
|
instance_exec for:
|
||||||
|
|
||||||
|
module Affable
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def extra_affable(&fn)
|
||||||
|
@extra_affable = true
|
||||||
|
@extra_affable_fn = fn
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_added(method_name)
|
||||||
|
return unless @extra_affable
|
||||||
|
|
||||||
|
@extra_affable = false
|
||||||
|
extra_affable_fn = @extra_affable_fn
|
||||||
|
|
||||||
|
original_method_name = "#{method_name}_without_affability".to_sym
|
||||||
|
alias_method original_method_name, method_name
|
||||||
|
|
||||||
|
define_method(method_name) do |*args, &fn|
|
||||||
|
original_result = send(original_method_name, *args, &fn)
|
||||||
|
instance_exec(original_result, &extra_affable_fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Running that gives us this:
|
||||||
|
|
||||||
|
Lemur.new("Indigo").farewell
|
||||||
|
# => "Indigo: Farewell! It was lovely to chat. Very lovely indeed!"
|
||||||
|
|
||||||
|
Now pay very close attention to this line:
|
||||||
|
|
||||||
|
extra_affable_fn = @extra_affable_fn
|
||||||
|
|
||||||
|
We need to use this because inside define_method's block is inside the
|
||||||
|
instance, which has no clue what @extra_affable_fn is. That said, it can still
|
||||||
|
see outside to the context where the block was called, meaning it can see that
|
||||||
|
local version of extra_affable_fn sitting right there, allowing us to call it:
|
||||||
|
|
||||||
|
instance_exec(original_result, &extra_affable_fn)
|
||||||
|
|
||||||
|
[57] instance_eval vs instance_exec?
|
||||||
|
|
||||||
|
Why not use instance_eval? instance_exec allows us to pass along arguments as
|
||||||
|
well, otherwise instance_eval would make a lot of sense to evaluate something
|
||||||
|
in an instance. Instead, we need to execute something in the context of an
|
||||||
|
instance, so we use instance_exec here.
|
||||||
|
|
||||||
|
[58] Wrapping Up
|
||||||
|
|
||||||
|
So that was quite a lot of magic, and it took me a fair bit to really
|
||||||
|
understand what some of it was doing and why. That's perfectly ok, if I
|
||||||
|
understood everything the first time I'd be worried because that means I'm not
|
||||||
|
really learning anything!
|
||||||
|
|
||||||
|
One issue I think this will have later is I wonder how poorly having multiple
|
||||||
|
hooks to method_added will work. If it turns out it makes things go boom in a
|
||||||
|
spectacularly pretty and confounding way there'll be a part three. If not, this
|
||||||
|
paragraph will disappear and I'll pretend to not know what you're talking about
|
||||||
|
if you ask me about it.
|
||||||
|
|
||||||
|
There's a lot of potential here for some really interesting things, but there's
|
||||||
|
also a lot of potential for abuse. Be sure to not abuse such magic, because for
|
||||||
|
every layer of redefinition code can become increasingly harder to reason about
|
||||||
|
and test later.
|
||||||
|
|
||||||
|
In most cases I would instead advocate for SimpleDelegate, Forwardable, or
|
||||||
|
simple inheritance with super to extend behavior of classes. Don't use a
|
||||||
|
chainsaw where hedge trimmers will do, but on occasion it's nice to know a
|
||||||
|
chainsaw is there for those particularly gnarly problems.
|
||||||
|
|
||||||
|
Discretion is the name of the game.
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
|
||||||
|
• [59]Part One - Symbol Method Decoration
|
||||||
|
• [60]Part Two - Method Added Decoration
|
||||||
|
• [61]Part Three - Prepending Decoration
|
||||||
|
|
||||||
|
[62]<< Previous | [63]Next >>
|
||||||
|
|
||||||
|
[64]Decorating Ruby (3 Part Series)
|
||||||
|
|
||||||
|
[65] 1 Decorating Ruby - Part One - Symbol Method Decoration [66] 2 Decorating
|
||||||
|
Ruby - Part Two - Method Added Decoration [67] 3 Decorating Ruby - Part Three -
|
||||||
|
Prepending Decoration
|
||||||
|
|
||||||
|
Top comments (2)
|
||||||
|
|
||||||
|
Subscribe
|
||||||
|
pic
|
||||||
|
[ ]
|
||||||
|
Personal Trusted User
|
||||||
|
[75] Create template
|
||||||
|
|
||||||
|
Templates let you quickly answer FAQs or store snippets for re-use.
|
||||||
|
|
||||||
|
Submit Preview [78]Dismiss
|
||||||
|
|
||||||
|
[79] edisonywh profile image
|
||||||
|
[80] Edison Yap
|
||||||
|
Edison Yap
|
||||||
|
[82] [https] Edison Yap
|
||||||
|
Follow
|
||||||
|
An aspiring software engineer from the tiny city of Kuala Lumpur.
|
||||||
|
|
||||||
|
• Location
|
||||||
|
Stockholm, Sweden
|
||||||
|
• Education
|
||||||
|
RMIT University, Melbourne
|
||||||
|
• Work
|
||||||
|
Software Engineer at Klarna
|
||||||
|
• Joined
|
||||||
|
Jul 25, 2018
|
||||||
|
|
||||||
|
• [84] Aug 18 '19 • Edited on Aug 18 • Edited
|
||||||
|
|
||||||
|
• [86]Copy link
|
||||||
|
•
|
||||||
|
• Hide
|
||||||
|
•
|
||||||
|
•
|
||||||
|
•
|
||||||
|
|
||||||
|
Wow this is really cool, thanks for sharing Brandon!
|
||||||
|
|
||||||
|
Is there a way to hook onto the last method_added? For example I'd like to
|
||||||
|
execute something after all methods are added
|
||||||
|
|
||||||
|
EDIT: also quick search online seems to say that method_added only works for
|
||||||
|
instance methods, but there's singleton_method_added hook for class methods
|
||||||
|
too!
|
||||||
|
|
||||||
|
1 like Like [89] Reply
|
||||||
|
|
||||||
|
[90] baweaver profile image
|
||||||
|
[91] Brandon Weaver
|
||||||
|
Brandon Weaver
|
||||||
|
[93] [https] Brandon Weaver
|
||||||
|
Follow
|
||||||
|
Principal Ruby Engineer at Gusto on Payroll Services. Autistic / ADHD, He /
|
||||||
|
Him. I'm the Lemur guy.
|
||||||
|
|
||||||
|
• Location
|
||||||
|
San Francisco, CA
|
||||||
|
• Work
|
||||||
|
Principal Engineer - Payroll Services at Gusto
|
||||||
|
• Joined
|
||||||
|
Jan 16, 2019
|
||||||
|
|
||||||
|
• [95] Aug 18 '19 • Edited on Aug 18 • Edited
|
||||||
|
|
||||||
|
• [97]Copy link
|
||||||
|
•
|
||||||
|
• Hide
|
||||||
|
•
|
||||||
|
•
|
||||||
|
•
|
||||||
|
|
||||||
|
Technically in Ruby there's never a point in which methods are no longer added,
|
||||||
|
so it's a bit hard to hook that. One potential is to use TracePoint to hook the
|
||||||
|
ending of a class definition and retaining a class of "infected" classes, but
|
||||||
|
that'd be slow.
|
||||||
|
|
||||||
|
Look for "Class End Event" in this article: [99]medium.com/@baweaver/
|
||||||
|
exploring-tra...
|
||||||
|
|
||||||
|
EDIT - ...though now I'm curious if one could use such things to freeze a class
|
||||||
|
from modifications.
|
||||||
|
|
||||||
|
1 like Like [101] Reply
|
||||||
|
[102]Code of Conduct • [103]Report abuse
|
||||||
|
|
||||||
|
Are you sure you want to hide this comment? It will become hidden in your post,
|
||||||
|
but will still be visible via the comment's [107]permalink.
|
||||||
|
|
||||||
|
[109][ ]
|
||||||
|
|
||||||
|
Hide child comments as well
|
||||||
|
|
||||||
|
Confirm
|
||||||
|
|
||||||
|
For further actions, you may consider blocking this person and/or [111]
|
||||||
|
reporting abuse
|
||||||
|
|
||||||
|
Read next
|
||||||
|
|
||||||
|
[112]
|
||||||
|
cherryramatis profile image
|
||||||
|
|
||||||
|
Bringing more sweetness to Ruby with Sorbet types 🍦
|
||||||
|
|
||||||
|
Cherry Ramatis - Sep 18 '23
|
||||||
|
|
||||||
|
[113]
|
||||||
|
hungle00 profile image
|
||||||
|
|
||||||
|
Ruby's main object
|
||||||
|
|
||||||
|
hungle00 - Oct 1 '23
|
||||||
|
|
||||||
|
[114]
|
||||||
|
braindeaf profile image
|
||||||
|
|
||||||
|
Making a YouTube Short
|
||||||
|
|
||||||
|
RobL - Sep 28 '23
|
||||||
|
|
||||||
|
[115]
|
||||||
|
iberianpig profile image
|
||||||
|
|
||||||
|
Enhance Your Touchpad Experience on Linux with ThumbSense!
|
||||||
|
|
||||||
|
Kohei Yamada - Sep 27 '23
|
||||||
|
|
||||||
|
[116] [https] Brandon Weaver
|
||||||
|
Follow
|
||||||
|
Principal Ruby Engineer at Gusto on Payroll Services. Autistic / ADHD, He /
|
||||||
|
Him. I'm the Lemur guy.
|
||||||
|
|
||||||
|
• Location
|
||||||
|
San Francisco, CA
|
||||||
|
• Work
|
||||||
|
Principal Engineer - Payroll Services at Gusto
|
||||||
|
• Joined
|
||||||
|
Jan 16, 2019
|
||||||
|
|
||||||
|
More from [118]Brandon Weaver
|
||||||
|
|
||||||
|
[119] Understanding Ruby - Memoization
|
||||||
|
#ruby #beginners
|
||||||
|
[120] In Favor of Ruby Central Memberships
|
||||||
|
#ruby #community
|
||||||
|
[121] Pattern Matching Interfaces in Ruby
|
||||||
|
#ruby #rails #functional
|
||||||
|
|
||||||
|
Once suspended, baweaver will not be able to comment or publish posts until
|
||||||
|
their suspension is removed.
|
||||||
|
|
||||||
|
[ ]
|
||||||
|
[ ]
|
||||||
|
[ ]
|
||||||
|
Note: [ ]
|
||||||
|
|
||||||
|
Submit & Suspend
|
||||||
|
|
||||||
|
Once unsuspended, baweaver will be able to comment and publish posts again.
|
||||||
|
|
||||||
|
[ ]
|
||||||
|
[ ]
|
||||||
|
[ ]
|
||||||
|
Note: [ ]
|
||||||
|
|
||||||
|
Submit & Unsuspend
|
||||||
|
|
||||||
|
Once unpublished, all posts by baweaver will become hidden and only accessible
|
||||||
|
to themselves.
|
||||||
|
|
||||||
|
If baweaver is not suspended, they can still re-publish their posts from their
|
||||||
|
dashboard.
|
||||||
|
|
||||||
|
Note:[ ]
|
||||||
|
|
||||||
|
Unpublish all posts
|
||||||
|
|
||||||
|
Once unpublished, this post will become invisible to the public and only
|
||||||
|
accessible to Brandon Weaver.
|
||||||
|
|
||||||
|
They can still re-publish the post if they are not suspended.
|
||||||
|
|
||||||
|
Unpublish Post
|
||||||
|
|
||||||
|
Thanks for keeping DEV Community safe. Here is what you can do to flag
|
||||||
|
baweaver:
|
||||||
|
|
||||||
|
[129]( ) Make all posts by baweaver less visible
|
||||||
|
|
||||||
|
baweaver consistently posts content that violates DEV Community's code of
|
||||||
|
conduct because it is harassing, offensive or spammy.
|
||||||
|
|
||||||
|
[130] Report other inappropriate conduct
|
||||||
|
|
||||||
|
Confirm Flag
|
||||||
|
|
||||||
|
Unflagging baweaver will restore default visibility to their posts.
|
||||||
|
|
||||||
|
Confirm Unflag
|
||||||
|
|
||||||
|
[133]DEV Community — A constructive and inclusive social network for software
|
||||||
|
developers. With you every step of your journey.
|
||||||
|
|
||||||
|
• [134] Home
|
||||||
|
• [135] Podcasts
|
||||||
|
• [136] Videos
|
||||||
|
• [137] Tags
|
||||||
|
• [138] FAQ
|
||||||
|
• [139] Forem Shop
|
||||||
|
• [140] Advertise on DEV
|
||||||
|
• [141] About
|
||||||
|
• [142] Contact
|
||||||
|
• [143] Guides
|
||||||
|
• [144] Software comparisons
|
||||||
|
|
||||||
|
• [145] Code of Conduct
|
||||||
|
• [146] Privacy Policy
|
||||||
|
• [147] Terms of use
|
||||||
|
|
||||||
|
Built on [148]Forem — the [149]open source software that powers [150]DEV and
|
||||||
|
other inclusive communities.
|
||||||
|
|
||||||
|
Made with love and [151]Ruby on Rails. DEV Community © 2016 - 2024.
|
||||||
|
|
||||||
|
DEV Community
|
||||||
|
|
||||||
|
We're a place where coders share, stay up-to-date and grow their careers.
|
||||||
|
|
||||||
|
[152] Log in [153] Create account
|
||||||
|
● ● ● ● ●
|
||||||
|
|
||||||
|
References:
|
||||||
|
|
||||||
|
[1] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#main-content
|
||||||
|
[3] https://dev.to/
|
||||||
|
[6] https://dev.to/search
|
||||||
|
[7] https://dev.to/enter
|
||||||
|
[8] https://dev.to/enter?state=new-user
|
||||||
|
[20] https://twitter.com/intent/tweet?text=%22Decorating%20Ruby%20-%20Part%20Two%20-%20Method%20Added%20Decoration%22%20by%20%40keystonelemur%20%23DEVCommunity%20https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[21] https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj&title=Decorating%20Ruby%20-%20Part%20Two%20-%20Method%20Added%20Decoration&summary=How%20various%20forms%20of%20method%20decoration%20work%20in%20Ruby&source=DEV%20Community
|
||||||
|
[22] https://www.reddit.com/submit?url=https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj&title=Decorating%20Ruby%20-%20Part%20Two%20-%20Method%20Added%20Decoration
|
||||||
|
[23] https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj&t=Decorating%20Ruby%20-%20Part%20Two%20-%20Method%20Added%20Decoration
|
||||||
|
[24] https://www.facebook.com/sharer.php?u=https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[25] https://toot.kytta.dev/?text=https%3A%2F%2Fdev.to%2Fbaweaver%2Fdecorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[26] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#
|
||||||
|
[27] https://dev.to/report-abuse
|
||||||
|
[28] https://res.cloudinary.com/practicaldev/image/fetch/s--UjPHgJnM--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://thepracticaldev.s3.amazonaws.com/i/rdvh6fph3zoga5f9pw98.png
|
||||||
|
[29] https://dev.to/baweaver
|
||||||
|
[30] https://dev.to/baweaver
|
||||||
|
[31] https://dev.to/t/ruby
|
||||||
|
[32] https://dev.to/baweaver/series/10894
|
||||||
|
[33] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[34] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[35] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[36] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#whats-in-store-for-today
|
||||||
|
[37] https://res.cloudinary.com/practicaldev/image/fetch/s--L5_TPTzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/5pzy2brl5apjtt4edgrm.png
|
||||||
|
[38] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[39] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[40] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[41] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[42] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[43] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#what-does-method-added-decoration-look-like
|
||||||
|
[44] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#making-our-own-method-added-decoration
|
||||||
|
[45] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#module-inclusion
|
||||||
|
[46] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#feeling-included
|
||||||
|
[47] https://www.merriam-webster.com/dictionary/affable
|
||||||
|
[48] https://www.merriam-webster.com/dictionary/affable
|
||||||
|
[49] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#hook-line-and-sinker
|
||||||
|
[50] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#extra-classy-indeed
|
||||||
|
[51] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#raise-your-flag
|
||||||
|
[52] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#method-added
|
||||||
|
[53] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#politely-aliasing
|
||||||
|
[54] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#wrap-battle
|
||||||
|
[55] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#-raw-send-endraw-help
|
||||||
|
[56] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#-raw-exec-endraw-utive-functions
|
||||||
|
[57] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#-raw-instanceeval-endraw-vs-raw-instanceexec-endraw-
|
||||||
|
[58] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#wrapping-up
|
||||||
|
[59] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[60] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[61] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[62] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[63] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[64] https://dev.to/baweaver/series/10894
|
||||||
|
[65] https://dev.to/baweaver/decorating-ruby-part-1-symbol-method-decoration-4po2
|
||||||
|
[66] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj
|
||||||
|
[67] https://dev.to/baweaver/decorating-ruby-part-three-prepending-decoration-1ehc
|
||||||
|
[75] https://dev.to/settings/response-templates
|
||||||
|
[78] https://dev.to/404.html
|
||||||
|
[79] https://dev.to/edisonywh
|
||||||
|
[80] https://dev.to/edisonywh
|
||||||
|
[82] https://dev.to/edisonywh
|
||||||
|
[84] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#comment-e96o
|
||||||
|
[86] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#comment-e96o
|
||||||
|
[89] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#/baweaver/decorating-ruby-part-two-method-added-decoration-48mj/comments/new/e96o
|
||||||
|
[90] https://dev.to/baweaver
|
||||||
|
[91] https://dev.to/baweaver
|
||||||
|
[93] https://dev.to/baweaver
|
||||||
|
[95] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#comment-e9g0
|
||||||
|
[97] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#comment-e9g0
|
||||||
|
[99] https://medium.com/@baweaver/exploring-tracepoint-in-ruby-part-two-events-f4fd291992f5
|
||||||
|
[101] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#/baweaver/decorating-ruby-part-two-method-added-decoration-48mj/comments/new/e9g0
|
||||||
|
[102] https://dev.to/code-of-conduct
|
||||||
|
[103] https://dev.to/report-abuse
|
||||||
|
[107] https://dev.to/baweaver/decorating-ruby-part-two-method-added-decoration-48mj#
|
||||||
|
[111] https://dev.to/report-abuse
|
||||||
|
[112] https://dev.to/cherryramatis/bringing-more-sweetness-to-ruby-with-sorbet-types-13jp
|
||||||
|
[113] https://dev.to/hungle00/rubys-main-object-5hni
|
||||||
|
[114] https://dev.to/braindeaf/making-a-youtube-short-5gih
|
||||||
|
[115] https://dev.to/iberianpig/enhance-your-touchpad-experience-on-linux-with-thumbsense-391n
|
||||||
|
[116] https://dev.to/baweaver
|
||||||
|
[118] https://dev.to/baweaver
|
||||||
|
[119] https://dev.to/baweaver/understanding-ruby-memoization-2be5
|
||||||
|
[120] https://dev.to/baweaver/in-favor-of-ruby-central-memberships-15gl
|
||||||
|
[121] https://dev.to/baweaver/pattern-matching-interfaces-in-ruby-1b15
|
||||||
|
[130] javascript:void(0);
|
||||||
|
[133] https://dev.to/
|
||||||
|
[134] https://dev.to/
|
||||||
|
[135] https://dev.to/pod
|
||||||
|
[136] https://dev.to/videos
|
||||||
|
[137] https://dev.to/tags
|
||||||
|
[138] https://dev.to/faq
|
||||||
|
[139] https://shop.forem.com/
|
||||||
|
[140] https://dev.to/advertise
|
||||||
|
[141] https://dev.to/about
|
||||||
|
[142] https://dev.to/contact
|
||||||
|
[143] https://dev.to/guides
|
||||||
|
[144] https://dev.to/software-comparisons
|
||||||
|
[145] https://dev.to/code-of-conduct
|
||||||
|
[146] https://dev.to/privacy
|
||||||
|
[147] https://dev.to/terms
|
||||||
|
[148] https://www.forem.com/
|
||||||
|
[149] https://dev.to/t/opensource
|
||||||
|
[150] https://dev.to/
|
||||||
|
[151] https://dev.to/t/rails
|
||||||
|
[152] https://dev.to/enter
|
||||||
|
[153] https://dev.to/enter?state=new-user
|
||||||
132
static/archive/mufid-github-io-2czqvc.txt
Normal file
132
static/archive/mufid-github-io-2czqvc.txt
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
fidz
|
||||||
|
}
|
||||||
|
|
||||||
|
[1]Mufid's Code blog
|
||||||
|
|
||||||
|
• [2]RSS
|
||||||
|
|
||||||
|
[4][ ]
|
||||||
|
• [5]Blog
|
||||||
|
• [6]Archives
|
||||||
|
|
||||||
|
In Ruby, Everything is Evaluated
|
||||||
|
|
||||||
|
Jul 10th, 2016 | [7]Comments
|
||||||
|
|
||||||
|
So if i write
|
||||||
|
|
||||||
|
1 def hello
|
||||||
|
2 puts 'world'
|
||||||
|
3 end
|
||||||
|
|
||||||
|
It will evaluate def, to which Ruby will “create a method named hello in global
|
||||||
|
scope, with puts ‘world’ as a block”. We can change “global scope” to any
|
||||||
|
object we want.
|
||||||
|
|
||||||
|
1 class Greeting
|
||||||
|
2 def hello
|
||||||
|
3 puts 'world'
|
||||||
|
4 end
|
||||||
|
5 end
|
||||||
|
|
||||||
|
The class “Greeting” is actually EVALUATED, NOT DEFINED (e.g. In Java, after we
|
||||||
|
define a signature of a class/method, we can’t change it, except using
|
||||||
|
reflection). So actually, we can put anything in “Greeting” block, like
|
||||||
|
|
||||||
|
1 class Greeting
|
||||||
|
2 puts "Will define hello in greeting"
|
||||||
|
3 def hello
|
||||||
|
4 puts 'world'
|
||||||
|
5 end
|
||||||
|
6 end
|
||||||
|
|
||||||
|
Save above script as “test.rb” (or anything) and try to run it. It will show
|
||||||
|
“Will define hello in greeting” EVEN you don’t call “Greeting” class or “hello”
|
||||||
|
class or you don’t even need to instantiate “Greeting” class. This language
|
||||||
|
feature allows meta programming, like what we see in Rails.
|
||||||
|
|
||||||
|
This time i will use Class Attribute within active support. If you ever run
|
||||||
|
Rails, you should have it, but you can gem install active_support if you don’t.
|
||||||
|
|
||||||
|
1 require 'active_support/core_ext/class/attribute'
|
||||||
|
2
|
||||||
|
3 module Greeting; end
|
||||||
|
4
|
||||||
|
5 class Greeting::Base
|
||||||
|
6
|
||||||
|
7 class_attribute :blocks
|
||||||
|
8
|
||||||
|
9 def hello(name)
|
||||||
|
10 self.blocks[:greeting].call(name)
|
||||||
|
11 self.blocks[:hello].call(name)
|
||||||
|
12 end
|
||||||
|
13
|
||||||
|
14 protected
|
||||||
|
15 def self.define_greeting(sym, &blk)
|
||||||
|
16 self.blocks ||= {}
|
||||||
|
17 self.blocks[sym] = blk
|
||||||
|
18 end
|
||||||
|
19 end
|
||||||
|
20
|
||||||
|
21 class Greeting::English < Greeting::Base
|
||||||
|
22 define_greeting :greeting do |who|
|
||||||
|
23 puts "Hi #{who}, Ruby will greet you with hello world!"
|
||||||
|
24 end
|
||||||
|
25 define_greeting :hello do |who|
|
||||||
|
26 puts "Hello World, #{who}!"
|
||||||
|
27 end
|
||||||
|
28 end
|
||||||
|
29
|
||||||
|
30 class Greeting::Indonesian < Greeting::Base
|
||||||
|
31 define_greeting :greeting do |who|
|
||||||
|
32 puts "Halo kakak #{who}, Ruby akan menyapamu dengan Halo Dunia!"
|
||||||
|
33 end
|
||||||
|
34 define_greeting :hello do |who|
|
||||||
|
35 puts "Halo dunia! Salam, #{who}!"
|
||||||
|
36 end
|
||||||
|
37 end
|
||||||
|
38
|
||||||
|
39 x = Greeting::English.new
|
||||||
|
40 x.hello "Fido"
|
||||||
|
41 # Hi Fido, Ruby will greet you with hello world!
|
||||||
|
42 # Hello World, Fido!
|
||||||
|
43 x = Greeting::Indonesian.new
|
||||||
|
44 x.hello "Fido"
|
||||||
|
45 # Halo kakak Fido, Ruby akan menyapamu dengan Halo Dunia!
|
||||||
|
46 # Halo dunia! Salam, Fido!
|
||||||
|
|
||||||
|
Previously i want to move the class attribute logic to above code, but after i
|
||||||
|
see the [8]Active Support code, it is pretty complex, so i just require it : /
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Previously, i posted [9]this in Reddit
|
||||||
|
|
||||||
|
Posted by Mufid Jul 10th, 2016
|
||||||
|
|
||||||
|
[10]Tweet
|
||||||
|
|
||||||
|
[11]« Using Class with Generics <T> Without the T in C# [12]Cara Googling »
|
||||||
|
|
||||||
|
Comments
|
||||||
|
|
||||||
|
Please enable JavaScript to view the [13]comments powered by Disqus.
|
||||||
|
|
||||||
|
Copyright © 2021 - Mufid - Powered by [14]Octopress
|
||||||
|
|
||||||
|
|
||||||
|
References:
|
||||||
|
|
||||||
|
[1] https://mufid.github.io/blog/
|
||||||
|
[2] https://mufid.github.io/atom.xml
|
||||||
|
[5] https://mufid.github.io/blog/
|
||||||
|
[6] https://mufid.github.io/blog/blog/archives
|
||||||
|
[7] https://mufid.github.io/blog/2016/ruby-class-evaluation/#disqus_thread
|
||||||
|
[8] https://github.com/rails/rails/blob/e35b98e6f5c54330245645f2ed40d56c74538902/activesupport/lib/active_support/core_ext/class/attribute.rb
|
||||||
|
[9] https://www.reddit.com/r/ProgrammerTIL/comments/4s2vmr/ruby_til_in_ruby_everything_is_evaluated/
|
||||||
|
[10] http://twitter.com/share
|
||||||
|
[11] https://mufid.github.io/blog/2016/generic-type-csharp/
|
||||||
|
[12] https://mufid.github.io/blog/2016/how-to-google/
|
||||||
|
[13] http://disqus.com/?ref_noscript
|
||||||
|
[14] http://octopress.org/
|
||||||
357
static/archive/yehudakatz-com-sacizu.txt
Normal file
357
static/archive/yehudakatz-com-sacizu.txt
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
[1] Katz Got Your Tongue
|
||||||
|
|
||||||
|
• [3]Home
|
||||||
|
• [4]About
|
||||||
|
• [5]Projects
|
||||||
|
• [6]Talks
|
||||||
|
• [7]Podcasts
|
||||||
|
• [8]Schedule
|
||||||
|
|
||||||
|
Login Subscribe
|
||||||
|
Jan 9, 2012 6 min read
|
||||||
|
|
||||||
|
JavaScript Needs Blocks
|
||||||
|
|
||||||
|
While reading Hacker News posts about JavaScript, I often come across the
|
||||||
|
misconception that Ruby's blocks are essentially equivalent to JavaScript's
|
||||||
|
"first class functions". Because the ability to pass functions around,
|
||||||
|
especially when you can create them anonymously, is extremely powerful, the
|
||||||
|
fact that both JavaScript and Ruby have a mechanism to do so makes it natural
|
||||||
|
to assume equivalence.
|
||||||
|
|
||||||
|
In fact, when people talk about why Ruby's blocks are different from Python's
|
||||||
|
functions, they usually talk about anonymity, something that Ruby and
|
||||||
|
JavaScript share, but Python does not have. At first glance, a Ruby block is an
|
||||||
|
"anonymous function" (or colloquially, a "closure") just as a JavaScript
|
||||||
|
function is one.
|
||||||
|
|
||||||
|
This impression, which I admittedly shared in my early days as a Ruby/
|
||||||
|
JavaScript developer, misses an important subtlety that turns out to have large
|
||||||
|
implications. This subtlety is often referred to as "Tennent's Correspondence
|
||||||
|
Principle". In short, Tennent's Correspondence Principle says:
|
||||||
|
|
||||||
|
"For a given expression expr, lambda expr should be equivalent."
|
||||||
|
|
||||||
|
This is also known as the principle of abstraction, because it means that it is
|
||||||
|
easy to refactor common code into methods that take a block. For instance,
|
||||||
|
consider the common case of file resource management. Imagine that the block
|
||||||
|
form of File.open didn't exist in Ruby, and you saw a lot of the following in
|
||||||
|
your code:
|
||||||
|
|
||||||
|
begin
|
||||||
|
f = File.open(filename, "r")
|
||||||
|
# do something with f
|
||||||
|
ensure
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
|
||||||
|
In general, when you see some code that has the same beginning and end, but a
|
||||||
|
different middle, it is natural to refactor it into a method that takes a
|
||||||
|
block. You would write a method like this:
|
||||||
|
|
||||||
|
def read_file(filename)
|
||||||
|
f = File.open(filename, "r")
|
||||||
|
yield f
|
||||||
|
ensure
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
|
||||||
|
And you'd refactor instances of the pattern in your code with:
|
||||||
|
|
||||||
|
read_file(filename) do |f|
|
||||||
|
# do something with f
|
||||||
|
end
|
||||||
|
|
||||||
|
In order for this strategy to work, it's important that the code inside the
|
||||||
|
block look the same after refactoring as before. We can restate the
|
||||||
|
correspondence principle in this case as:
|
||||||
|
|
||||||
|
```ruby # do something with f ```
|
||||||
|
|
||||||
|
should be equivalent to:
|
||||||
|
|
||||||
|
do
|
||||||
|
# do something with
|
||||||
|
end
|
||||||
|
|
||||||
|
At first glance, it looks like this is true in Ruby and JavaScript. For
|
||||||
|
instance, let's say that what you're doing with the file is printing its mtime.
|
||||||
|
You can easily refactor the equivalent in JavaScript:
|
||||||
|
|
||||||
|
try {
|
||||||
|
// imaginary JS file API
|
||||||
|
var f = File.open(filename, "r");
|
||||||
|
sys.print(f.mtime);
|
||||||
|
} finally {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Into this:
|
||||||
|
|
||||||
|
read_file(function(f) {
|
||||||
|
sys.print(f.mtime);
|
||||||
|
});
|
||||||
|
|
||||||
|
In fact, cases like this, which are in fact quite elegant, give people the
|
||||||
|
mistaken impression that Ruby and JavaScript have a roughly equivalent ability
|
||||||
|
to refactor common functionality into anonymous functions.
|
||||||
|
|
||||||
|
However, consider a slightly more complicated example, first in Ruby. We'll
|
||||||
|
write a simple class that calculates a File's mtime and retrieves its body:
|
||||||
|
|
||||||
|
class FileInfo
|
||||||
|
def initialize(filename)
|
||||||
|
@name = filename
|
||||||
|
end
|
||||||
|
|
||||||
|
# calculate the File's +mtime+
|
||||||
|
def mtime
|
||||||
|
f = File.open(@name, "r")
|
||||||
|
mtime = mtime_for(f)
|
||||||
|
return "too old" if mtime < (Time.now - 1000)
|
||||||
|
puts "recent!"
|
||||||
|
mtime
|
||||||
|
ensure
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# retrieve that file's +body+
|
||||||
|
def body
|
||||||
|
f = File.open(@name, "r")
|
||||||
|
f.read
|
||||||
|
ensure
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# a helper method to retrieve the mtime of a file
|
||||||
|
def mtime_for(f)
|
||||||
|
File.mtime(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
We can easily refactor this code using blocks:
|
||||||
|
|
||||||
|
class FileInfo
|
||||||
|
def initialize(filename)
|
||||||
|
@name = filename
|
||||||
|
end
|
||||||
|
|
||||||
|
# refactor the common file management code into a method
|
||||||
|
# that takes a block
|
||||||
|
def mtime
|
||||||
|
with_file do |f|
|
||||||
|
mtime = mtime_for(f)
|
||||||
|
return "too old" if mtime < (Time.now - 1000)
|
||||||
|
puts "recent!"
|
||||||
|
mtime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def body
|
||||||
|
with_file { |f| f.read }
|
||||||
|
end
|
||||||
|
|
||||||
|
def mtime_for(f)
|
||||||
|
File.mtime(f)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# this method opens a file, calls a block with it, and
|
||||||
|
# ensures that the file is closed once the block has
|
||||||
|
# finished executing.
|
||||||
|
def with_file
|
||||||
|
f = File.open(@name, "r")
|
||||||
|
yield f
|
||||||
|
ensure
|
||||||
|
f.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Again, the important thing to note here is that we could move the code into a
|
||||||
|
block without changing it. Unfortunately, this same case does not work in
|
||||||
|
JavaScript. Let's first write the equivalent FileInfo class in JavaScript.
|
||||||
|
|
||||||
|
// constructor for the FileInfo class
|
||||||
|
FileInfo = function(filename) {
|
||||||
|
this.name = filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileInfo.prototype = {
|
||||||
|
// retrieve the file's mtime
|
||||||
|
mtime: function() {
|
||||||
|
try {
|
||||||
|
var f = File.open(this.name, "r");
|
||||||
|
var mtime = this.mtimeFor(f);
|
||||||
|
if (mtime < new Date() - 1000) {
|
||||||
|
return "too old";
|
||||||
|
}
|
||||||
|
sys.print(mtime);
|
||||||
|
} finally {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// retrieve the file's body
|
||||||
|
body: function() {
|
||||||
|
try {
|
||||||
|
var f = File.open(this.name, "r");
|
||||||
|
return f.read();
|
||||||
|
} finally {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// a helper method to retrieve the mtime of a file
|
||||||
|
mtimeFor: function(f) {
|
||||||
|
return File.mtime(f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
If we try to convert the repeated code into a method that takes a function, the
|
||||||
|
mtime method will look something like:
|
||||||
|
|
||||||
|
function() {
|
||||||
|
// refactor the common file management code into a method
|
||||||
|
// that takes a block
|
||||||
|
this.withFile(function(f) {
|
||||||
|
var mtime = this.mtimeFor(f);
|
||||||
|
if (mtime < new Date() - 1000) {
|
||||||
|
return "too old";
|
||||||
|
}
|
||||||
|
sys.print(mtime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
There are two very common problems here. First, this has changed contexts. We
|
||||||
|
can fix this by allowing a binding as a second parameter, but it means that we
|
||||||
|
need to make sure that every time we refactor to a lambda we make sure to
|
||||||
|
accept a binding parameter and pass it in. The var self = this pattern emerged
|
||||||
|
in JavaScript primarily because of the lack of correspondence.
|
||||||
|
|
||||||
|
This is annoying, but not deadly. More problematic is the fact that return has
|
||||||
|
changed meaning. Instead of returning from the outer function, it returns from
|
||||||
|
the inner one.
|
||||||
|
|
||||||
|
This is the right time for JavaScript lovers (and I write this as a sometimes
|
||||||
|
JavaScript lover myself) to argue that return behaves exactly as intended, and
|
||||||
|
this behavior is simpler and more elegant than the Ruby behavior. That may be
|
||||||
|
true, but it doesn't alter the fact that this behavior breaks the
|
||||||
|
correspondence principle, with very real consequences.
|
||||||
|
|
||||||
|
Instead of effortlessly refactoring code with the same start and end into a
|
||||||
|
function taking a function, JavaScript library authors need to consider the
|
||||||
|
fact that consumers of their APIs will often need to perform some gymnastics
|
||||||
|
when dealing with nested functions. In my experience as an author and consumer
|
||||||
|
of JavaScript libraries, this leads to many cases where it's just too much
|
||||||
|
bother to provide a nice block-based API.
|
||||||
|
|
||||||
|
In order to have a language with return (and possibly super and other similar
|
||||||
|
keywords) that satisfies the correspondence principle, the language must, like
|
||||||
|
Ruby and Smalltalk before it, have a function lambda and a block lambda.
|
||||||
|
Keywords like return always return from the function lambda, even inside of
|
||||||
|
block lambdas nested inside. At first glance, this appears a bit inelegant, and
|
||||||
|
language partisans often accuse Ruby of unnecessarily having two types of
|
||||||
|
"callables", in my experience as an author of large libraries in both Ruby and
|
||||||
|
JavaScript, it results in more elegant abstractions in the end.
|
||||||
|
|
||||||
|
Iterators and Callbacks
|
||||||
|
|
||||||
|
It's worth noting that block lambdas only make sense for functions that take
|
||||||
|
functions and invoke them immediately. In this context, keywords like return,
|
||||||
|
super and Ruby's yield make sense. These cases include iterators, mutex
|
||||||
|
synchronization and resource management (like the block form of File.open).
|
||||||
|
|
||||||
|
In contrast, when functions are used as callbacks, those keywords no longer
|
||||||
|
make sense. What does it mean to return from a function that has already
|
||||||
|
returned? In these cases, typically involving callbacks, function lambdas make
|
||||||
|
a lot of sense. In my view, this explains why JavaScript feels so elegant for
|
||||||
|
evented code that involves a lot of callbacks, but somewhat clunky for the
|
||||||
|
iterator case, and Ruby feels so elegant for the iterator case and somewhat
|
||||||
|
more clunky for the evented case. In Ruby's case, (again in my opinion), this
|
||||||
|
clunkiness is more from the massively pervasive use of blocks for synchronous
|
||||||
|
code than a real deficiency in its structures.
|
||||||
|
|
||||||
|
Because of these concerns, the ECMA working group responsible for ECMAScript,
|
||||||
|
TC39, [12]is considering adding block lambdas to the language. This would mean
|
||||||
|
that the above example could be refactored to:
|
||||||
|
|
||||||
|
FileInfo = function(name) {
|
||||||
|
this.name = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileInfo.prototype = {
|
||||||
|
mtime: function() {
|
||||||
|
// use the proposed block syntax, `{ |args| }`.
|
||||||
|
this.withFile { |f|
|
||||||
|
// in block lambdas, +this+ is unchanged
|
||||||
|
var mtime = this.mtimeFor(f);
|
||||||
|
if (mtime < new Date() - 1000) {
|
||||||
|
// block lambdas return from their nearest function
|
||||||
|
return "too old";
|
||||||
|
}
|
||||||
|
sys.print(mtime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
body: function() {
|
||||||
|
this.withFile { |f| f.read(); }
|
||||||
|
},
|
||||||
|
|
||||||
|
mtimeFor: function(f) {
|
||||||
|
return File.mtime(f);
|
||||||
|
},
|
||||||
|
|
||||||
|
withFile: function(block) {
|
||||||
|
try {
|
||||||
|
var f = File.open(this.name, "r");
|
||||||
|
block(f);
|
||||||
|
} finally {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Note that a parallel proposal, which replaces function-scoped var with
|
||||||
|
block-scoped let, will almost certainly be accepted by TC39, which would
|
||||||
|
slightly, but not substantively, change this example. Also note block lambdas
|
||||||
|
automatically return their last statement.
|
||||||
|
|
||||||
|
Our experience with Smalltalk and Ruby show that people do not need to
|
||||||
|
understand the SCARY correspondence principle for a language that satisfies it
|
||||||
|
to yield the desired results. I love the fact that the concept of "iterator" is
|
||||||
|
not built into the language, but is instead a consequence of natural block
|
||||||
|
semantics. This gives Ruby a rich, broadly useful set of built-in iterators,
|
||||||
|
and language users commonly build custom ones. As a JavaScript practitioner, I
|
||||||
|
often run into situations where using a for loop is significantly more
|
||||||
|
straight-forward than using forEach, always because of the lack of
|
||||||
|
correspondence between the code inside a built-in for loop and the code inside
|
||||||
|
the function passed to forEach.
|
||||||
|
|
||||||
|
For the reasons described above, I strongly approve of [13]the block lambda
|
||||||
|
proposal and hope it is adopted.
|
||||||
|
|
||||||
|
[14]
|
||||||
|
|
||||||
|
Published by:
|
||||||
|
|
||||||
|
[15] Yehuda Katz
|
||||||
|
[16]
|
||||||
|
Katz Got Your Tongue © 2024
|
||||||
|
[17]Powered by Ghost
|
||||||
|
[pixel]
|
||||||
|
|
||||||
|
References:
|
||||||
|
|
||||||
|
[1] https://yehudakatz.com/
|
||||||
|
[3] http://www.yehudakatz.com/
|
||||||
|
[4] https://yehudakatz.com/about/
|
||||||
|
[5] https://yehudakatz.com/projects/
|
||||||
|
[6] https://yehudakatz.com/talks/
|
||||||
|
[7] https://yehudakatz.com/podcasts/
|
||||||
|
[8] https://yehudakatz.com/schedule/
|
||||||
|
[12] http://wiki.ecmascript.org/doku.php?id=strawman%3Ablock_lambda_revival&ref=yehudakatz.com
|
||||||
|
[13] http://wiki.ecmascript.org/doku.php?id=strawman%3Ablock_lambda_revival&ref=yehudakatz.com
|
||||||
|
[14] https://yehudakatz.com/2011/12/12/amber-js-formerly-sproutcore-2-0-is-now-ember-js/
|
||||||
|
[15] https://yehudakatz.com/author/wycats/
|
||||||
|
[16] https://yehudakatz.com/2012/04/13/tokaido-my-hopes-and-dreams/
|
||||||
|
[17] https://ghost.org/
|
||||||
Reference in New Issue
Block a user