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. @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 Ruby is the best javascript Kevin Kuchta | he/him

    | Joyable Inc
  3. @kkuchta Good ideas • Opal: ruby -> js • Therubyracer:

    js in ruby • Our thing: ruby == js will not feature heavily in this talk
  4. @kkuchta Inspiration

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

    = 4; var sum = function(a, b) { a + b; } console.log("Sum = ", sum(first, second));
  6. @kkuchta def var(_);end var(first = 3) var second = 4;

  7. @kkuchta console.log("something") Console.foo

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

    = Console.new() console.log("something") Console.foo
  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')
  10. @kkuchta Splat: * def log(*args) puts args.join(',') end log('Hey', 'world')

  11. @kkuchta Classes class Foo ... end bar = Foo.new

  12. @kkuchta Classes bar.class # Foo Foo.class # Class Class.class #

    Class
  13. @kkuchta Anonymous Classes Foo = Class.new do ... end bar

    = Foo.new
  14. @kkuchta Anonymous Classes bar = (Class.new { ... }).new

  15. @kkuchta Anonymous Object bar = Object.new def bar.whatever # ...do

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

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

    + b; } def function(*args, &block) return Proc.new { ... } end
  19. @kkuchta Main Object $ irb > self => main >

    self.class => Object
  20. @kkuchta Main Object class Object def foo 'bar' end end

    3.foo # 'bar' foo # 'bar'
  21. @kkuchta MethodMissing class Foo def method_missing(*args) args[0] end end bar

    = Foo.new bar.nonexistent # returns :nonexistent
  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
  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
  24. @kkuchta Functions var sum = function(a, b) { |a, b|

    return a + b; }
  25. @kkuchta Send some_obj = [ 'a', 'b', 'c'] some_obj.first #

    'a' some_obj.send('first') # 'a'
  26. @kkuchta Assignment obj = Class.new { attr_accessor(:foo) }.new obj.foo =

    3 obj.foo=(3) obj.send('foo=', 3)
  27. @kkuchta Instance Eval obj = Class .new { attr_accessor :a,

    :b } .new obj.a = 3 obj.b = 4 obj.instance_eval { a + b }
  28. @kkuchta Function Function def function(*args, &block) end [:a, :b] {

    a + b }
  29. @kkuchta Function Function def function(*args, &block) function_block = Proc.new {

    |*arg_values| } return function_block end [3, 4] [:a, :b] { a + b }
  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]
  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 }
  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 }
  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 }
  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]
  35. @kkuchta So Close var sum = function(a, b) { a

    + b } sum.call(3, 4) # 7
  36. @kkuchta Define_method class Foo [1,2,3,4].each do |i| define_method("get_#{i}") { i

    } end end foo = Foo.new foo.get_1 # 1 foo.get_2 # 2
  37. @kkuchta So Close-er var func_1234 = function(a, b) { a

    + b } define_method(:sum, &func_1234) puts sum(3, 4) # 7
  38. @kkuchta Local Variables a = 1 b = 2 local_variables

    # [:b, :a]
  39. @kkuchta eval eval("3 + 4") # 7 x = 5

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

    ... } var(sum = function(...) { ... }) var(sum = "func_1234")
  42. @kkuchta Var Redux define_method(:var) { |random_function_name| } "func_1234"

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

    |local_var| end # var_name should be "sum" } "func_1234"
  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"
  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"
  46. @kkuchta Success! var sum = function(a, b) { a +

    b } puts sum(3, 4) # 7
  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) } }
  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
  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
  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));