Polymorphism & Interfaces

What is Polymorphism?

"One interface, many implementations."

from abc import ABC, abstractmethod
from math import pi

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return pi * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

def process(shape: Shape) -> None:
    print(f"Area: {shape.area():.2f}")

process(Circle(5))        # 78.54
process(Rectangle(3, 4))  # 12
process(Triangle(3, 4))   # 6
#include <iostream>
#include <cmath>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return M_PI * radius_ * radius_; }
};

class Rectangle : public Shape {
    double width_, height_;
public:
    Rectangle(double w, double h) : width_(w), height_(h) {}
    double area() const override { return width_ * height_; }
};

class Triangle : public Shape {
    double base_, height_;
public:
    Triangle(double b, double h) : base_(b), height_(h) {}
    double area() const override { return 0.5 * base_ * height_; }
};

void process(const Shape& shape) {
    std::cout << "Area: " << shape.area() << std::endl;
}

int main() {
    process(Circle(5));        // 78.54
    process(Rectangle(3, 4));  // 12
    process(Triangle(3, 4));   // 6
}
public abstract class Shape {
    public abstract double area();
}

public final class Circle extends Shape {
    private final double radius;
    public Circle(double r) { this.radius = r; }
    public double area() { return Math.PI * radius * radius; }
}

public final class Rectangle extends Shape {
    private final double width, height;
    public Rectangle(double w, double h) { this.width = w; this.height = h; }
    public double area() { return width * height; }
}

public final class Triangle extends Shape {
    private final double base, height;
    public Triangle(double b, double h) { this.base = b; this.height = h; }
    public double area() { return 0.5 * base * height; }
}

public final class Example {
    public static void process(Shape shape) {
        System.out.println("Area: " + shape.area());
    }

    public static void main(String[] args) {
        process(new Circle(5));        // 78.54
        process(new Rectangle(3, 4));  // 12
        process(new Triangle(3, 4));   // 6
    }
}
using System;

public abstract class Shape {
    public abstract double Area();
}

public sealed class Circle : Shape {
    private readonly double _radius;
    public Circle(double r) => _radius = r;
    public override double Area() => Math.PI * _radius * _radius;
}

public sealed class Rectangle : Shape {
    private readonly double _width, _height;
    public Rectangle(double w, double h) { _width = w; _height = h; }
    public override double Area() => _width * _height;
}

public sealed class Triangle : Shape {
    private readonly double _base, _height;
    public Triangle(double b, double h) { _base = b; _height = h; }
    public override double Area() => 0.5 * _base * _height;
}

public static class Program {
    static void Process(Shape shape) {
        Console.WriteLine($"Area: {shape.Area():F2}");
    }

    static void Main() {
        Process(new Circle(5));        // 78.54
        Process(new Rectangle(3, 4));  // 12
        Process(new Triangle(3, 4));   // 6
    }
}
class Shape
  def area = raise NotImplementedError
end

class Circle < Shape
  def initialize(radius)
    @radius = radius
  end

  def area
    Math::PI * @radius**2
  end
end

class Rectangle < Shape
  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    @width * @height
  end
end

class Triangle < Shape
  def initialize(base, height)
    @base = base
    @height = height
  end

  def area
    0.5 * @base * @height
  end
end

def process(shape)
  puts format("Area: %.2f", shape.area)
end

process(Circle.new(5))        # 78.54
process(Rectangle.new(3, 4))  # 12
process(Triangle.new(3, 4))   # 6

Types of Polymorphism

Type Example
Subtype (Runtime) Inheritance + method overriding
Parametric (Generics) List[T], Optional[T]
Ad-hoc (Overloading) Multiple add(a, b) for different types

Subtype Polymorphism (The Classic)

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def charge(self, amount: float) -> bool:
        pass

class StripeProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Stripe")
        return True

class PayPalProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via PayPal")
        return True

class CryptoProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Charging ${amount:.2f} via Crypto")
        return True
class PaymentProcessor {
public:
    virtual bool charge(double amount) = 0;
    virtual ~PaymentProcessor() = default;
};

class StripeProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via Stripe" << std::endl;
        return true;
    }
};

class PayPalProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via PayPal" << std::endl;
        return true;
    }
};

class CryptoProcessor : public PaymentProcessor {
public:
    bool charge(double amount) override {
        std::cout << "Charging $" << amount << " via Crypto" << std::endl;
        return true;
    }
};
public interface PaymentProcessor {
    boolean charge(double amount);
}

public final class StripeProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via Stripe");
        return true;
    }
}

public final class PayPalProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via PayPal");
        return true;
    }
}

public final class CryptoProcessor implements PaymentProcessor {
    public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via Crypto");
        return true;
    }
}
public interface IPaymentProcessor {
    bool Charge(double amount);
}

public sealed class StripeProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Stripe");
        return true;
    }
}

public sealed class PayPalProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via PayPal");
        return true;
    }
}

public sealed class CryptoProcessor : IPaymentProcessor {
    public bool Charge(double amount) {
        Console.WriteLine($"Charging ${amount:F2} via Crypto");
        return true;
    }
}
class PaymentProcessor
  def charge(amount) = raise NotImplementedError
end

class StripeProcessor < PaymentProcessor
  def charge(amount)
    puts format("Charging $%.2f via Stripe", amount)
    true
  end
end

class PayPalProcessor < PaymentProcessor
  def charge(amount)
    puts format("Charging $%.2f via PayPal", amount)
    true
  end
end

class CryptoProcessor < PaymentProcessor
  def charge(amount)
    puts format("Charging $%.2f via Crypto", amount)
    true
  end
end
def checkout(processor: PaymentProcessor, amount: float) -> None:
    if processor.charge(amount):
        print("Payment successful!")

if __name__ == "__main__":
    checkout(StripeProcessor(), 99.99)
    checkout(PayPalProcessor(), 49.99)
    checkout(CryptoProcessor(), 199.00)
void checkout(PaymentProcessor& processor, double amount) {
    if (processor.charge(amount)) {
        std::cout << "Payment successful!\n";
    }
}

int main() {
    checkout(StripeProcessor(), 99.99);
    checkout(PayPalProcessor(), 49.99);
    checkout(CryptoProcessor(), 199.00);
}
public static void checkout(PaymentProcessor processor, double amount) {
    if (processor.charge(amount)) {
        System.out.println("Payment successful!");
    }
}

public static void main(String[] args) {
    checkout(new StripeProcessor(), 99.99);
    checkout(new PayPalProcessor(), 49.99);
    checkout(new CryptoProcessor(), 199.00);
}
static void Checkout(IPaymentProcessor processor, double amount) {
    if (processor.Charge(amount)) {
        Console.WriteLine("Payment successful!");
    }
}

static void Main() {
    Checkout(new StripeProcessor(), 99.99);
    Checkout(new PayPalProcessor(), 49.99);
    Checkout(new CryptoProcessor(), 199.00);
}
def checkout(processor, amount)
  puts "Payment successful!" if processor.charge(amount)
end

checkout(StripeProcessor.new, 99.99)
checkout(PayPalProcessor.new, 49.99)
checkout(CryptoProcessor.new, 199.00)

Interface Segregation Principle

Many specific interfaces > one general interface

from abc import ABC, abstractmethod

# BAD — fat interface
class Worker(ABC):
    @abstractmethod
    def work(self) -> None: pass
    @abstractmethod
    def eat(self) -> None: pass
    @abstractmethod
    def sleep(self) -> None: pass

class Robot(Worker):
    def work(self) -> None: pass
    def eat(self) -> None: raise RuntimeError("Robots don't eat!")
    def sleep(self) -> None: raise RuntimeError("Robots don't sleep!")

# GOOD — segregated interfaces
class Workable(ABC):
    @abstractmethod
    def work(self) -> None: pass

class Eatable(ABC):
    @abstractmethod
    def eat(self) -> None: pass

class Sleepable(ABC):
    @abstractmethod
    def sleep(self) -> None: pass

class Human(Workable, Eatable, Sleepable):
    def work(self) -> None: pass
    def eat(self) -> None: pass
    def sleep(self) -> None: pass
