[swift-evolution] [swift-evolution-announce] [Review] SE-0023 API Design Guidelines

David Owens II david at owensd.io
Mon Jan 25 11:55:52 CST 2016


Let’s try again with less of a sleepy-sick stupor. =)

My concern is that the name of the function is the only indicator for non-struct types when looking at the signature. For structs, we can get more assurance; e.g. the bug demonstrated below cannot happen. However, that doesn’t mean the opposite bug is avoidable: return a copying instead of mutating self.

The problem I was trying to illustrate, and did a really poor job at, is that the convention doesn’t actually provide us with any guarantees that our implementations are correct. So when I say this:

> I guess my point is this: codifying a convention seems pre-mature as that convention doesn't bring the safety goals of the language into a place that's verifiable. All of the other guidelines are simply about clarity of use, this convention has a far reaching impact.


Here’s a demonstration of when the guidelines was applied incorrectly, either due to ignorance of the guidelines or simply a copy/paste type bug.

var bar = Bar(items: [2, 1, 3])
let filteredBar = bar.filter { $0 != 3 }

bar.items                      // output: [2, 1 3]
filteredBar.items              // output: [2, 1]

let filteredSortedBar = bar
    .filter { $0 != 3 }
    .sort()

bar.items                      // output: [2, 1, 3]
filteredSortedBar.items        // output: [1, 2]

let sortedFilterBar = bar
    .sort()
    .filter { $0 != 3 }

bar.items                      // output: [1, 2, 3] WHAT?
sortedFilterBar.items          // output: [1, 2]

var expected = Bar(items: [2, 1, 3])
expected
    .filterInPlace { $0 != 3 }
    .sortInPlace()

expected.items                 // output: [1, 2]

When reading the code with an understanding of the guidelines, this code is misleading as to what it should be doing. That’s what I find dangerous. Of course, we can argue on the merits of these types of bugs, but we’ve all seen conventions misused and it sucks.

If this convention is just a stop-gap until a language feature can be built to help with this, then ok. 

-David


sample implementation that demonstrates the bug that gets us out-of-sync with the guidelines:

public class Bar<T : Comparable> {
    var items: [T]
    
    init(items: [T]) {
        self.items = items
    }
    
    func sortInPlace() -> Self {
        self.items.sortInPlace { $0 < $1 }
        return self
    }
    
    func sort() -> Self {
        self.items.sortInPlace { $0 < $1 }
        return self
    }
    
    func filterInPlace(includeElement: (T) -> Bool) -> Self {
        self.items = self.items.filter(includeElement)
        return self
    }
    
    func filter(includeElement: (T) -> Bool) -> Bar<T> {
        let newItems: [T] = self.items.filter(includeElement)
        return Bar(items: newItems)
    }
}

> On Jan 24, 2016, at 7:18 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Sun Jan 24 2016, David Owens II <david-AT-owensd.io> wrote:
> 
>> Sorry, I meant to add the do() version too in order to show the
>> difference better. /sigh
> 
> do() version?
> 
>> 
>>> On Jan 24, 2016, at 4:53 PM, David Owens II via swift-evolution
>>> <swift-evolution at swift.org> wrote:
>>> 
>>> 
>>>>> I guess my point is this: codifying a convention seems pre-mature as
>>>>> that convention doesn't bring the safety goals of the language into a
>>>>> place that's verifiable. All of the other guidelines are simply about
>>>>> clarity of use, this convention has a far reaching impact.
>>>> 
>>>> Sorry, could you clarify what you mean by "bring the safety goals of the
>>>> language into a place that's verifiable" and clarify why having a "far
>>>> reaching impact" would somehow conflict with being "about clarity of use?"
>>>> 
>>>> It seems to me that this convention is about how to express whether a
>>>> method is going to mutate so it's clear at the use-site.  What am I
>>>> missing?
>>> 
>>> 
>>> The problem is it's unclear to me whether you mean mutate in the
>>> true sense of the word or only applied to a struct with a function
>>> annotated with the mutating keyword.
>>> 
>>> The naming convention provides no safety when dealing with
>>> non-struct types as we cannot enforce that a method on a class does
>>> not mutate it's internal members.
>>> 
>>> That's the clarity I'm looking for.
>>> 
>>> Given this API set:
>>> 
>>> protocol InPlaceable {
>>>    mutating func doInPlace()
>>> }
>>> 
>>> public struct Foo: InPlaceable {
>>>    mutating func doInPlace() {}
>>> }
>>> 
>>> public class Bar: InPlaceable {
>>>    func doInPlace() {}
>>> }
>>> 
>>> var lie: InPlaceable = Bar()
>>> lie.doInPlace()
>>> 
>>> let lie2 = Bar()
>>> lie2.doInPlace()
>>> 
>>> The convention will tell us a lie unless we are extremely
>>> careful. It's this lie that concerns me. We cannot guarantee that
>>> the "doInPlace" truly matches the definition we are seeking.
>>> 
>>> 
>>> -David
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 
> -- 
> -Dave

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160125/583f88c4/attachment.html>


More information about the swift-evolution mailing list