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

Ruby is the Best Javascript

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for Kevin Kuchta Kevin Kuchta
November 15, 2018
440

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.

Avatar for Kevin Kuchta

Kevin Kuchta

November 15, 2018

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));