// BAD — fat interface
class Worker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual ~Worker() = default;
};

class Robot : public Worker {
public:
    void work() override {}
    void eat() override { throw std::runtime_error("Robots don't eat!"); }
    void sleep() override { throw std::runtime_error("Robots don't sleep!"); }
};

// GOOD — segregated interfaces
class Workable { public: virtual void work() = 0; virtual ~Workable() = default; };
class Eatable  { public: virtual void eat() = 0;  virtual ~Eatable() = default; };
class Sleepable{ public: virtual void sleep() = 0; virtual ~Sleepable() = default; };

class Human : public Workable, public Eatable, public Sleepable {
public:
    void work() override {}
    void eat() override {}
    void sleep() override {}
};
// BAD — fat interface
interface Worker {
    void work();
    void eat();
    void sleep();
}

class Robot implements Worker {
    public void work() {}
    public void eat() { throw new RuntimeException("Robots don't eat!"); }
    public void sleep() { throw new RuntimeException("Robots don't sleep!"); }
}

// GOOD — segregated interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }

class Human implements Workable, Eatable, Sleepable {
    public void work() {}
    public void eat() {}
    public void sleep() {}
}
// BAD — fat interface
interface IWorker {
    void Work();
    void Eat();
    void Sleep();
}

class Robot : IWorker {
    public void Work() {}
    public void Eat() => throw new Exception("Robots don't eat!");
    public void Sleep() => throw new Exception("Robots don't sleep!");
}

// GOOD — segregated interfaces
interface IWorkable { void Work(); }
interface IEatable { void Eat(); }
interface ISleepable { void Sleep(); }

class Human : IWorkable, IEatable, ISleepable {
    public void Work() {}
    public void Eat() {}
    public void Sleep() {}
}
# BAD — fat interface (a module used as one)
module Worker
  def work = raise NotImplementedError
  def eat = raise NotImplementedError
  def sleep = raise NotImplementedError
end

class Robot
  include Worker
  def work; end
  def eat = raise "Robots don't eat!"
  def sleep = raise "Robots don't sleep!"
end

# GOOD — segregated modules, mixed in as needed
module Workable
  def work = raise NotImplementedError
end

module Eatable
  def eat = raise NotImplementedError
end

module Sleepable
  def sleep = raise NotImplementedError
end

class Human
  include Workable
  include Eatable
  include Sleepable

  def work; end
  def eat; end
  def sleep; end
end

Abstract Base Classes (ABC)

from abc import ABC, abstractmethod
from typing import Callable

class Database(ABC):
    @abstractmethod
    def connect(self) -> None:
        pass

    @abstractmethod
    def execute(self, query: str) -> None:
        pass

    @abstractmethod
    def commit(self) -> None:
        pass

    @abstractmethod
    def rollback(self) -> None:
        pass

    # Concrete method (template method pattern)
    def transaction(self, work: Callable[[], None]) -> None:
        self.connect()
        try:
            work()
            self.commit()
        except Exception:
            self.rollback()
            raise
class Database {
public:
    virtual ~Database() = default;
    virtual void connect() = 0;
    virtual void execute(const std::string& query) = 0;
    virtual void commit() = 0;
    virtual void rollback() = 0;

    // Concrete method (template method pattern)
    template<typename F>
    void transaction(std::function<void()> work) {
        connect();
        try {
            work();
            commit();
        } catch (...) {
            rollback();
            throw;
        }
    }
};
public abstract class Database {
    public abstract void connect();
    public abstract void execute(String query);
    public abstract void commit();
    public abstract void rollback();

    public void transaction(Runnable work) {
        connect();
        try {
            work.run();
            commit();
        } catch (Exception e) {
            rollback();
            throw new RuntimeException(e);
        }
    }
}
public abstract class Database {
    public abstract void Connect();
    public abstract void Execute(string query);
    public abstract void Commit();
    public abstract void Rollback();

