Skip to main content
Examples Verified (100%)

Intersection Types

Coming Soon

This feature is planned for a future release.

Intersection types allow you to combine multiple types into one, creating a type that has all the properties and methods of each combined type. Think of intersection types as "AND" relationships—a value must satisfy all types in the intersection.

Understanding Intersection Types​

While union types represent "either-or" relationships (A | B means "A OR B"), intersection types represent "and" relationships (A & B means "A AND B").

Union vs Intersection​

# Union type: value can be String OR Integer
type StringOrInt = String | Integer
value1: StringOrInt = "hello" # OK
value2: StringOrInt = 42 # OK

# Intersection type: value must have properties of BOTH types
type NamedAndAged = Named & Aged
# Must have both name (from Named) and age (from Aged)

Basic Intersection Syntax​

The intersection operator is &:

type Combined = TypeA & TypeB & TypeC

Combining Interfaces​

The most common use of intersection types is combining interfaces:

# Define individual interfaces
interface Named
def name: String
end

interface Aged
def age: Integer
end

interface Contactable
def email: String
def phone: String
end

# Combine interfaces with intersection
type Person = Named & Aged
type Employee = Named & Aged & Contactable

# A class implementing the intersection must implement all interfaces
class User
implements Named, Aged

@name: String
@age: Integer

def initialize(name: String, age: Integer): void
@name = name
@age = age
end

def name: String
@name
end

def age: Integer
@age
end
end

# User can be used as Person type
user: Person = User.new("Alice", 30)
puts user.name # OK: Named interface
puts user.age # OK: Aged interface

Mixing Types and Interfaces​

You can combine interfaces with class types:

# Base class
class Entity
@id: Integer

def initialize(id: Integer): void
@id = id
end

def id: Integer
@id
end
end

# Interface
interface Timestamped
def created_at: Time
def updated_at: Time
end

# Intersection of class and interface
type TimestampedEntity = Entity & Timestamped

# Implementation must extend Entity AND implement Timestamped
class User < Entity
implements Timestamped

@name: String
@created_at: Time
@updated_at: Time

def initialize(id: Integer, name: String): void
super(id)
@name = name
@created_at = Time.now
@updated_at = Time.now
end

def created_at: Time
@created_at
end

def updated_at: Time
@updated_at
end
end

# User satisfies the intersection type
user: TimestampedEntity = User.new(1, "Alice")
puts user.id # From Entity class
puts user.created_at # From Timestamped interface

Practical Examples​

Mixins Pattern​

Intersection types work well with Ruby's mixin concept:

# Define capability interfaces
interface Serializable
def to_json: String
def self.from_json(json: String): self
end

interface Validatable
def valid?: Boolean
def errors: String[]
end

interface Persistable
def save: Boolean
def delete: Boolean
end

# Combine capabilities as needed
type Model = Serializable & Validatable & Persistable

# A full-featured model class
class Article
implements Serializable, Validatable, Persistable

@title: String
@content: String
@errors: String[]

def initialize(title: String, content: String): void
@title = title
@content = content
@errors = []
end

def to_json: String
"{ \"title\": \"#{@title}\", \"content\": \"#{@content}\" }"
end

def self.from_json(json: String): Article
# Parse JSON and create instance
Article.new("Title", "Content")
end

def valid?: Boolean
@errors = []
@errors.push("Title cannot be empty") if @title.empty?
@errors.push("Content cannot be empty") if @content.empty?
@errors.empty?
end

def errors: String[]
@errors
end

def save: Boolean
return false unless valid?
# Save to database
true
end

def delete: Boolean
# Delete from database
true
end
end

# Article satisfies Model intersection type
article: Model = Article.new("Hello", "World")
puts article.to_json # Serializable
puts article.valid? # Validatable
article.save # Persistable

Repository Pattern​

interface Identifiable
def id: Integer | String
end

interface Timestamped
def created_at: Time
def updated_at: Time
end

interface SoftDeletable
def deleted?: Boolean
def deleted_at: Time | nil
end

# Different combinations for different needs
type BaseEntity = Identifiable & Timestamped
type DeletableEntity = Identifiable & Timestamped & SoftDeletable

