<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ulysseszh.github.io/feed/tags/metaprogramming.xml" rel="self" type="application/atom+xml" /><link href="https://ulysseszh.github.io/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-11-10T03:31:40-08:00</updated><id>https://ulysseszh.github.io/feed/tags/metaprogramming.xml</id><title type="html"><![CDATA[Ulysses’ trip]]></title><subtitle>Here we are at the awesome (awful) blog written by UlyssesZhan!</subtitle><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><entry><title type="html"><![CDATA[Polymorphism on a single object in Ruby]]></title><link href="https://ulysseszh.github.io/programming/2026/11/10/single-polymorphism.html" rel="alternate" type="text/html" title="Polymorphism on a single object in Ruby" /><published>2026-11-10T02:58:31-08:00</published><updated>2026-11-10T02:58:31-08:00</updated><id>https://ulysseszh.github.io/programming/2026/11/10/single-polymorphism</id><content type="html" xml:base="https://ulysseszh.github.io/programming/2026/11/10/single-polymorphism.html"><![CDATA[<p>Consider the following code snippet:</p>
<pre><code><span class="k">class</span> <span class="nc">MyData</span>
	<span class="nb">attr_accessor</span> <span class="ss">:x</span><span class="p">,</span> <span class="ss">:y</span><span class="p">,</span> <span class="ss">:text</span>

	<span class="kp">extend</span> <span class="no">Binary</span>

	<span class="no">MASK</span> <span class="o">=</span> <span class="p">[</span><span class="mh">0xdeadbeef</span><span class="p">].</span><span class="nf">pack</span> <span class="p">?</span><span class="n">v</span>
	<span class="k">def</span> <span class="nf">mask</span> <span class="n">data</span>
		<span class="n">data</span><span class="p">.</span><span class="nf">bytes</span><span class="p">.</span><span class="nf">zip</span><span class="p">((</span><span class="no">MASK</span> <span class="o">*</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="nf">bytesize</span> <span class="o">/</span> <span class="no">MASK</span><span class="p">.</span><span class="nf">bytesize</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)).</span><span class="nf">bytes</span><span class="p">).</span><span class="nf">map</span> <span class="p">{</span> <span class="n">_1</span> <span class="o">^</span> <span class="n">_2</span> <span class="p">}.</span><span class="nf">pack</span> <span class="s1">'C*'</span>
	<span class="k">end</span>

	<span class="n">read</span> <span class="k">do</span>
		<span class="k">raise</span> <span class="s1">'incorrect format'</span> <span class="k">if</span> <span class="n">bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="o">!=</span> <span class="s1">'Poly'</span>
		<span class="n">read_raw</span> <span class="n">mask</span> <span class="n">rest</span>
	<span class="k">end</span>

	<span class="n">read</span> <span class="p">:</span><span class="n">raw</span> <span class="k">do</span>
		<span class="vi">@x</span> <span class="o">=</span> <span class="n">u8</span>
		<span class="vi">@y</span> <span class="o">=</span> <span class="n">u8</span>
		<span class="k">raise</span> <span class="s1">'incorrect format'</span> <span class="k">if</span> <span class="vi">@x</span> <span class="o">+</span> <span class="vi">@y</span> <span class="o">!=</span> <span class="n">u8</span>
		<span class="vi">@text</span> <span class="o">=</span> <span class="n">string</span>
	<span class="k">end</span>

	<span class="n">write</span> <span class="k">do</span>
		<span class="n">bytes</span> <span class="s1">'Poly'</span>
		<span class="n">bytes</span> <span class="n">mask</span> <span class="n">write_raw</span>
	<span class="k">end</span>

	<span class="n">write</span> <span class="ss">:raw</span> <span class="k">do</span>
		<span class="n">u8</span> <span class="vi">@x</span>
		<span class="n">u8</span> <span class="vi">@y</span>
		<span class="n">u8</span> <span class="vi">@x</span> <span class="o">+</span> <span class="vi">@y</span>
		<span class="n">string</span> <span class="vi">@text</span>
	<span class="k">end</span>
<span class="k">end</span>