    public void Transaction(Action work) {
        Connect();
        try {
            work();
            Commit();
        } catch {
            Rollback();
            throw;
        }
    }
}
class Database
  def connect = raise NotImplementedError
  def execute(query) = raise NotImplementedError
  def commit = raise NotImplementedError
  def rollback = raise NotImplementedError

  # Concrete method (template method pattern)
  def transaction
    connect
    begin
      yield
      commit
    rescue StandardError
      rollback
      raise
    end
  end
end

Mixing Abstract + Concrete

from abc import ABC, abstractmethod

class HTTPClient(ABC):
    @abstractmethod
    def request(self, method: str, url: str, data: str = "") -> str:
        pass

    def get(self, url: str) -> str:
        return self.request("GET", url)

    def post(self, url: str, data: str = "") -> str:
        return self.request("POST", url, data)

    def put(self, url: str, data: str = "") -> str:
        return self.request("PUT", url, data)

    def delete(self, url: str) -> str:
        return self.request("DELETE", url)
class HTTPClient {
public:
    virtual ~HTTPClient() = default;
    virtual std::string request(std::string method, std::string url, std::string data = "") = 0;

    std::string get(std::string url) { return request("GET", url, ""); }
    std::string post(std::string url, std::string data = "") { return request("POST", url, data); }
    std::string put(std::string url, std::string data = "") { return request("PUT", url, data); }
    std::string del(std::string url) { return request("DELETE", url, ""); }
};
public abstract class HTTPClient {
    public abstract String request(String method, String url, String data);

    public String get(String url) { return request("GET", url, ""); }
    public String post(String url, String data) { return request("POST", url, data); }
    public String put(String url, String data) { return request("PUT", url, data); }
    public String delete(String url) { return request("DELETE", url, ""); }
}
public abstract class HttpClient {
    public abstract string Request(string method, string url, string data = "");

    public string Get(string url) => Request("GET", url);
    public string Post(string url, string data) => Request("POST", url, data);
    public string Put(string url, string data) => Request("PUT", url, data);
    public string Delete(string url) => Request("DELETE", url);
}
class HTTPClient
  def request(method, url, data = "") = raise NotImplementedError

  def get(url)
    request("GET", url)
  end

  def post(url, data = "")
    request("POST", url, data)
  end

  def put(url, data = "")
    request("PUT", url, data)
  end

  def delete(url)
    request("DELETE", url)
  end
end

Mixing Abstract + Concrete (Template Method Pattern)

from abc import ABC, abstractmethod
from typing import Callable, List

class DataExporter(ABC):
    @abstractmethod
    def write_header(self) -> None:
        pass

    @abstractmethod
    def write_row(self, data: dict) -> None:
        pass

    @abstractmethod
    def write_footer(self) -> None:
        pass

    # Concrete template method
    def export(self, data: List[dict]) -> None:
        self.write_header()
        for row in data:
            self.write_row(row)
        self.write_footer()

class CSVExporter(DataExporter):
    def write_header(self) -> None:
        print("id,name,email")

    def write_row(self, data: dict) -> None:
        print(f"{data['id']},{data['name']},{data['email']}")

    def write_footer(self) -> None:
        print("# End of CSV")

class JSONExporter(DataExporter):
    def write_header(self) -> None:
        print("[")

    def write_row(self, data: dict) -> None:
        print(f'  {{"id": {data["id"]}, "name": "{data["name"]}", "email": "{data["email"]}"}},')

    def write_footer(self) -> None:
        print("]")
#include <vector>
#include <string>
#include <iostream>

class DataExporter {
public:
    virtual ~DataExporter() = default;
    virtual void writeHeader() = 0;
    virtual void writeRow(const std::string& data) = 0;
    virtual void writeFooter() = 0;

    // Concrete template method
    void export(const std::vector<std::string>& data) {
        writeHeader();
        for (const auto& row : data) writeRow(row);
        writeFooter();
    }
};

class CSVExporter : public DataExporter {
    void writeHeader() override { std::cout << "id,name,email\n"; }
    void writeRow(const std::string& data) override { std::cout << data << "\n"; }
    void writeFooter() override { std::cout << "# End of CSV\n"; }
};

