I tried to reimplement Ruby Float (arg, exception: true) with builtin

Introduction

This article is an article that redefines the method Float (arg, exception: true) using builtin introduced in the following article I wrote earlier.

I implemented Ruby with Ruby (and C) (I played with builtin)

What you are doing is roughly the same as in the previous article. The difference from the last time is that I tried to redefine the method that can be called anywhere with builtin instead of the method of a specific class.

After writing the previous article, I was wondering if methods such as raise and Float could support builtin, so I felt like trying it out.

From the conclusion, it seems that such global methods can also be supported by builtin (although it may not be recommended because we have not confirmed whether it is the intended behavior ...)

What is builtin?

builtin is to implement Ruby itself in Ruby (and C). For details, please read the previous article.

I implemented Ruby with Ruby (and C) (I played with builtin)

Environment

The environment of the previous article is used as it is. There are no major changes.

I implemented Ruby with Ruby (and C) (I played with builtin)

I tried it

Find the implementation location of the Float method

First, find the source that implements the Float method. Ruby methods are generally defined like this.

rb_define_method(rb_mKernel, "to_s", rb_any_to_s, 0);

The first argument is the variable of the class / module in which the method is defined. Generally, it is created by class name or module name such as rb_cArray. The second argument " to_s " is the method name called on the Ruby side. The third argument is a C function that is executed when the method is called, and the last argument specifies the number of arguments the method receives.

This time, I want to reimplement the Float method, so let's search in the Ruby source code withgit grep \ "Float \"etc. Then, it seems that it is implemented as follows in ʻobject.c`.

rb_define_global_function("Float", rb_f_float, -1);

Let's reimplement it.

Reimplementation of Float method

In the previous article, I reimplemented Hash # delete. Also, to support builtin, I modified common.mk etc. However, in the current Ruby master code, Kernel # clone, which was implemented in ʻobject.c`, is made compatible with builtin, so there is no need to modify them.

There are two sources that need to be modified: ʻobject.c and kernel.rb`.

Modify object.c

In ʻobject.c, first delete the following code that defines the Float` method.

void
InitVM_Object(void)
{
    Init_class_hierarchy();

    //abridgement
-    rb_define_global_function("Float", rb_f_float, -1);
    //abridgement
}

Next, modify the rb_f_float that defines the processing of the Float method as follows.

- /*	
-  *  call-seq:	
-  *     Float(arg, exception: true)    -> float or nil	
-  *	
-  *  Returns <i>arg</i> converted to a float. Numeric types are	
-  *  converted directly, and with exception to String and	
-  *  <code>nil</code> the rest are converted using	
-  *  <i>arg</i><code>.to_f</code>.  Converting a String with invalid	
-  *  characters will result in a ArgumentError.  Converting	
-  *  <code>nil</code> generates a TypeError.  Exceptions can be	
-  *  suppressed by passing <code>exception: false</code>.	
-  *	
-  *     Float(1)                 #=> 1.0	
-  *     Float("123.456")         #=> 123.456	
-  *     Float("123.0_badstring") #=> ArgumentError: invalid value for Float(): "123.0_badstring"	
-  *     Float(nil)               #=> TypeError: can't convert nil into Float	
-  *     Float("123.0_badstring", exception: false)  #=> nil	
-  */	

static VALUE	static VALUE
- rb_f_float(int argc, VALUE *argv, VALUE obj)
+ rb_f_float(rb_execution_context_t *ec, VALUE main, VALUE arg, VALUE opts)
{
-    VALUE arg = Qnil, opts = Qnil;	
-
-    rb_scan_args(argc, argv, "1:", &arg, &opts);	
-    return rb_convert_to_float(arg, opts_exception_p(opts));	    
+   return rb_convert_to_float(arg, opts_exception_p(opts));
}

Since it uses builtin and receives keyword arguments, it is set to VALUE main, VALUE arg, VALUE opts instead of ʻint argc, VALUE * argv. Also, VALUE main` is added to take the arguments that the global method implicitly receives instead.

This completes the modification in ʻobject.c`!

Fix kernel.rb

In Ruby, global methods that can be called from anywhere can be redefined in the Kernel module.

For example, the puts method can be monkey patched as follows:

module Kernel
   def puts *args
      p "hoge" 
   end
end


puts :ho
#=> "hoge"Is displayed

Use this to add the Float method in kernel.rb as shown below.

module Kernel
+ 
+   #
+   #  call-seq:
+   #     Float(arg, exception: true)    -> float or nil
+   #
+   #  Returns <i>arg</i> converted to a float. Numeric types are
+   #  converted directly, and with exception to String and
+   #  <code>nil</code> the rest are converted using
+   #  <i>arg</i><code>.to_f</code>.  Converting a String with invalid
+   #  characters will result in a ArgumentError.  Converting
+   #  <code>nil</code> generates a TypeError.  Exceptions can be
+   #  suppressed by passing <code>exception: false</code>.
+   #
+   #     Float(1)                 #=> 1.0
+   #     Float("123.456")         #=> 123.456
+   #     Float("123.0_badstring") #=> ArgumentError: invalid value for Float(): "123.0_badstring"
+   #     Float(nil)               #=> TypeError: can't convert nil into Float
+   #     Float("123.0_badstring", exception: false)  #=> nil
+   #
+   def Float(arg, exception: true)
+     __builtin_rb_f_float(arg, exception)
+   end
+ 
  #
  #  call-seq:
  #     obj.clone(freeze: nil) -> an_object
  #
  #  Produces a shallow copy of <i>obj</i>---the instance variables of
  #  <i>obj</i> are copied, but not the objects they reference.
  #  #clone copies the frozen value state of <i>obj</i>, unless the
  #  +:freeze+ keyword argument is given with a false or true value.
  #  See also the discussion under Object#dup.
  #
  #     class Klass
  #        attr_accessor :str
  #     end
  #     s1 = Klass.new      #=> #<Klass:0x401b3a38>
  #     s1.str = "Hello"    #=> "Hello"
  #     s2 = s1.clone       #=> #<Klass:0x401b3998 @str="Hello">
  #     s2.str[1,4] = "i"   #=> "i"
  #     s1.inspect          #=> "#<Klass:0x401b3a38 @str=\"Hi\">"
  #     s2.inspect          #=> "#<Klass:0x401b3998 @str=\"Hi\">"
  #
  #  This method may have class-specific behavior.  If so, that
  #  behavior will be documented under the #+initialize_copy+ method of
  #  the class.
  #
  def clone(freeze: nil)
    __builtin_rb_obj_clone2(freeze)
  end
