     associatedtype Right : Table
     
     var join:Join<Left, Right> {get}
+}
+
+public extension JoinProtocol {
+    public var datasets:(Left, Right) {
+        switch join {
+        case .cross(left: let left, right: let right):
+            return (left, right)
+        case .inner(left: let left, right: let right, condition: _):
+            return (left, right)
+        case .outer(left: let left, right: let right, condition: _, direction: _):
+            return (left, right)
+        }
+    }
+    
+    func replace(left _left:Left? = nil, right _right:Right? = nil) -> Join<Left, Right> {
+        switch join {
+        case .cross(left: let left, right: let right):
+            return .cross(left: _left ?? left, right: _right ?? right)
+        case .inner(left: let left, right: let right, condition: let condition):
+            return .inner(left: _left ?? left, right: _right ?? right, condition: condition)
+        case .outer(left: let left, right: let right, condition: let condition, direction: let direction):
+            return .outer(left: _left ?? left, right: _right ?? right, condition: condition, direction: direction)
+        }
+    }
 }
 
 public indirect enum Join<A : Dataset, B : Table> : Dataset, JoinProtocol {

    public init(name:String, columns:Columns = .all) {
        self.name = name
        self.columns = columns
    }
}



public protocol Table : Named, Dataset {
    var columns:Columns {get}
    
    init(name:String, columns:Columns)
    
    subscript(_ column:String) -> Column {get}
}

public protocol Column : Named {
    var table:Table {get}
}

public struct ErasedColumn : Column {
    public let name:String
    public let table:Table
    
    init(name: String, in table: Table) {
        self.name = name
        self.table = table
    }
}

public struct ErasedTable : Table {
