Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby is the Best Javascript

Kevin Kuchta
November 15, 2018
360

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.

Kevin Kuchta

November 15, 2018
Tweet

Transcript

  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. @kkuchta Good ideas • Opal: ruby -> js • Therubyracer:

    js in ruby • Our thing: ruby == js will not feature heavily in this talk
  3. @kkuchta Taking it further var first = 3; var second

    = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second));
  4. @kkuchta class Console def log(input) puts input end end console

    = Console.new() console.log("something") Console.foo
  5. @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. @kkuchta Console.foo console = Object.new def console.log(*x) puts x.join(',') end

    console.log('hello', 'world') # prints "hello,world"
  7. @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. @kkuchta Functions var sum = function(a, b) { return a

    + b; } def function(*args, &block) return Proc.new { ... } end
  9. @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
  10. @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
  11. @kkuchta Instance Eval obj = Class .new { attr_accessor :a,

    :b } .new obj.a = 3 obj.b = 4 obj.instance_eval { a + b }
  12. @kkuchta Function Function def function(*args, &block) function_block = Proc.new {

    |*arg_values| } return function_block end [3, 4] [:a, :b] { a + b }
  13. @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]
  14. @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 }
  15. @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 }
  16. @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 }
  17. @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]
  18. @kkuchta So Close-er var func_1234 = function(a, b) { a

    + b } define_method(:sum, &func_1234) puts sum(3, 4) # 7
  19. @kkuchta eval eval("3 + 4") # 7 x = 5

    eval("x + 1") # 6 eval("puts 'Hello, #{input}!'") input = "Ted';User.delete_all;'" # Oh dear
  20. @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
  21. @kkuchta Var Redux def var(_);end var sum = function(...) {

    ... } var(sum = function(...) { ... }) var(sum = "func_1234")
  22. @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"
  23. @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"
  24. @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) } }
  25. @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
  26. @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
  27. @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));