class Repository<T: BaseEntity>
@items: T[]

def initialize: void
@items = []
end

def find(id: Integer | String): T | nil
@items.find { |item| item.id == id }
end

def all: T[]
@items.dup
end

def recent(limit: Integer = 10): T[]
@items.sort_by { |item| item.created_at }.reverse.take(limit)
end
end

class SoftDeleteRepository<T: DeletableEntity> < Repository<T>
def all: T[]
@items.reject { |item| item.deleted? }
end

def with_deleted: T[]
@items.dup
end

def only_deleted: T[]
@items.select { |item| item.deleted? }
end
end

Event System​

interface Event
def event_type: String
def timestamp: Time
end

interface Cancellable
def cancelled?: Boolean
def cancel: void
end

interface Prioritized
def priority: Integer
end

# Different event types with different capabilities
type BasicEvent = Event
type CancellableEvent = Event & Cancellable
type PrioritizedCancellableEvent = Event & Cancellable & Prioritized

class UserClickEvent
implements Event

@event_type: String
@timestamp: Time

def initialize: void
@event_type = "user_click"
@timestamp = Time.now
end

def event_type: String
@event_type
end

def timestamp: Time
@timestamp
end
end

class NetworkRequestEvent
implements Event, Cancellable

@event_type: String
@timestamp: Time
@cancelled: Boolean

def initialize: void
@event_type = "network_request"
@timestamp = Time.now
@cancelled = false
end

def event_type: String
@event_type
end

def timestamp: Time
@timestamp
end

def cancelled?: Boolean
@cancelled
end

def cancel: void
@cancelled = true
end
end

class CriticalAlertEvent
implements Event, Cancellable, Prioritized

@event_type: String
@timestamp: Time
@cancelled: Boolean
@priority: Integer

def initialize(priority: Integer): void
@event_type = "critical_alert"
@timestamp = Time.now
@cancelled = false
@priority = priority
end

def event_type: String
@event_type
end

def timestamp: Time
@timestamp
end

def cancelled?: Boolean
@cancelled
end

def cancel: void
@cancelled = true
end

def priority: Integer
@priority
end
end

# Event handlers for different event types
def handle_basic_event(event: BasicEvent): void
puts "Event: #{event.event_type} at #{event.timestamp}"
end

def handle_cancellable_event(event: CancellableEvent): void
if event.cancelled?
puts "Event #{event.event_type} was cancelled"
else
puts "Processing #{event.event_type}"
end
end

def handle_priority_event(event: PrioritizedCancellableEvent): void
puts "Priority #{event.priority}: #{event.event_type}"
event.cancel if event.priority < 5
end

Intersection with Generics​

Intersection types can be combined with generics:

# Generic type with intersection constraint
def process<T: Serializable & Validatable>(item: T): Boolean
if item.valid?
json = item.to_json
# Send to API
true
else
puts "Validation errors: #{item.errors.join(', ')}"
false
end
end

# Collection that requires multiple capabilities
class ValidatedCollection<T: Identifiable & Validatable>
@items: T[]

def initialize: void
@items = []
end

def add(item: T): Boolean
if item.valid?
@items.push(item)
true
else
false
end
end

def find(id: Integer | String): T | nil
@items.find { |item| item.id == id }
end

def all_valid: T[]
@items.select { |item| item.valid? }
end

def all_invalid: T[]
@items.reject { |item| item.valid? }
end
end

Type Guards and Narrowing​

Intersection types work with type narrowing:

interface Animal
def speak: String
end

interface Swimmable
def swim: void
end

interface Flyable
def fly: void
end

type Duck = Animal & Swimmable & Flyable

class DuckImpl
implements Animal, Swimmable, Flyable

def speak: String
"Quack!"
end

def swim: void
puts "Swimming..."
end

def fly: void
puts "Flying..."
end
end

def test_duck(animal: Animal): void
puts animal.speak

# Type narrowing with responds_to?
if animal.responds_to?(:swim) && animal.responds_to?(:fly)
# Here animal is treated as Duck (Animal & Swimmable & Flyable)
duck = animal as Duck
duck.swim
duck.fly
end
end

