Page Content

Tutorials

What Is Duck Typing In Ruby With Practical Code Examples

Programmers’ interactions with objects are influenced by the fundamental Ruby concept of duck typing, which puts behavior above formal class structure.

Explanation of Duck Typing in Ruby

Duck typing in Ruby is a programming concept where the type or class of an object is less important than the methods it defines. It emphasizes behavior over inheritance, meaning that if an object can respond to a specific method, it is treated as having the necessary “type” for that operation, regardless of its actual class. The idea that if something “walks like a duck and quacks like a duck, it must be a duck” is known as dragon typing. This implies that rather than the class from which an object derives or the modules it contains, its usefulness is based on what it can accomplish (the methods it supports).

The capabilities of an object determine its type in Ruby, a dynamically typed language.

Comparison to Strong Typing

Ruby’s dynamic typing allows the interpreter to treat an object as a “duck” if it “walks like a duck and talks like a duck,” unlike tightly typed languages (like C# or Java) where a variable specified as an array cannot be utilized as another data type.

  • Strong Typing usually requires that an object provided to a method be an instance of an expected class or interface, which is known as type checking and is frequently done at compile time.
  • Duck Typing makes typing relevant to a task mandatory. The code functions properly if a method calls for an object that supports that method (such as quack), provided that the object, regardless of its class, reacts to the method.

Beginners may put off learning the notions of explicit types or classes until they are absolutely required, but this method is quite convenient for them.

The Role of Duck Typing

Instead of using is_a? to check a variable’s class when programming with the duck typing philosophy, it is better to use the respond_to? method to see if the variable supports a certain method. By depending on respond_to?, you guarantee that you and other future users can design new classes that provide the same functionality without being constrained by an existing class structure.

Writing a method that only accepts Strings instead of anything that can be converted (like to_str) compromises the duck typing technique for other users of your code.

Code Examples Demonstrating Duck Typing

The following examples show how Duck Typing enables generic treatment of various objects as long as they share the necessary behavior (method signature).

Example 1: Objects with Shared Behavior

Think of Duck, Goose, and DuckRecording as three classes.

class Duck
  def quack
    'Quack!'
  end
  def swim
    'Paddle paddle paddle...'
  end
end

class Goose
  def honk
    'Honk!'
  end
  def swim
    'Splash splash splash...'
  end
end

class DuckRecording
  def quack
    play
  end
  def play
    'Quack!'
  end
end

# --- ADDED CODE TO CREATE OUTPUT ---

# 1. Instantiate the classes
daisy = Duck.new
greg = Goose.new
tape = DuckRecording.new

puts "--- Method Calls ---"

# 2. Call methods on the objects
puts "Daisy the Duck says: #{daisy.quack}" # Duck responds to quack
puts "Daisy the Duck swims: #{daisy.swim}"

puts "Greg the Goose says: #{greg.honk}" # Goose responds to honk
puts "Greg the Goose swims: #{greg.swim}"

# 3. Demonstrate Duck Typing (Focusing on the 'quack' method)
# Even though DuckRecording is not a Duck, it has a 'quack' method.

puts "\n--- Duck Typing Example ---"

def make_it_quack(animal)
  # This method doesn't care about the object's class, only that it responds to 'quack'
  puts "I asked an object to quack, and it said: #{animal.quack}"
end

puts "Asking the Duck to quack:"
make_it_quack(daisy) # Works!

puts "Asking the DuckRecording to quack:"
make_it_quack(tape) # Works, demonstrating Duck Typing!

Output

--- Method Calls ---
Daisy the Duck says: Quack!
Daisy the Duck swims: Paddle paddle paddle...
Greg the Goose says: Honk!
Greg the Goose swims: Splash splash splash...

--- Duck Typing Example ---
Asking the Duck to quack:
I asked an object to quack, and it said: Quack!
Asking the DuckRecording to quack:
I asked an object to quack, and it said: Quack!

If the expected method is present, any of these classes can be handled by methods written in Ruby that make use of duck typing:

class Duck
  def quack
    'Quack!'
  end
  def swim
    'Paddle paddle paddle...'
  end
end

class Goose
  def honk
    'Honk!'
  end
  def swim
    'Splash splash splash...'
  end
end

class DuckRecording
  def quack
    play
  end
  def play
    'Quack!'
  end
  # DuckRecording does NOT have a swim method
end

# --- Functions Demonstrating Duck Typing ---

def make_it_quack(duck)
  # Relies only on the presence of the 'quack' method
  duck.quack
end

def make_it_swim(animal)
  # Relies only on the presence of the 'swim' method
  animal.swim
end

# --- Usage (Output) ---

puts "--- Quack Examples ---"
# Works for Duck
puts make_it_quack(Duck.new)          # => Quack!

# Works for DuckRecording (even though it's not a Duck)
puts make_it_quack(DuckRecording.new) # => Quack!

puts "\n--- Swim Examples ---"
# Works for Duck
puts make_it_swim(Duck.new)           # => Paddle paddle paddle...

# Works for Goose (even though it's not a Duck)
puts make_it_swim(Goose.new)          # => Splash splash splash...

# Note: Calling make_it_swim(DuckRecording.new) would raise a NoMethodError
# because DuckRecording does not respond to the 'swim' method.

Output

--- Quack Examples ---
Quack!
Quack!

--- Swim Examples ---
Paddle paddle paddle...
Splash splash splash...

Nevertheless, a NoMethodError will be raised if an object is supplied that does not define the desired method:

make_it_quack(Goose.new) # NoMethodError: undefined method `quack' for #<Goose:0x...> 

Example 2: Using for Robustness

When drafting a method that must guarantee that its input is “point-like” (has x and y methods), using respond_to? to check for the capability offers flexibility and, if required, an enhanced error message:

# A simple struct-like class to hold point coordinates
Point = Struct.new(:x, :y) do
  # Override the standard addition operator
  def +(other)
    # 1. Looser version of type checking using respond_to?
    raise TypeError, "Point-like argument expected" unless
      other.respond_to?(:x) && other.respond_to?(:y) # Check for methods
    
    # 2. Perform the addition using the 'x' and 'y' methods
    Point.new(self.x + other.x, self.y + other.y)
  end
end

# --- ADDED CODE TO CREATE OUTPUT ---

# Create Point objects
p1 = Point.new(10, 20)
p2 = Point.new(5, -3)

# 1. Demonstrate Point + Point (Successful Operation)
p3 = p1 + p2
puts "Point 1: #{p1.inspect}" # => #<struct Point x=10, y=20>
puts "Point 2: #{p2.inspect}" # => #<struct Point x=5, y=-3>
puts "Point 1 + Point 2: #{p3.inspect}"
# Expected Output: Point.new(10+5, 20-3) => #<struct Point x=15, y=17>

puts "\n"

# 2. Demonstrate Duck Typing (Successful Operation with a non-Point object)
class DummyVector
  # A class that isn't a Point, but implements the required 'x' and 'y' methods
  def x; 
    100; 
  end
  def y; 
    200; 
  end
end

dummy = DummyVector.new
p4 = p1 + dummy # This works because dummy.respond_to?(:x) and dummy.respond_to?(:y) are true
puts "Point 1 + DummyVector: #{p4.inspect}"
# Expected Output: Point.new(10+100, 20+200) => #<struct Point x=110, y=220>

puts "\n"

# 3. Demonstrate Failure (Raises TypeError due to missing method)
begin
  p1 + "not a point" # Strings don't respond to 'x' or 'y'
rescue TypeError => e
  puts "Error caught: #{e.message}"
end

Output

Point 1: #<struct Point x=10, y=20>
Point 2: #<struct Point x=5, y=-3>
Point 1 + Point 2: #<struct Point x=15, y=17>

Point 1 + DummyVector: #<struct Point x=110, y=220>

Error caught: Point-like argument expected

This preserves the duck type concept by guaranteeing that any object which defines methods called x and y will be appropriate for the + operation, regardless of its class.

Example 3: File-like Objects

For operations like file I/O and testing, duck typing is useful. The Enumerable module is part of the IO class and the String class. Often, you can write code to read from an object without knowing its type (e.g., String or File). A file object or a StringIO object (which emulates a file in memory) might be passed to a helper method that expects to “write” data to an object, for example.

Duck typing can also be used for resource management in setup and cleanup tasks, such as making sure a file is closed, by employing a method that expects an object that responds to close (such as File.new utilizing a block).

You can also read What Is Code Blocks In Ruby With Practical Code Examples

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index