Ruby is the Best Javascript

B5c79b428fca86c70fd59d1c27a980d8?s=47 Kevin Kuchta
November 15, 2018
180

Ruby is the Best Javascript

Some people love Ruby and some people love Javascript. Both will hate this talk. In a display of complete moral depravity, we'll abuse flexible syntax and metaprogramming to make valid Ruby code that's indistinguishable from Javascript. We'll use a variety of tricks to accomplish it that you can take home and use in more respectable code.

B5c79b428fca86c70fd59d1c27a980d8?s=128

Kevin Kuchta

November 15, 2018
Tweet

Transcript

  1. 1.

    @kkuchta This is ruby var first = 3; var second

    = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second));
  2. 3.

    @kkuchta Good ideas • Opal: ruby -> js • Therubyracer:

    js in ruby • Our thing: ruby == js will not feature heavily in this talk
  3. 5.

    @kkuchta Taking it further var first = 3; var second

    = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second));
  4. 8.

    @kkuchta class Console def log(input) puts input end end console

    = Console.new() console.log("something") Console.foo
  5. 9.

    @kkuchta Splat: * def some_func(*my_args) puts my_args[0] puts my_args[1] puts

    my_args[2] end some_func('first arg', 'second arg', 'third arg')
  6. 16.

    @kkuchta Console.foo console = Object.new def console.log(*x) puts x.join(',') end

    console.log('hello', 'world') # prints "hello,world"
  7. 17.

    @kkuchta Anonymous Functions // Javascript var sum = function(a,b){ return

    a + b } sum(3, 4) # ruby sum = Proc.new { |a, b| a + b } sum.call(3, 4) sum[3, 4]
  8. 18.

    @kkuchta Functions var sum = function(a, b) { return a

    + b; } def function(*args, &block) return Proc.new { ... } end
  9. 21.
  10. 22.

    @kkuchta GlobalMissing class Object def method_missing(*args) return args[0] end end

    3.whatever # :whatever function(a, b) { return a + b } a # :a b # :b
  11. 23.

    @kkuchta Caveat def method_missing(*args) skip_methods = %i( to_a to_hash to_io

    to_str to_ary to_int ) return nil if skip_methods.include?(args[0]) return args[0] end
  12. 25.
  13. 27.

    @kkuchta Instance Eval obj = Class .new { attr_accessor :a,

    :b } .new obj.a = 3 obj.b = 4 obj.instance_eval { a + b }
  14. 29.

    @kkuchta Function Function def function(*args, &block) function_block = Proc.new {

    |*arg_values| } return function_block end [3, 4] [:a, :b] { a + b }
  15. 30.

    @kkuchta Function Function def function(*args, &block) klass = Class.new {

    attr_accessor *args } function_block = Proc.new { |*arg_values| } return function_block end [:a, :b] { a + b } [3, 4]
  16. 31.

    @kkuchta Function Function def function(*args, &block) klass = Class.new {

    attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new } return function_block end [3, 4] [:a, :b] { a + b }
  17. 32.

    @kkuchta Function Function def function(*args, &block) klass = Class.new {

    attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new obj.a = 3 obj.b = 4 } return function_block end [3, 4] [:a, :b] { a + b }
  18. 33.

    @kkuchta Function Function def function(*args, &block) klass = Class.new {

    attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new args.zip(arg_values).each { |arg, arg_value| obj.send(:"#{arg}=", arg_value) } } return function_block end [3, 4] [:a, :b] { a + b }
  19. 34.

    @kkuchta Function Function def function(*args, &block) klass = Class.new {

    attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new args.zip(arg_values).each { |arg, arg_value| obj.send(:"#{arg}=", arg_value) } obj.instance_eval(&block) } return function_block end [:a, :b] { a + b } [3, 4]
  20. 35.
  21. 36.
  22. 37.

    @kkuchta So Close-er var func_1234 = function(a, b) { a

    + b } define_method(:sum, &func_1234) puts sum(3, 4) # 7
  23. 39.

    @kkuchta eval eval("3 + 4") # 7 x = 5

    eval("x + 1") # 6 eval("puts 'Hello, #{input}!'") input = "Ted';User.delete_all;'" # Oh dear
  24. 40.

    @kkuchta Back to the Function def function(*args, &block) ... function_block

    = Proc.new {...} ... func_name = :"func_#{rand(1000000)}" define_method(func_name, &function_block) func_name # "func_1234" end
  25. 41.

    @kkuchta Var Redux def var(_);end var sum = function(...) {

    ... } var(sum = function(...) { ... }) var(sum = "func_1234")
  26. 44.

    @kkuchta Var Redux define_method(:var) { |random_function_name| var_name = local_variables.find do

    |local_var| local_var != :random_function_name && eval(local_var.to_s) == random_function_name end # var_name should be "sum" } "func_1234"
  27. 45.

    @kkuchta Var Redux define_method(:var) { |random_function_name| var_name = local_variables.find do

    |local_var| local_var != :random_function_name && eval(local_var.to_s) == random_function_name end # var_name should be "sum" define_method(var_name) { |*args| send(random_function_name, *args) } # sum(...) -> func_1234(...) } "func_1234"
  28. 47.

    @kkuchta Tiny Text Success! def function(*args, &block) func_name = :"func_#{rand(1000000)}"

    klass = Class.new { attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new args.zip(arg_values).each {|arg, arg_value| obj.send(:"#{arg}=", arg_value) } obj.instance_eval(&block) } define_method(func_name, &function_block) func_name end define_method(:var) { |random_function_name| var_name = local_variables.find do |local_var| local_var != :random_function_name && eval(local_var.to_s) == random_function_name end define_method(var_name) { |*args| send(random_function_name, *args) } }
  29. 48.

    @kkuchta This is ruby var first = 3; var second

    = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second)); I'm @kkuchta
  30. 49.

    @kkuchta If you're reading this after the talk, you might

    have noticed a bug: we're missing `return`. `function sum(a,b) { return a + b }` is the correct javascript, but it'll fail in our ruby code. This was a blatant oversight on my part in the original blog post I wrote around this idea. Credit for a simple fix goes to /u/TikiTDO on Reddit: we just need to ditch the cool instance_eval and instead actually define a _method_ on our `obj`: def function(*args, &block) func_name = :"func_#{rand(1000000)}" impl_name = :"func_#{rand(1000000)}" klass = Class.new { attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new klass.send(:define_method, impl_method_name, &block) args.zip(arg_values).each {|arg, arg_value| obj.send(:"#{arg} =", arg_value) } obj.send(impl_method_name) } define_method(func_name, &function_block) func_name end
  31. 50.

    @kkuchta Bonus: here's a complete version of the code that

    you can actually run: console = Object.new def console.log(*x) puts x.map(&:to_s).join(',') end class Object def method_missing(*args) return args[0] end end def function(*args, &block) func_name = :"func_#{rand(1000000)}" impl_name = :"func_#{rand(1000000)}" klass = Class.new { attr_accessor *args } function_block = Proc.new { |*arg_values| obj = klass.new klass.send(:define_method, impl_method_name, &block) args.zip(arg_values).each {|arg, arg_value| obj.send(:"#{arg}=", arg_value) } obj.send(impl_method_name) } define_method(func_name, &function_block) func_name end define_method(:var) { |random_function_name| var_name = local_variables.find do |local_var| local_var != :random_function_name && eval(local_var.to_s) == random_function_name end define_method(var_name) { |*args| send(random_function_name, *args) } } var foo = function(a, b) { return a + b } var first = 3; var second = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second));