Simulating Keyword Arguments

Problem

A function or method can accept many optional arguments. You want to let callers pass in only the arguments they have values for, but Ruby doesn't support keyword arguments as Python and Lisp do.

Solution

Write your function to accept as its final argument a map of symbols to values. Consult the map as necessary to see what arguments were passed in.

	def fun_with_text(text, args={})
	 text = text.upcase if args[:upcase]
	 text = text.downcase if args[:downcase]
	 if args[:find] and args[:replace]
	 text = text.gsub(args[:find], args[:replace])
	 end
	 text = text.slice(0, args[:truncate_at]) if args[:truncate_at]
	 return text
	end

Ruby has syntactic sugar that lets you define a hash inside a function call without putting it in curly brackets. This makes the code look more natural:

	fun_with_text("Foobar", {:upcase => true, :truncate_at => 5})
	# => "FOOBA"
	fun_with_text("Foobar", :upcase => true, :truncate_at => 5)
	# => "FOOBA"
	fun_with_text("Foobar", :find => /(o+)/, :replace => '1d', :downcase => true)
	# => "foodbar"

 

Discussion

This simple code works well in most cases, but it has a couple of shortcomings compared to "real" keyword arguments. These simulated keyword arguments don't work like regular arguments because they're hidden inside a hash. You can't reject an argument that's not part of the "signature," and you can't force a caller to provide a particular keyword argument.

Each of these problems is easy to work around (for instance, does a required argument really need to be a keyword argument?), but it's best to define the workaround code in a mixin so you only have to do it once. The following code is based on a KeywordProcessor module by Gavin Sinclair:

	###
	# This mix-in module lets methods match a caller's hash of keyword
	# parameters against a hash the method keeps, mapping keyword
	# arguments to default parameter values.
	#
	# If the caller leaves out a keyword parameter whose default value is
	# :MANDATORY (a constant in this module), then an error is raised.
	#
	# If the caller provides keyword parameters which have no
	# corresponding keyword arguments, an error is raised.
	#
	module KeywordProcessor
	 MANDATORY = :MANDATORY

	 def process_params(params, defaults)
	 # Reject params not present in defaults.
	 params.keys.each do |key|
	 unless defaults.has_key? key
	 raise ArgumentError, "No such keyword argument: #{key}"
	 end
	 end
	 result = defaults.dup.update(params)

	 # Ensure mandatory params are given.
	 unfilled = result.select { |k,v| v == MANDATORY }.map { |k,v| k.inspect }
	 unless unfilled.empty?
	 msg = "Mandatory keyword parameter(s) not given: #{unfilled.join(', ')}"
	 raise ArgumentError, msg
	 end

	 return result
	 end
	end

Here's KeywordProcessor in action. Note how I set a default other than nil for a keyword argument, by defining it in the default value of args:

	class TextCanvas
	 include 
KeywordProcessor

	 def render(text, args={}.freeze)
	 args = process_params(args, {:font => 'New Reykjavik Solemn', :size => 36,
	 :bold => false, :x => :MANDATORY,
	 :y => :MANDATORY }.freeze)
	 # …
	 puts "DEBUG: Found font #{args[:font]} in catalog."
	 # …
	 end
	end

	canvas = TextCanvas.new

	canvas.render('Hello', :x => 4, :y => 100)
	# DEBUG: Found font New Reykjavik Solemn in catalog.

	canvas.render('Hello', :x => 4, :y => 100, :font => 'Lacherlich')
	# DEBUG: Found font Lacherlich in catalog.

	canvas.render('Hello', :font => "Lacherlich")
	# ArgumentError: Mandatory keyword parameter(s) not given: :x, :y

	canvas.render('Hello', :x => 4, :y => 100, :italic => true)
	# ArgumentError: No such keyword argument: italic

Ruby 2.0 will, hopefully, have full support for keyword arguments.

See Also

  • Recipe 8.8, "Delegating Method Calls to Another Object"
  • The KeywordProcessor module is based on the one in "Emulating Keyword Arguments in Ruby"; I modified it to be less oriented around the initialize method (http://www.rubygarden.org/ruby?KeywordArguments)


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net