All The Methods

Created 2016-09-14 / Edited 2016-09-23

A fun thing to do is to explore things using introspection/reflection. In Ruby and Perl6, for example, we can get a list of methods for a given object instance pretty easily:

# Ruby
"hello good people!".methods.each { |method| puts method.to_s }

# Perl6
for "hello good people!".^methods -> $method { say $method }

On the REPL (irb/pry or perl6) this is even shorter since it prints out lists of things by default, so you can do:

# Ruby
"hello good people!".methods

# Perl6
"hello good people!".^methods

One difference you see here is that Perl6 has a separate way to call meta-methods, whereas Ruby provides them directly. In some other languages you need to call into a separate reflection library and give it your object and what you want from it.

Cool!

This is a great and devious way to answer the question "what can I do with this object?". In Ruby's pry you can use the "ls" command like "ls foo" to get an even prettier version.

But you know... why stop there? Let's call the methods.

All of them.

In Perl6 that looks like:

sub all-the-methods($thing) {
  for $thing.^methods -> $method {
    say "{$method.name} => {$thing.clone.$method.gist}";
    CATCH { default { say "{$method.name} => ERROR" } }
  }
}

all-the-methods <this is words>;
all-the-methods "hello little fishies!";

Taking the list of methods, we loop over them and invoke them one at a time. Here we do ".clone" so that if it is a mutating function we'll work on a copy. Note that ".clone" is NOT a deep-copy, so while it works in my simple list and string examples it might not work so well for other things. The call to .gist gives us a nice printable version of the result.

We invoke the method with no parameters, and many methods don't like that. So here we provide a CATCH block that just prints out a generic message -- it's inside the loop so after catching an error it'll just go to the next method.

Here is some of the output:

# <this is words>
permutations => ((this is words) (this words is) (is this words) (is words this) (words this is) (words is this))
join => thisiswords
pick => is
roll => words
reverse => (words is this)
rotate => (is words this)
append => ERROR

# "hello little fishies!"
ords => (104 101 108 108 111 32 108 105 116 116 108 101 32 102 105 115 104 105 101 115 33)
wordcase => Hello Little Fishies!
uc => HELLO LITTLE FISHIES!
flip => !seihsif elttil olleh
chop => hello little fishies
contains => ERROR

Lots of the functions just result in 'ERROR', especially for those pesky methods that take some sort of parameter. A lot of these methods, however, work ok with no params and return some value.

In Ruby this looks like:

def all_the_methods(thing)
  thing.methods.each do |method|
    begin
      if method =~ /^pry|byebug|debugger$/
        puts "#{method} => SKIPPING"
        next
      end
      puts "#{method} => #{ thing.clone.send(method) }"
    rescue
      puts "#{method} => ERROR"
    end
  end
end

all_the_methods %w(this is words)
all_the_methods "hello little fishies!"

Here we had to explicitly skip the pry/byebug things, at least for my REPL execution, because those entered a subshell which isn't what I'm going for. Similar to Perl6 we also clone the object (also a shallow-clone) and had to handle exceptions, which are again almost entirely from methods that take more than zero parameters. In Ruby interpolated values automatically get .to_s called on them, so we don't need to do anything there.

One very notable thing is that operators in Ruby are methods directly on the object! This is nice for lots of reasons, including that you can easily override them. In Perl6 operators are standalone subs that do typed multi-dispatch (pattern matching) to decide what to execute, so the code isn't usually directly part of the class itself even if it ends up calling methods on the class or messing with the state.

Some of the output:

# %w(this is words)
shuffle! => ["words", "is", "this"]
include? => ERROR
permutation => #<Enumerator:0x00000002e48188>
combination => ERROR
sample => is
sort => ["is", "this", "words"]
count => 3
first => this
== => ERROR

# "hello little fishies!"
succ! => hello little fishiet!
upcase => HELLO LITTLE FISHIES!
downcase! => 
capitalize => Hello little fishies!
codepoints => [104, 101, 108, 108, 111, 32, 108, 105, 116, 116, 108, 101, 32, 102, 105, 115, 104, 105, 101, 115, 33]
reverse! => !seihsif elttil olleh
center => ERROR

Again most of the results are 'ERROR', especially where they expect more arguments. Another interesting thing is 'permutation', which returns an enumerator rather than a list, and isn't forced to evaluate upon rendering or anything. Also I notice here that 'downcase!' doesn't return the result of downcasing :)

BONUS

Since I'm going all polyglot here anyway, I was starting to wonder which methods were equivalent. I could read through and guess, but that's no fun. Instead I made an alternate version of the above that does two things -- it reverses the method-result relationship so that we get a list of methods that return a specific result. Then I also output in a more language-neutral JSON format to compare across languages, and and turn that into a list of methods and their result prefixed with the language name.

perl6-rotate ruby-rotate ruby-rotate! => [ "is", "words", "this" ]

perl6-words ruby-split => [ "hello", "little", "fishies!" ]

perl6-ords perl6-NFC perl6-NFD perl6-NFKC perl6-encode perl6-NFKD ruby-codepoints ruby-bytes => [
  104, 101, 108, 108, 111, 32, 108, 105, 116, 116, 108, 101, 32, 102, 105, 115, 104, 105, 101, 115, 33
]

perl6-lines ruby-lines => [ "hello little fishies!" ]

perl6-reverse ruby-reverse! ruby-reverse => [ "words", "is", "this" ]

perl6-comb ruby-chars => [
  "h", "e", "l", "l", "o", " ", "l", "i", "t", "t", "l", "e", " ",
  "f", "i", "s", "h", "i", "e", "s", "!"
]

perl6-sort ruby-sort ruby-sort! => [ "is", "this", "words" ]

perl6-Slip perl6-values perl6-tree perl6-unique perl6-List perl6-Array
perl6-words perl6-squish perl6-item perl6-clone perl6-flat perl6-eager
perl6-Seq perl6-FLATTENABLE_LIST perl6-reification-target perl6-ZEN-KEY
perl6-ZEN-POS perl6-return-rw perl6-cache perl6-self perl6-lazy perl6-return
ruby-untaint ruby-unshift ruby-freeze ruby-dup ruby-compact ruby-uniq
ruby-taint ruby-itself ruby-to_ary ruby-untrust ruby-clone ruby-push
ruby-entries ruby-flatten ruby-to_a ruby-trust => [
  "this", "is", "words"
]

That is a lot fewer than I expected! I had to run the perl6 with .methods(:all) to get ".sort", which was surprising. This probably misses others where my JSON encoding normalization trick doesn't work.