Conflicts and Resolution​

When intersection types have conflicting members, the more specific type wins:

interface HasName
def name: String
end

interface HasOptionalName
def name: String | nil
end

# The intersection requires the more restrictive type
type Person = HasName & HasOptionalName
# person.name must be String (not String | nil)
# because String is more specific than String | nil

class User
implements HasName, HasOptionalName

@name: String

def initialize(name: String): void
@name = name
end

# Must return String to satisfy both interfaces
def name: String
@name
end
end

Best Practices​

1. Compose Small, Focused Interfaces​

# Good: Small, single-responsibility interfaces
interface Identifiable
def id: Integer
end

interface Named
def name: String
end

interface Timestamped
def created_at: Time
end

type Entity = Identifiable & Named & Timestamped

# Less good: Large, monolithic interface
interface Entity
def id: Integer
def name: String
def created_at: Time
def updated_at: Time
def save: Boolean
def delete: Boolean
# Too many responsibilities
end

2. Use Meaningful Names​

# Good: Clear what the intersection represents
type AuditedEntity = Entity & Auditable
type SerializableModel = Model & Serializable

# Less good: Generic names
type TypeA = Interface1 & Interface2
type Combined = Foo & Bar

3. Don't Over-Complicate​

# Good: Reasonable number of intersections
type FullModel = Identifiable & Timestamped & Validatable

# Potentially problematic: Too many intersections
type SuperType = A & B & C & D & E & F & G & H
# Hard to implement and understand

4. Document Intent​

# Good: Comment explains why intersection is needed
# Represents entities that can be both serialized and cached
type CacheableEntity = Serializable & Identifiable

# Cache implementation
class Cache<T: CacheableEntity>
@store: Hash<Integer | String, String>

def set(entity: T): void
@store[entity.id] = entity.to_json
end

def get(id: Integer | String): String | nil
@store[id]
end
end

Common Patterns​

Builder Pattern​

interface Buildable
def build: self
end

interface Validatable
def valid?: Boolean
end

interface Resettable
def reset: void
end

type CompleteBuilder = Buildable & Validatable & Resettable

class FormBuilder
implements Buildable, Validatable, Resettable

@fields: Hash<String, String>
@errors: String[]

def initialize: void
@fields = {}
@errors = []
end

def add_field(name: String, value: String): self
@fields[name] = value
self
end

def build: self
self
end

def valid?: Boolean
@errors = []
@errors.push("No fields") if @fields.empty?
@errors.empty?
end

def reset: void
@fields = {}
@errors = []
end
end

State Machine​

interface State
def name: String
end

interface Transitionable
def can_transition_to?(state: String): Boolean
def transition_to(state: String): void
end

interface Observable
def on_enter: void
def on_exit: void
end

type ManagedState = State & Transitionable & Observable

class WorkflowState
implements State, Transitionable, Observable

@name: String
@allowed_transitions: String[]

def initialize(name: String, allowed_transitions: String[]): void
@name = name
@allowed_transitions = allowed_transitions
end

def name: String
@name
end

def can_transition_to?(state: String): Boolean
@allowed_transitions.include?(state)
end

def transition_to(state: String): void
if can_transition_to?(state)
on_exit
# Perform transition
on_enter
else
raise "Invalid transition from #{@name} to #{state}"
end
end

def on_enter: void
puts "Entering state: #{@name}"
end

def on_exit: void
puts "Exiting state: #{@name}"
end
end

Limitations​

Cannot Intersect Primitive Types​

# This doesn't make sense - a value can't be both String AND Integer
# type Impossible = String & Integer # Would be empty type

# Intersection makes sense for structural types (interfaces, classes)
type Valid = Interface1 & Interface2

Implementation Requirements​

# When using intersection, implementation must satisfy ALL parts
type Complete = Interface1 & Interface2 & Interface3

class MyClass
# Must implement ALL of: Interface1, Interface2, Interface3
implements Interface1, Interface2, Interface3
# ...
end

Next Steps​

Now that you understand intersection types, explore: