Add some ruby references

This commit is contained in:
David Eisinger
2024-01-31 10:56:46 -05:00
parent d0efa6fc66
commit 32acda0509
4 changed files with 1300 additions and 0 deletions

View 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

View 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 cant 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 dont call “Greeting” class or “hello”
class or you dont 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 dont.
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/

View 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/