iosswiftoperatorsfluent

Swift fluent-like API design with custom operators


I would like to write some kind of fluent-like API with custom operators and functions. Here's an example of what I'm looking for:

var result = [Section]()
    
result +++= Section()
    .appendTitle(title)
    .appendPhotos(photos)

result +++= Section()
    .appendSomething(sth)
    .appendPhotos(photos)
    .appendAnything()

return result

To do that, I've declared two custom operators:

infix operator +++{ associativity left precedence 95 }

func +++(lhs : [Section], rhs : Section) -> [Section] {
    var result = lhs
    result.append(rhs)
    return result
}

infix operator +++= { associativity left precedence 95 }

func +++=(inout lhs : [Section], rhs : Section) {
    lhs = lhs +++ rhs
}

And of course the correct extension for the Section struct:

extension Section {
    mutating func appendTitle(title: String?) -> Section {
        guard let unwrappedTitle = title
            else { return self }
    
        ...
    
        return self
    }

    mutating func appendPhotos(photos: [Photo]?) -> Section {
        ...
    }

    ...
}

Unfortunately that does not work as expected...

The line result +++= Section() alone is correct, but when I add the .append it does not compile.

The first message is:

Passing value of type '[Section]' to an inout parameter requires explicit '&'

Then I tried to put an & in front of result (but I've never done it for a += 1) there's a second message:

Cannot use mutating member on immutable value: function call returns immutable value

So if anyone can help, it would be much appreciated.

I'm using Swift 2.2 and Xcode 7.


Solution

  • Well, You've found a solution. That's Great!

    But Please allow me to explain what has actually happened here.

    Let's take a closer look at your earlier implementation.

    extension Section {
        mutating func appendTitle(title: String?) -> Section {
    
            guard let unwrappedTitle = title else { return self }
            self.title = unwrappedTitle
            return self
        }
    
        mutating func appendPhotos(photos: [Photo]?) -> Section {
            guard let unwrappedPhotos = photos else { return self }
            self.photos = unwrappedPhotos
            return self
        }
    }
    

    We know (atleast now) that operator overloading functions are absolutely fine, Nothing wrong there.

    Given extension is changing values for a parameter, hence it should be mutating, and it is. Then why the error :

    "Cannot use mutating member on immutable value: function call returns immutable value"

    Reason : When "appendTitle" or "appendPhotos" is invoked, it returns a copy of self. Not the exact same reference!

    This is because of the way swift deals with immutability. If value for a member property is changed for a value type, a copy is created with assigning the value to the respective member.

    Hence, when you write : Section().appendTitle(title).appendPhotos(photos)

    Section() returns a copy of self i.e. an immutable version of Section. Therefore one can't appendTitle to it, or cannot mutate it in general.

    That can be solved :

    var section = Section() section.appendTitle(title).appendPhotos(photos)

    But now appendTitle(title) returns a copy of self i.e. an immutable version of Section. and same problem persists.

    What you did was that instead of changing anything with self, you created a new instance, this time a mutable one using var. Mutated it and returned it. Now it's not mutating self, hence it need not be a mutating func.

    The solution you provided works well here, but in reality it is a hack, which is needed here to cover what i would say a "bug" in Swift for now.

    Other solutions can be changing struct Section to class Section. There you would not need to create var result, just change the properties and return.

    This is my first answer on SO :)