class JSONExporter : public DataExporter {
    void writeHeader() override { std::cout << "[\n"; }
    void writeRow(const std::string& data) override { std::cout << "  " << data << ",\n"; }
    void writeFooter() override { std::cout << "]\n"; }
};
public abstract class DataExporter {
    public abstract void writeHeader();
    public abstract void writeRow(String data);
    public abstract void writeFooter();

    // Concrete template method
    public void export(List<String> data) {
        writeHeader();
        for (String row : data) writeRow(row);
        writeFooter();
    }
}

class CSVExporter extends DataExporter {
    public void writeHeader() { System.out.println("id,name,email"); }
    public void writeRow(String data) { System.out.println(data); }
    public void writeFooter() { System.out.println("# End of CSV"); }
}

class JSONExporter extends DataExporter {
    public void writeHeader() { System.out.print("[\n"); }
    public void writeRow(String data) { System.out.println("  " + data + ","); }
    public void writeFooter() { System.out.println("]"); }
}
public abstract class DataExporter {
    public abstract void WriteHeader();
    public abstract void WriteRow(string data);
    public abstract void WriteFooter();

    // Concrete template method
    public void Export(IEnumerable<string> data) {
        WriteHeader();
        foreach (var row in data) WriteRow(row);
        WriteFooter();
    }
}

public sealed class CSVExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("id,name,email");
    public override void WriteRow(string data) => Console.WriteLine(data);
    public override void WriteFooter() => Console.WriteLine("# End of CSV");
}

public sealed class JSONExporter : DataExporter {
    public override void WriteHeader() => Console.WriteLine("[");
    public override void WriteRow(string data) => Console.WriteLine("  " + data + ",");
    public override void WriteFooter() => Console.WriteLine("]");
}
class DataExporter
  # Concrete template method
  def export(data)
    write_header
    data.each { |row| write_row(row) }
    write_footer
  end

  def write_header = raise NotImplementedError
  def write_row(data) = raise NotImplementedError
  def write_footer = raise NotImplementedError
end

class CSVExporter < DataExporter
  def write_header
    puts "id,name,email"
  end

  def write_row(data)
    puts "#{data[:id]},#{data[:name]},#{data[:email]}"
  end

  def write_footer
    puts "# End of CSV"
  end
end