<span class="n">obj</span> <span class="o">=</span> <span class="no">MyData</span><span class="p">.</span><span class="nf">new</span>
<span class="n">obj</span><span class="p">.</span><span class="nf">x</span><span class="p">,</span> <span class="n">obj</span><span class="p">.</span><span class="nf">y</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span>
<span class="n">obj</span><span class="p">.</span><span class="nf">text</span> <span class="o">=</span> <span class="s2">"Hello, World!"</span>
<span class="n">bin</span> <span class="o">=</span> <span class="n">obj</span><span class="p">.</span><span class="nf">write</span> <span class="c1"># =&gt; "Poly\xEE\xBC\xEC\xB3..."</span>

<span class="n">obj2</span> <span class="o">=</span> <span class="no">MyData</span><span class="p">.</span><span class="nf">new</span>
<span class="n">obj2</span><span class="p">.</span><span class="nf">read</span> <span class="n">bin</span>
<span class="nb">p</span> <span class="n">obj2</span><span class="p">.</span><span class="nf">x</span><span class="p">,</span> <span class="n">obj2</span><span class="p">.</span><span class="nf">y</span><span class="p">,</span> <span class="n">obj2</span><span class="p">.</span><span class="nf">text</span> <span class="c1"># =&gt; 1, 2, "Hello, World!"</span>
</code></pre>
<p>The semantics is clear: The <code>read do ... end</code> block defines a method <code>MyData#read</code> that populates the instance variables from binary data, where another method <code>MyData#read_raw</code>, defined by a <code>read :raw do ... end</code> block, is called to read some processed binary data. Similarly, the <code>write do ... end</code> block defines a method <code>MyData#write</code> that serializes the object into binary data, where another method <code>MyData#write_raw</code>, defined by a <code>write :raw do ... end</code> block, is called to write some processed binary data.</p>
<p>In those blocks, methods like <code>u8</code>, <code>u32</code>, <code>bytes</code>, and <code>string</code> are called. What is interesting is that they should be instance methods of <code>MyData</code> (because they are called in the same scope where the instance variables can be accessed directly), but they behave differently in the <code>read</code> and <code>write</code> blocks. For example, <code>u8</code> in the <code>read</code> block accepts no arguments and returns an integer read from data, while <code>u8</code> in the <code>write</code> block accepts an integer argument to write into data.</p>
<p>What we have here, is what I call the polymorphism on a single object: the same object has different behaviors in different contexts. In this case, it is the same <code>MyData</code> object that behaves differently in the <code>read</code> and <code>write</code> blocks. How can we achieve this?</p>
<p>One natural idea is to extend the object with a module that defines those polymorphic methods at the start of the block and to un-extend at the end of the block. The extended module should be different for the <code>read</code> and <code>write</code> blocks, so that the methods with the same name can have different implementations. The problem with this approach is that, if you look up the core library documentation of Ruby, you will find that there is no method to un-extend a module in pure Ruby. It is, however, possible to do this with C extensions, and there is a <a href="https://github.com/banister/remix" target="_blank" rel="external">gem</a> that has been developed for this exact purpose, but the gem is ancient and is not compatible with Ruby 1.9 and later.</p>
<p>The next idea, then, is to modify the extended module so that the methods are defined at the start of the block and removed at the end of the block. To create a module, to extend it, and to define all methods from another module <code>mod</code> in it, we can do the following:</p>
<pre><code><span class="kp">extend</span> <span class="n">ext</span> <span class="o">=</span> <span class="no">Module</span><span class="p">.</span><span class="nf">new</span>
<span class="n">mod</span><span class="p">.</span><span class="nf">instance_methods</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="n">ext</span><span class="p">.</span><span class="nf">define_method</span> <span class="n">_1</span><span class="p">,</span> <span class="n">mod</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="n">_1</span><span class="p">)</span> <span class="p">}</span>
</code></pre>
<p class="no-indent">
It is just as easy as that. Now, if <code>mod</code> is a module contains the definitions of the methods <code>u8</code>, <code>bytes</code>, etc., then they will now be available to <code>self</code>. After we are done, we can remove those methods from the extended module:
</p>
<pre><code><span class="n">ext</span><span class="p">.</span><span class="nf">instance_methods</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="n">ext</span><span class="p">.</span><span class="nf">remove_method</span> <span class="n">_1</span> <span class="p">}</span>
</code></pre>
<p class="no-indent">
It is also very easy. Now, although we still cannot un-extend <code>ext</code>, the methods are no longer available, which effectively makes the object behave as if it un-extended <code>ext</code>.
</p>
<p>A problem with this approach is that, if a method like <code>read</code> or <code>write</code> is called many times on the same object, then its <code>singleton_class.ancestors</code> will contain many garbage extended modules. Therefore, we should reuse the same extended module for the same object. At the start of the block in which the polymorphic methods are available, we should back up the original instance methods of the extended module, and at the end of the block, we should restore them. Another problem is that manipulating ancestors and methods is not thread-safe, so we need to lock the object while we are doing this. After considering all these problems, we can implement a <code>TempExtend</code> module like this:</p>
<pre><code><span class="k">module</span> <span class="nn">TempExtend</span>
	<span class="k">class</span> <span class="nc">Extendee</span> <span class="o">&lt;</span> <span class="no">Module</span>
		<span class="no">MUTEX</span> <span class="o">=</span> <span class="no">Mutex</span><span class="p">.</span><span class="nf">new</span>
		<span class="no">ALL</span> <span class="o">=</span> <span class="no">ObjectSpace</span><span class="o">::</span><span class="no">WeakMap</span><span class="p">.</span><span class="nf">new</span>
		<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">[]</span> <span class="n">receiver</span>
			<span class="n">e</span> <span class="o">=</span> <span class="no">MUTEX</span><span class="p">.</span><span class="nf">synchronize</span> <span class="k">do</span>
				<span class="k">break</span> <span class="no">ALL</span><span class="p">[</span><span class="n">receiver</span><span class="p">]</span> <span class="k">if</span> <span class="no">ALL</span><span class="p">[</span><span class="n">receiver</span><span class="p">]</span>
				<span class="no">TempExtend</span><span class="p">.</span><span class="nf">check_frozen</span> <span class="n">receiver</span>
				<span class="no">ALL</span><span class="p">[</span><span class="n">receiver</span><span class="p">]</span> <span class="o">=</span> <span class="n">new</span> <span class="n">receiver</span>
			<span class="k">end</span>
			<span class="nb">block_given?</span> <span class="p">?</span> <span class="n">e</span><span class="p">.</span><span class="nf">lock</span><span class="p">.</span><span class="nf">synchronize</span> <span class="p">{</span> <span class="k">yield</span> <span class="n">e</span> <span class="p">}</span> <span class="p">:</span> <span class="n">e</span>
		<span class="k">end</span>

		<span class="k">def</span> <span class="nf">initialize</span> <span class="n">receiver</span>
			<span class="k">super</span><span class="p">()</span>
			<span class="vi">@receiver</span> <span class="o">=</span> <span class="n">receiver</span><span class="p">.</span><span class="nf">extend</span> <span class="nb">self</span>
			<span class="vi">@lock</span> <span class="o">=</span> <span class="no">Monitor</span><span class="p">.</span><span class="nf">new</span>
		<span class="k">end</span>
		<span class="nb">attr_reader</span> <span class="ss">:lock</span>

		<span class="no">VS</span> <span class="o">=</span> <span class="sx">%i[public protected private]</span>
		<span class="k">def</span> <span class="nf">before</span> <span class="n">mod</span><span class="p">,</span> <span class="n">body</span>
			<span class="n">old</span> <span class="o">=</span> <span class="no">VS</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="nb">__send__</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">v</span><span class="si">}</span><span class="ss">_instance_methods"</span><span class="p">).</span><span class="nf">map</span> <span class="p">{</span> <span class="nb">instance_method</span> <span class="n">_1</span> <span class="p">}.</span><span class="nf">each</span> <span class="p">{</span> <span class="n">remove_method</span> <span class="n">_1</span><span class="p">.</span><span class="nf">name</span> <span class="p">}</span> <span class="p">}</span>
			<span class="no">VS</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">mod</span><span class="p">.</span><span class="nf">__send__</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">v</span><span class="si">}</span><span class="ss">_instance_methods"</span><span class="p">).</span><span class="nf">each</span> <span class="p">{</span> <span class="nb">__send__</span> <span class="n">v</span><span class="p">,</span> <span class="n">define_method</span><span class="p">(</span><span class="n">_1</span><span class="p">,</span> <span class="n">mod</span><span class="p">.</span><span class="nf">instance_method</span><span class="p">(</span><span class="n">_1</span><span class="p">))</span> <span class="p">}</span> <span class="p">}</span>
			<span class="n">meth</span> <span class="o">=</span> <span class="vi">@receiver</span><span class="p">.</span><span class="nf">define_singleton_method</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="p">).</span><span class="nf">each</span> <span class="p">{</span> <span class="vi">@receiver</span><span class="p">.</span><span class="nf">singleton_method</span><span class="p">(</span><span class="n">n</span> <span class="o">=</span> <span class="sc">?_</span><span class="o">*</span><span class="n">_1</span><span class="p">)</span> <span class="k">rescue</span> <span class="k">break</span> <span class="n">n</span> <span class="p">},</span> <span class="o">&amp;</span><span class="n">body</span>
			<span class="p">[</span><span class="n">old</span><span class="p">,</span> <span class="n">meth</span><span class="p">]</span>
		<span class="k">end</span>

		<span class="k">def</span> <span class="nf">after</span> <span class="n">old</span><span class="p">,</span> <span class="n">meth</span>
			<span class="vi">@receiver</span><span class="p">.</span><span class="nf">singleton_class</span><span class="p">.</span><span class="nf">remove_method</span> <span class="n">meth</span>
			<span class="no">VS</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="nb">__send__</span><span class="p">(</span><span class="ss">:"</span><span class="si">#{</span><span class="n">v</span><span class="si">}</span><span class="ss">_instance_methods"</span><span class="p">).</span><span class="nf">each</span> <span class="p">{</span> <span class="n">remove_method</span> <span class="n">_1</span> <span class="p">}</span> <span class="p">}</span>
			<span class="no">VS</span><span class="p">.</span><span class="nf">zip</span><span class="p">(</span><span class="n">old</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="p">,</span> <span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="nb">__send__</span> <span class="n">v</span><span class="p">,</span> <span class="n">define_method</span><span class="p">(</span><span class="n">_1</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span> <span class="n">_1</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span>
		<span class="k">end</span>
	<span class="k">end</span>

	<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">check_frozen</span> <span class="n">object</span>
		<span class="k">return</span> <span class="k">if</span> <span class="p">[</span><span class="n">object</span><span class="p">,</span> <span class="n">object</span><span class="p">.</span><span class="nf">singleton_class</span><span class="p">].</span><span class="nf">none?</span> <span class="o">&amp;</span><span class="ss">:frozen?</span>
		<span class="k">raise</span> <span class="no">FrozenError</span><span class="p">.</span><span class="nf">new</span> <span class="s2">"can't modify frozen </span><span class="si">#{</span><span class="n">object</span><span class="p">.</span><span class="nf">class</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">object</span><span class="p">.</span><span class="nf">inspect</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="ss">receiver: </span><span class="n">object</span>
	<span class="k">end</span>

	<span class="k">def</span> <span class="nf">def_extend</span> <span class="nb">name</span><span class="p">,</span> <span class="n">mod</span><span class="p">,</span> <span class="ss">before: </span><span class="kp">nil</span><span class="p">,</span> <span class="ss">after: </span><span class="kp">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">body</span>
		<span class="n">define_method</span> <span class="nb">name</span> <span class="k">do</span> <span class="o">|*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">opts</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="o">|</span>
			<span class="no">Extendee</span><span class="p">[</span><span class="nb">self</span><span class="p">]</span> <span class="k">do</span> <span class="o">|</span><span class="n">ext</span><span class="o">|</span>
				<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">before</span> <span class="k">if</span> <span class="n">before</span>
				<span class="no">TempExtend</span><span class="p">.</span><span class="nf">check_frozen</span> <span class="nb">self</span>
				<span class="n">old</span><span class="p">,</span> <span class="n">meth</span> <span class="o">=</span> <span class="n">ext</span><span class="p">.</span><span class="nf">before</span> <span class="n">mod</span><span class="p">,</span> <span class="n">body</span>
				<span class="k">begin</span>
					<span class="nb">__send__</span> <span class="n">meth</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">opts</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
				<span class="k">ensure</span>
					<span class="n">ext</span><span class="p">.</span><span class="nf">after</span> <span class="n">old</span><span class="p">,</span> <span class="n">meth</span>
					<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">after</span> <span class="k">if</span> <span class="n">after</span>
				<span class="k">end</span>
			<span class="k">end</span>
		<span class="k">end</span>
	<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p class="no-indent">
There is a notable interesting pattern here when defining <code>meth</code> (though it is not closely related to the topic of this article) as it finds an unused singleton method name to define a singleton method with the given block to circumvent the limitation of <code>instance_exec</code> that it cannot pass a block; I opened a feature request for <a href="https://bugs.ruby-lang.org/issues/22486" target="_blank" rel="external">anonymous methods</a> hoping that the same workaround is not needed in the future.
</p>
<p>We can now finally define our version of <code>Binary</code> module, extended by <code>MyData</code>, that enables it to use <code>read</code> and <code>write</code> methods to define blocks for polymorphic methods.</p>
<pre><code><span class="k">module</span> <span class="nn">Binary</span>
	<span class="kp">include</span> <span class="no">TempExtend</span>

	<span class="no">BEFORE</span> <span class="o">=</span> <span class="nb">proc</span> <span class="p">{</span> <span class="p">(</span><span class="vi">@stack</span> <span class="o">||=</span> <span class="p">[]).</span><span class="nf">push</span> <span class="p">[</span><span class="vi">@data</span><span class="p">,</span> <span class="vi">@pos</span><span class="p">]</span> <span class="k">if</span> <span class="vi">@data</span> <span class="p">}</span>
	<span class="no">AFTER</span> <span class="o">=</span> <span class="nb">proc</span> <span class="p">{</span> <span class="vi">@data</span><span class="p">,</span> <span class="vi">@pos</span> <span class="o">=</span> <span class="vi">@stack</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">pop</span> <span class="p">}</span>

	<span class="k">def</span> <span class="nf">read</span> <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
		<span class="n">def_extend</span> <span class="nb">name</span> <span class="p">?</span> <span class="p">:</span><span class="s2">"read_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span> <span class="p">:</span> <span class="ss">:read</span><span class="p">,</span> <span class="no">Reader</span><span class="p">,</span> <span class="ss">before: </span><span class="no">BEFORE</span><span class="p">,</span> <span class="ss">after: </span><span class="no">AFTER</span> <span class="k">do</span> <span class="o">|</span><span class="n">data</span><span class="p">,</span> <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="o">|</span>
			<span class="vi">@data</span> <span class="o">=</span> <span class="n">data</span>
			<span class="vi">@pos</span> <span class="o">=</span> <span class="n">pos</span>
			<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">block</span>
			<span class="vi">@pos</span>
		<span class="k">end</span>
	<span class="k">end</span>

	<span class="k">def</span> <span class="nf">write</span> <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
		<span class="n">def_extend</span> <span class="nb">name</span> <span class="p">?</span> <span class="p">:</span><span class="s2">"write_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span> <span class="p">:</span> <span class="ss">:write</span><span class="p">,</span> <span class="no">Writer</span><span class="p">,</span> <span class="ss">before: </span><span class="no">BEFORE</span><span class="p">,</span> <span class="ss">after: </span><span class="no">AFTER</span> <span class="k">do</span> <span class="o">|</span><span class="n">data</span> <span class="o">=</span> <span class="s1">''</span><span class="p">.</span><span class="nf">b</span><span class="o">|</span>
			<span class="vi">@data</span> <span class="o">=</span> <span class="n">data</span>
			<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">block</span>
			<span class="vi">@data</span>
		<span class="k">end</span>
	<span class="k">end</span>

	<span class="k">module</span> <span class="nn">Reader</span>
		<span class="k">def</span> <span class="nf">u8</span> <span class="o">=</span> <span class="n">bytes</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nf">unpack1</span> <span class="p">?</span><span class="no">C</span>
		<span class="k">def</span> <span class="nf">u16</span> <span class="o">=</span> <span class="n">bytes</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">unpack1</span> <span class="p">?</span><span class="n">v</span>
		<span class="k">def</span> <span class="nf">u32</span> <span class="o">=</span> <span class="n">bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">unpack1</span> <span class="p">?</span><span class="no">V</span>
		<span class="k">def</span> <span class="nf">var_uint</span> <span class="o">=</span> <span class="p">(</span><span class="n">n</span> <span class="o">=</span> <span class="n">u8</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0xff</span> <span class="p">?</span> <span class="p">(</span><span class="n">n</span> <span class="o">=</span> <span class="n">u16</span><span class="p">)</span> <span class="o">==</span> <span class="mh">0xffff</span> <span class="p">?</span> <span class="n">u32</span> <span class="p">:</span> <span class="n">n</span> <span class="p">:</span> <span class="n">n</span>
		<span class="k">def</span> <span class="nf">string</span> <span class="o">=</span> <span class="n">bytes</span><span class="p">(</span><span class="n">var_uint</span><span class="p">).</span><span class="nf">force_encoding</span> <span class="no">Encoding</span><span class="o">::</span><span class="no">UTF_8</span>
		<span class="k">def</span> <span class="nf">rest</span> <span class="o">=</span> <span class="n">bytes</span> <span class="vi">@data</span><span class="p">.</span><span class="nf">bytesize</span> <span class="o">-</span> <span class="vi">@pos</span>
		<span class="k">def</span> <span class="nf">bytes</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">=</span> <span class="vi">@data</span><span class="p">[</span><span class="vi">@pos</span><span class="o">...</span><span class="vi">@pos</span> <span class="o">+=</span> <span class="n">n</span><span class="p">]</span>
	<span class="k">end</span>

	<span class="k">module</span> <span class="nn">Writer</span>
		<span class="k">def</span> <span class="nf">u8</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="n">bytes</span> <span class="p">[</span><span class="n">v</span><span class="p">].</span><span class="nf">pack</span> <span class="p">?</span><span class="no">C</span>
		<span class="k">def</span> <span class="nf">u16</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="n">bytes</span> <span class="p">[</span><span class="n">v</span><span class="p">].</span><span class="nf">pack</span> <span class="p">?</span><span class="n">v</span>
		<span class="k">def</span> <span class="nf">u32</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="n">bytes</span> <span class="p">[</span><span class="n">v</span><span class="p">].</span><span class="nf">pack</span> <span class="p">?</span><span class="no">V</span>
		<span class="k">def</span> <span class="nf">var_uint</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="mh">0xff</span> <span class="p">?</span> <span class="n">u8</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">:</span> <span class="p">(</span><span class="n">u8</span> <span class="mh">0xff</span><span class="p">;</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="mh">0xffff</span> <span class="p">?</span> <span class="n">u16</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">:</span> <span class="p">(</span><span class="n">u16</span> <span class="mh">0xffff</span><span class="p">;</span> <span class="n">u32</span> <span class="n">v</span><span class="p">))</span>
		<span class="k">def</span> <span class="nf">string</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">var_uint</span> <span class="n">v</span><span class="p">.</span><span class="nf">bytesize</span><span class="p">;</span> <span class="n">bytes</span> <span class="n">v</span><span class="p">.</span><span class="nf">encoding</span> <span class="o">==</span> <span class="no">Encoding</span><span class="o">::</span><span class="no">BINARY</span> <span class="p">?</span> <span class="n">v</span> <span class="p">:</span> <span class="n">v</span><span class="p">.</span><span class="nf">b</span><span class="p">)</span>
		<span class="k">def</span> <span class="nf">bytes</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span> <span class="vi">@data</span><span class="p">.</span><span class="nf">concat</span> <span class="n">v</span>
	<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>For Ruby 4.1 and later, there is a new method <code>Proc#refined</code> that can be used to modify the methods available in a block. Our <code>TempExtend</code> module is of no use anymore, and we can simplify the <code>Binary</code> module as follows:</p>
<pre><code><span class="k">module</span> <span class="nn">Binary</span>
	<span class="no">BEFORE</span> <span class="o">=</span> <span class="nb">proc</span> <span class="p">{</span> <span class="p">(</span><span class="vi">@stack</span> <span class="o">||=</span> <span class="p">[]).</span><span class="nf">push</span> <span class="p">[</span><span class="vi">@data</span><span class="p">,</span> <span class="vi">@pos</span><span class="p">]</span> <span class="k">if</span> <span class="vi">@data</span> <span class="p">}</span>
	<span class="no">AFTER</span> <span class="o">=</span> <span class="nb">proc</span> <span class="p">{</span> <span class="vi">@data</span><span class="p">,</span> <span class="vi">@pos</span> <span class="o">=</span> <span class="vi">@stack</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">pop</span> <span class="p">}</span>

	<span class="k">def</span> <span class="nf">read</span> <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
		<span class="n">block</span> <span class="o">=</span> <span class="n">block</span><span class="p">.</span><span class="nf">refined</span> <span class="no">Module</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="n">_1</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:refine</span><span class="p">,</span> <span class="nb">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">import_methods</span> <span class="no">Reader</span> <span class="p">}</span> <span class="p">}</span>
		<span class="n">define_method</span> <span class="nb">name</span> <span class="p">?</span> <span class="p">:</span><span class="s2">"read_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span> <span class="p">:</span> <span class="ss">:read</span> <span class="k">do</span> <span class="o">|</span><span class="n">data</span><span class="p">,</span> <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="o">|</span>
			<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="no">BEFORE</span>
			<span class="vi">@data</span> <span class="o">=</span> <span class="n">data</span>
			<span class="vi">@pos</span> <span class="o">=</span> <span class="n">pos</span>
			<span class="k">begin</span>
				<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">block</span>
				<span class="vi">@pos</span>
			<span class="k">ensure</span>
				<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="no">AFTER</span>
			<span class="k">end</span>
		<span class="k">end</span>
	<span class="k">end</span>

	<span class="k">def</span> <span class="nf">write</span> <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span>
		<span class="n">block</span> <span class="o">=</span> <span class="n">block</span><span class="p">.</span><span class="nf">refined</span> <span class="no">Module</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="n">_1</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:refine</span><span class="p">,</span> <span class="nb">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">import_methods</span> <span class="no">Writer</span> <span class="p">}</span> <span class="p">}</span>
		<span class="n">define_method</span> <span class="nb">name</span> <span class="p">?</span> <span class="p">:</span><span class="s2">"write_</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span> <span class="p">:</span> <span class="ss">:write</span> <span class="k">do</span> <span class="o">|</span><span class="n">data</span> <span class="o">=</span> <span class="s1">''</span><span class="p">.</span><span class="nf">b</span><span class="o">|</span>
			<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="no">BEFORE</span>
			<span class="vi">@data</span> <span class="o">=</span> <span class="n">data</span>
			<span class="k">begin</span>
				<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="n">block</span>
				<span class="vi">@data</span>
			<span class="k">ensure</span>
				<span class="nb">instance_eval</span> <span class="o">&amp;</span><span class="no">AFTER</span>
			<span class="k">end</span>
		<span class="k">end</span>
	<span class="k">end</span>

	<span class="k">module</span> <span class="nn">Reader</span>
		<span class="c1"># ...</span>
	<span class="k">end</span>
	<span class="k">module</span> <span class="nn">Writer</span>
		<span class="c1"># ...</span>
	<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p class="no-indent">
Not only is this approach cleaner, it is also effectively better: the extended module is now invisible in the ancestors of the object, and the methods are now truly local to the block protected by the lexical scope of the block. The latter advantage is, however, also a disadvantage in some use cases: if you want to define a method in <code>MyData</code> outside of <code>Binary::Reader</code> or <code>Binary::Writer</code> that calls methods like <code>u8</code>, it will not work because the method definition is outside of the lexical scope of the block.
</p>]]></content><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><category term="programming" /><category term="ruby" /><category term="metaprogramming" /><summary type="html"><![CDATA[By dynamically redefining singleton methods on an object and instance methods on an extended module, we can achieve polymorphism on a single object based on the context. With the new <code>Proc#refined</code> method in Ruby 4.1, this can be done in a simpler way.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ulysseszh.github.io/assets/images/covers/2026-11-10-single-polymorphism.png" /><media:content medium="image" url="https://ulysseszh.github.io/assets/images/covers/2026-11-10-single-polymorphism.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>