afkortingen voor methoden namen
Commandline utilities hebben vaak korte namen; ls
, rm
, grep
, svn
etc. De subversion client svn
heeft (sub-)commando’s welke allemaal afkortingen hebben; svn status
kan je schrijven als svn st
e.d. Toen ik een ruby wrapper class had geschreven om het svn
commando, miste ik metteen de svn st
variant. Natuurlijk kan je gewoon wat aliases aanmaken voor commando’s maar ik zag een kans voor een experimentje in een verloren uurtje.
Bladerend door de appendix van pickaxe 2 ben ik ooit tegen abbrev aangelopen. Gegeven een lijst termen geeft deze een hash van mogelijke afkortingen terug:
require 'abbrev' require 'pp' pp %w{bla die foo bar}.abbrev
levert:
{"die"=>"die", "bla"=>"bla", "d"=>"die", "di"=>"die", "foo"=>"foo", "f"=>"foo", "bar"=>"bar", "ba"=>"bar", "fo"=>"foo", "bl"=>"bla"}
Mooi om automagische afkortingen te leveren. We kunnen aan de slag!
Als we een methode op een object aanroepen die niet bestaat wordt de method_missing
methode aangeroepen. Hier kunnen we kijken of het object met een afgekorte methode naam wordt aangeroepen en hiernaar doorspringen:
require 'abbrev' module Shorthand alias :method_missing_orig :method_missing def method_missing(method, *args) original = methods.abbrev[method.to_s] if original send(original, *args) else method_missing_orig(method, *args) end end end
En dan nu toepassen:
class String; include Shorthand; end
Maar wat heeft dit eigenlijk voor afkortingen opgeleverd? De methods
methode aanroepen op een instance van String
heeft geen zin omdat onze afkortingen via method_missing
worden aangeroepen. Eens kijken wat abbrev
oplevert, gesorteerd op lengte:
m = "".methods.abbrev m.keys.sort{|a,b|a.size<=>b.size}.each{|k|puts "#{k.inspect} => #{m[k].inspect}"} "%" => "%" "b" => "between?" "k" => "kind_of?" ">" => ">" "<" => "<" "z" => "zip" "*" => "*" "+" => "+" "ty" => "type" "cr" => "crypt" "sp" => "split" "pu" => "public_methods" "rj" => "rjust" "be" => "between?" "ri" => "rindex" "le" => "length" "mi" => "min" "ni" => "nil?" "id" => "id" "lj" => "ljust" "[]" => "[]" "pa" => "partition" "is" => "is_a?" ">=" => ">=" "gr" => "grep" ..
Eigenlijk niet veel soeps. We kunnen nu "hallo wereld".le
gebruiken in plaats van "hallo wereld".length
, big deal.. Waarom krijgt upcase
bijvoorbeeld geen mooie afkorting? Er is een upcase
en upcase!
daar valt dus niet aan af te korten, tenzij we deze suffix er eerst afhalen en er later weer aanplakken. Behouden we ook nog eens de punktuatie!
module Shorthand alias :method_missing_orig :method_missing def method_missing(method, *args) shorthands = methods.reject{|t|t =~ /[!?]$/}.abbrev shorthands.merge! Hash[*methods.select{|t|t =~ /!$/}.map do |t| t.chop end.abbrev.map{|a,b|[a + '!', b + '!']}.flatten] shorthands.merge! Hash[*methods.select{|t|t =~ /\?$/}.map do |t| t.chop end.abbrev.map{|a,b|[a + '?', b + '?']}.flatten] original = shorthands[method.to_s] if original send(original, *args) else method_missing_orig(method, *args) end end end
Met als top 15 hoog rendement afkortingen:
"n" => "next" "z" => "zip" "ne" => "next" "lj" => "ljust" "sq" => "squeeze" "n?" => "nil?" "sp" => "split" "sl" => "slice" "le" => "length" "gr" => "grep" "st" => "strip" "gs" => "gsub" "rs" => "rstrip" "m?" => "member?" "u!" => "upcase!"
Nu hebben we zelfs u!
voor upcase!
, dit begint ergens op te lijken! Wat wel jammer is dat de methods
methode op een object met deze module je afkortingen niet levert. Hoewel je er (bijna) niets aan hebt omdat je niet weet waar het een afkorting voor is, werkt het voor een implementatie die aliases gebruikt wel:
module Shorthand def self.included(klass) methods = klass.public_instance_methods shorthands = methods.reject{|t|t =~ /[!?]$/}.abbrev shorthands.merge! Hash[*methods.select{|t|t =~ /!$/}.map do |t| t.chop end.abbrev.map{|a,b|[a + '!', b + '!']}.flatten] shorthands.merge! Hash[*methods.select{|t|t =~ /\?$/}.map do |t| t.chop end.abbrev.map{|a,b|[a + '?', b + '?']}.flatten] shorthands.each{|k,v| klass.class_eval "alias #{k} #{v}"} end end
Het was een leuk experimentje in een verloren uurtje maar de afkortingen waar abbrev
meekomt zijn voor m’n Svn
class waardeloos. De commit
methode zou afgekort worden naar co
wat voor de commandline tool gelijk is aan checkout
, niet echt wenselijk. Daarbij kan je je afvragen of een beter afkort algoritme veel uit zal halen. Dergelijk automagische afkortingen zullen zeer instabiele interfaces opleveren. De kans is te groot dat de introductie van een nieuwe methode een belangrijk deel van de afkortingen die je ingebruik hebt omzeep zal helpen.
Geen goed idee, ik maak wel aliases met de hand, wel een interessant uurtje waarin ik anders op een collega’s had zitten wachten.