class JSONExporter < DataExporter
  def write_header
    puts "["
  end

  def write_row(data)
    puts %(  {"id": #{data[:id]}, "name": "#{data[:name]}", "email": "#{data[:email]}"},)
  end

  def write_footer
    puts "]"
  end
end

Multiple Dispatch / Double Dispatch

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def collide_with(self, other: 'Shape') -> str:
        pass

    def collide(self, other: 'Shape') -> str:
        return other.collide_with(self)

class Circle(Shape):
    def collide_with(self, other: 'Shape') -> str:
        if isinstance(other, Circle):
            return "Circle-Circle"
        return "Circle-Other"

class Rectangle(Shape):
    def collide_with(self, other: 'Shape') -> str:
        if isinstance(other, Rectangle):
            return "Rect-Rect"
        return "Rect-Other"

# Usage
circle = Circle()
rect = Rectangle()
print(circle.collide(rect))  # Circle-Rect
print(rect.collide(circle))  # Circle-Rect (double dispatch!)
#include <memory>
#include <string>

class Shape {
public:
    virtual ~Shape() = default;
    virtual std::string collideWith(const Shape& other) const = 0;
    std::string collide(const Shape& other) const { return other.collideWith(*this); }
};

class Circle : public Shape {
    std::string collideWith(const Shape& other) const override {
        if (dynamic_cast<const Circle*>(&other)) return "Circle-Circle";
        return "Circle-Other";
    }
};

class Rectangle : public Shape {
    std::string collideWith(const Shape& other) const override {
        if (dynamic_cast<const Rectangle*>(&other)) return "Rect-Rect";
        return "Rect-Other";
    }
};

// Usage
Circle circle; Rectangle rect;
circle.collide(rect);  // Circle-Rect
rect.collide(circle);  // Circle-Rect (double dispatch!)
interface Shape {
    String collideWith(Shape other);
    default String collide(Shape other) { return other.collideWith(this); }
}

class Circle implements Shape {
    public String collideWith(Shape other) {
        if (other instanceof Circle) return "Circle-Circle";
        return "Circle-Other";
    }
}

class Rectangle implements Shape {
    public String collideWith(Shape other) {
        if (other instanceof Rectangle) return "Rect-Rect";
        return "Rect-Other";
    }
}

// Usage
Shape circle = new Circle();
Shape rect = new Rectangle();
circle.collide(rect);  // Circle-Rect
rect.collide(circle);  // Circle-Rect (double dispatch!)
public interface IShape {
    string CollideWith(IShape other);
    string Collide(IShape other) => other.CollideWith(this);
}

public sealed class Circle : IShape {
    public string CollideWith(IShape other) => other is Circle ? "Circle-Circle" : "Circle-Other";
}

public sealed class Rectangle : IShape {
    public string CollideWith(IShape other) => other is Rectangle ? "Rect-Rect" : "Rect-Other";
}

// Usage
var circle = new Circle();
var rect = new Rectangle();
circle.Collide(rect);  // Circle-Rect
rect.Collide(circle);  // Circle-Rect (double dispatch!)
class Shape
  def collide_with(other) = raise NotImplementedError

  def collide(other)
    other.collide_with(self)
  end
end

class Circle < Shape
  def collide_with(other)
    other.is_a?(Circle) ? "Circle-Circle" : "Circle-Other"
  end
end

class Rectangle < Shape
  def collide_with(other)
    other.is_a?(Rectangle) ? "Rect-Rect" : "Rect-Other"
  end
end

# Usage
circle = Circle.new
rect = Rectangle.new
puts circle.collide(rect)  # Circle-Rect
puts rect.collide(circle)  # Circle-Rect (double dispatch!)

Type Classes / Protocols (Ad-hoc Polymorphism)

from typing import Protocol, TypeVar
T = TypeVar('T')

class Comparable(Protocol):
    def __lt__(self, other: T) -> bool: ...

def sort(items: list[T]) -> list[T]:
    return sorted(items)

# Any type implementing __lt__ works
print(sort([3, 1, 2]))
print(sort(["c", "a", "b"]))
#include <algorithm>
#include <vector>
#include <string>

template<typename T>
requires std::sortable<T>
std::vector<T> sort(std::vector<T> items) {
    std::ranges::sort(items);
    return items;
}

// Any type with operator< works
std::vector<int> v1 = {3, 1, 2};
std::vector<std::string> v2 = {"c", "a", "b"};
auto s1 = sort(v1);
auto s2 = sort(v2);
import java.util.*;

public class Sorter {
    public static <T extends Comparable<T>> List<T> sort(List<T> items) {
        var copy = new ArrayList<>(items);
        copy.sort(Comparator.naturalOrder());
        return copy;
    }

    public static void main(String[] args) {
        System.out.println(sort(List.of(3, 1, 2)));
        System.out.println(sort(List.of("c", "a", "b")));
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

public static class Sorter {
    public static List<T> Sort<T>(List<T> items) where T : IComparable<T> {
        var copy = items.ToList();
        copy.Sort();
        return copy;
    }

    static void Main() {
        Console.WriteLine(string.Join(", ", Sort(new List<int> { 3, 1, 2 })));
        Console.WriteLine(string.Join(", ", Sort(new List<string> { "c", "a", "b" })));
    }
}
# Ruby's Comparable module is ad-hoc polymorphism in the standard library:
# any class defining <=> gains <, <=, ==, >, between?, clamp, sort support.

def sort(items)
  items.sort
end

# Any type implementing <=> works
p sort([3, 1, 2])
p sort(%w[c a b])

class Version
  include Comparable
  attr_reader :parts

  def initialize(str)
    @parts = str.split(".").map(&:to_i)
  end

  def <=>(other)
    parts <=> other.parts
  end
end

p sort([Version.new("1.10"), Version.new("1.2")]).map(&:parts)