end

The rb_f_float that was made compatible with builtin earlier is called with __builtin_rb_f_float. Now the fix in kernel.rb is OK.

Finally

After that, if you can build by executing make and make install, the implementation with builtin is completed.

in conclusion

It seems that methods such as Float and ʻInteger` can be built in like this. Regardless of whether it was sent as a patch, it was interesting that it seemed to be quite applicable.

By the way, when I tried the following benchmarks, I felt that I could expect performance improvement.

benchmark:
  float: "Float(42)"
  float_true: "Float(42, exception: true)"
  float_false: "Float(42, exception: false)"
loop_count: 10000

Results below

Calculating -------------------------------------
                     compare-ruby  built-ruby
               float      37.495M     15.878M i/s -     10.000k times in 0.000267s 0.000630s
          float_true       1.109M      1.211M i/s -     10.000k times in 0.009015s 0.008261s
         float_false       1.227M      1.191M i/s -     10.000k times in 0.008150s 0.008395s

Comparison:
                            float
        compare-ruby:  37495314.0 i/s
          built-ruby:  15878056.0 i/s - 2.36x  slower

                       float_true
          built-ruby:   1210507.2 i/s
        compare-ruby:   1109250.0 i/s - 1.09x  slower

                      float_false
        compare-ruby:   1226948.7 i/s
          built-ruby:   1191128.5 i/s - 1.03x  slower

If a method such as Float is built in, I think that code maintainability will be more important than performance.

Recommended Posts

I tried to reimplement Ruby Float (arg, exception: true) with builtin
I tried DI with Ruby
I tried to automate LibreOffice Calc with Ruby + PyCall.rb (Ubuntu 18.04)
I tried to solve the problem of "multi-stage selection" with Ruby
I tried to get started with WebAssembly
I tried to build Ruby 3.0.0 from source
I tried to implement ModanShogi with Kinx
[Ruby] I tried to diet the if statement code with the ternary operator
I tried to solve the tribonacci sequence problem in Ruby, with recursion.
I tried to make Basic authentication with Java
I tried to manage struts configuration with Coggle
I tried to manage login information with JMX
I implemented Ruby with Ruby (and C) (I played with builtin)
I made blackjack with Ruby (I tried using minitest)
[Ruby basics] I tried to learn modules (Chapter 1)
I tried to break a block with java (1)
I tried to get the distance from the address string to the nearest station with ruby
I tried what I wanted to try with Stream softly.
I tried to implement file upload with Spring MVC
I tried to implement TCP / IP + BIO with JAVA
[Beginner's point of view] I tried to solve the FizzBuzz problem "easily" with Ruby!
I started MySQL 5.7 with docker-compose and tried to connect
I tried to get started with Spring Data JPA
I tried to draw animation with Blazor + canvas API
I tried to implement Stalin sort with Java Collector
roman numerals (I tried to simplify it with hash)
[Swift] I tried to implement exception handling for vending machines
I tried to make an introduction to PHP + MySQL with Docker
I tried to create a java8 development environment with Chocolatey
I tried to modernize a Java EE application with OpenShift.
I tried to increase the processing speed with spiritual engineering
[Rails] I tried to create a mini app with FullCalendar
I tried to summarize the basic grammar of Ruby briefly
I tried to link chat with Minecraft server with Discord API
[Rails] I tried to implement batch processing with Rake task
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I tried to create a padrino development environment with Docker
I tried to get started with Swagger using Spring Boot
I tried upgrading from CentOS 6.5 to CentOS 7 with the upgrade tool
I tried to be able to pass multiple objects with Ractor
I tried UPSERT with PostgreSQL.
I tried BIND with Docker
I tried to verify yum-cron
I tried Jets (ruby serverless)
I tried to create an API to get data from a spreadsheet in Ruby (with service account)
I want to add a browsing function with ruby on rails
I tried to build the environment of PlantUML Server with Docker
I tried connecting to MySQL using JDBC Template with Spring MVC
I tried to write code like a type declaration in Ruby
[Ruby] Tonight, I tried to summarize the loop processing [times, break ...]
I tried to implement the image preview function with Rails / jQuery
I tried to build an http2 development environment with Eclipse + Tomcat
I tried to implement flexible OR mapping with MyBatis Dynamic SQL
I tried connecting to Oracle Autonomous Database 21c with JDBC Thin
I tried to make an Android application with MVC now (Java)
I tried to check the operation of gRPC server with grpcurl
I tried to make Numeron which is not good in Ruby
I tried to make a group function (bulletin board) with Rails
I tried to chew C # (indexer)
I tried using JOOQ with Gradle
I tried morphological analysis with MeCab