user6900229
user6900229

Reputation:

Pass a type to a function to return a closure that returns that type?

I've been working on Exercism.io's Swift track, after refactoring my solution for the BinarySearchTree problem to smallish code, I noticed a pattern that seems like it could be refactored. I think the answer is "No, you can't do that," but I figured it was worth asking.

typealias BinarySearchTree = BST

indirect enum BST< T: Comparable> {
    case N( T, BST, BST )
    case E

    init( _ v: T ) { self = .N( v, .E, .E ) }

    mutating func insert( _ n: T ) {
        if case .N( let v, var l, var r ) = self {
            if n <= v { l.insert( n ) } else { r.insert( n ) }
            self = .N( v, l, r )
        } else { self = .N( n, .E, .E ) }
    }

    var  data:    T?        { if case let .N( v, _, _ ) = self { return v } else { return nil } }
    var  left:    BST?      { if case let .N( _, l, _ ) = self { return l } else { return nil } }
    var  right:   BST?      { if case let .N( _, _, r ) = self { return r } else { return nil } }

    func allData() -> [ T ] { if case let .N( v, l, r ) = self { return l.allData() + [ v ] as [ T ] + r.allData() } else { return [ T ]() } }
}

My focus is on vars data, left, and right. See how they're almost spitting images of each other? Not that it should be done, but could it be done: Write a function that accepts T or BST through one parameter, a tuple position through another (Int), and returns a function or closure that enum decomposition and returns the captured value of passed in type?

I realize the short names and one-liner-ness of my code is far past the point of silly. It emerged after a few iterations over this initial solution, which I think you'll agree is more readable, though the pattern is less noticeable:

indirect enum BinarySearchTree< T: Comparable> {
    case Node( T, BinarySearchTree, BinarySearchTree )
    case None

    init( _ value: T ) { self = .Node( value, .None, .None ) }

    mutating func insert( _ new: T ) {
        if case .Node( let value, var left, var right ) = self {
            if new <= value { left.insert( new ) }
            else { right.insert( new ) }

            self = .Node( value, left, right )
        } else { self = .Node( new, .None, .None ) }
    }

    var data: T? {
        if case let .Node( value, _, _ ) = self { return value }
        else { return nil }
    }

    func allData() -> [ T ] {
        if case let .Node( value, left, right ) = self {
            return left.allData() + [ value ] as [ T ] + right.allData()
        } else { return [ T ]() }
    }

    var left: BinarySearchTree? {
        if case let .Node( _, left, _ ) = self { return left }
        else { return nil }
    }

    var right: BinarySearchTree? {
        if case let .Node( _, _, right ) = self { return right }
        else { return nil }
    }
}

Upvotes: 1

Views: 51

Answers (1)

Rob Napier
Rob Napier

Reputation: 299495

This can absolutely be simplified, and probably should be, with a helper function:

private var vlr: (T, BST, BST)? {
    switch self {
    case let .N(v, l, r): return (v,l,r)
    case .E: return nil
    }
}

var  data:  T?   { return vlr?.0 }
var  left:  BST? { return vlr?.1 }
var  right: BST? { return vlr?.2 }

I build these kinds of helpers on enums to turn associated data into Optionals all the time (in some cases as a public method).

This can be used in allData, too, which I'd probably write this way (chained + is notorious for creating compile problems in Swift, and can be extremely runtime inefficient due to creating intermediate copies):

func allData() -> [ T ] {
    if let (v, l, r) = vlr {
        var result = l.allData()
        result.append(v)
        result.append(contentsOf: r.allData())
        return result
    }
    return []
}

Upvotes: 1

Related Questions