Which is the more idiomatic approach for pinning down the type of a generic parameter?


(Marco Masser) #1

I want to write an extension on SequenceType that contains a set of methods named findFirst() that should return the first element that matches the given predicate.

For heterogeneous sequences where the caller is only interested in a specific subtype of elements, it is useful to have a variant of findFirst() that allows somehow specifying the type the caller is interested in so the predicate closure only gets called with that specific type.

That was probably not very easy to understand, but this example hopefully explains it clearly:
In an NSView’s subviews array (which are NSViews), find the first NSButton whose state is NSOnState. It would be neat if the predicate closure that checks the state property only gets passed NSButtons and no NSViews because that eliminates type checks in the closure.

I have two candidates for the implementation of this method and I’m looking for the more idiomatic one.

The examples work with a variable called containerView, which is of type NSView.

First try:

extension SequenceType {

    @warn_unused_result
    func findFirst<T>(_: T.Type, @noescape matching predicate: T throws -> Bool) rethrows -> T? {
        for case let element as T in self where try predicate(element) {
            return element
        }
        return nil
    }

}

let result1 = containerView.subviews.findFirst(NSButton.self, matching: { $0.state == NSOnState })

Here, the first parameter (which is ignored in the implementation) pins down the type of the generic parameter. Note that the predicate’s parameter is of type NSButton so there’s no as? or something like that necessary.

But there’s another way to pin down the generic parameter:

extension SequenceType {

    @warn_unused_result
    func findFirst<T>(@noescape matching predicate: T throws -> Bool) rethrows -> T? {
        for case let element as T in self where try predicate(element) {
            return element
        }
        return nil
    }

}

let result2: NSButton? = containerView.subviews.findFirst { $0.state == NSOnState }

This time, the generic parameter is inferred at call site by explicitly specifying the type of the result2 variable.

Note that with this implementation of findFirst() it’s also possible to pin down the generic parameter by giving the closure parameter a specific type:

let result3 = containerView.subviews.findFirst { (button: NSButton) in button.state == NSOnState }

The first implementation seems nice to me at the call site because users of the API can’t miss specifying the type of T. But I don’t really like the way NSButton.self looks and from a language standpoint, the first parameter is redundant. It’s even ignored in the implementation!

The second implementation seems more correct to me, but at the call site, it may be easy to miss specifying the resulting variable’s type, which leads to a compiler error that is not 100% obvious to newcomers (“Type of expression is ambiguous without more context” pointing at the $0), which leads to users thinking about what they did wrong, which leads to them coming to me asking how to use this method.

I’d love to hear comments on this and what you think is the better way to implement this. If that were a method in the Swift Standard Library, how would you expect it to work?

(Bonus question: Why isn’t there a method like this in the Standard Library?)


(Zhao Xin) #2

What about dropping T, just using Element, and converting Element to the
type in your predicate?

zhaoxin

···

On Wed, Jan 20, 2016 at 6:09 PM, Marco Masser via swift-users < swift-users@swift.org> wrote:

I want to write an extension on SequenceType that contains a set of
methods named findFirst() that should return the first element that
matches the given predicate.

For heterogeneous sequences where the caller is only interested in a
specific subtype of elements, it is useful to have a variant of
findFirst() that allows somehow specifying the type the caller is
interested in so the predicate closure only gets called with that specific
type.

That was probably not very easy to understand, but this example hopefully
explains it clearly:
In an NSView’s subviews array (which are NSViews), find the first NSButton
whose state is NSOnState. It would be neat if the predicate closure that
checks the state property only gets passed NSButtons and no NSViews
because that eliminates type checks in the closure.

I have two candidates for the implementation of this method and I’m
looking for the more idiomatic one.

The examples work with a variable called containerView, which is of type
NSView.

First try:

extension SequenceType {

    @warn_unused_result
    func findFirst<T>(_: T.Type, @noescape matching predicate: T throws
-> Bool) rethrows -> T? {
        for case let element as T in self where try predicate(element) {
            return element
        }
        return nil
    }

}

let result1 = containerView.subviews.findFirst(NSButton.self, matching: {
$0.state == NSOnState })

Here, the first parameter (which is ignored in the implementation) pins
down the type of the generic parameter. Note that the predicate’s parameter
is of type NSButton so there’s no as? or something like that necessary.

But there’s another way to pin down the generic parameter:

extension SequenceType {

    @warn_unused_result
    func findFirst<T>(@noescape matching predicate: T throws -> Bool)
rethrows -> T? {
        for case let element as T in self where try predicate(element) {
            return element
        }
        return nil
    }

}

let result2: NSButton? = containerView.subviews.findFirst { $0.state ==
NSOnState }

This time, the generic parameter is inferred at call site by explicitly
specifying the type of the result2 variable.

Note that with this implementation of findFirst() it’s also possible to
pin down the generic parameter by giving the closure parameter a specific
type:

let result3 = containerView.subviews.findFirst { (button: NSButton) in
button.state == NSOnState }

The first implementation seems nice to me at the call site because users
of the API can’t miss specifying the type of T. But I don’t really like
the way NSButton.self looks and from a language standpoint, the first
parameter is redundant. It’s even ignored in the implementation!

The second implementation seems more correct to me, but at the call site,
it may be easy to miss specifying the resulting variable’s type, which
leads to a compiler error that is not 100% obvious to newcomers (“Type of
expression is ambiguous without more context” pointing at the $0), which
leads to users thinking about what they did wrong, which leads to them
coming to me asking how to use this method.

I’d love to hear comments on this and what you think is the better way to
implement this. If that were a method in the Swift Standard Library, how
would you expect it to work?

(Bonus question: Why isn’t there a method like this in the Standard
Library?)

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Marco Masser) #3

As I wrote in my original message, I’m writing a whole set of findFirst() methods and one of them simply takes a predicate and returns an Element:

@warn_unused_result
func findFirst(@noescape predicate: Self.Generator.Element throws -> Bool) rethrows -> Self.Generator.Element? {
    for element in self where try predicate(element) {
        return element
    }
    return nil
}

If I understand correctly, you propose to check the type of the passed-in Element in the predicate, like this:

let result1 = containerView.subviews.findFirst { ($0 as? NSButton)?.state == NSOnState }

But that is cumbersome and has a very significant drawback: The type of result1 is now NSView, not NSButton.
That’s the whole point for the generic version of the method in my original message: the predicate doesn’t have to check the type, and the result of findFirst() has the type the caller specifies.
So to get the same result as with my generic method (but uglier and slower due to more type casts) you’d have to do this:

let result2 = containerView.subviews.findFirst { ($0 as? NSButton)?.state == NSOnState } as? NSButton

I think that’s not very nice compared to how the two generic versions in question look:

let result3 = containerView.subviews.findFirst(NSButton.self, matching: { $0.state == NSOnState })
or:
let result4: NSButton? = containerView.subviews.findFirst { $0.state == NSOnState }

···

On 2016-01-20, at 11:25, zhaoxin肇鑫 <owenzx@gmail.com> wrote:

What about dropping T, just using Element, and converting Element to the type in your predicate?


(Zhao Xin) #4

Then I think the function with NSButton.self is the best. It makes least
efforts for a programmer to get no mistakes.

zhaoxin

···

On Wed, Jan 20, 2016 at 7:25 PM, Marco Masser <lists@duckcode.com> wrote:

On 2016-01-20, at 11:25, zhaoxin肇鑫 <owenzx@gmail.com> wrote:

What about dropping T, just using Element, and converting Element to the
type in your predicate?

As I wrote in my original message, I’m writing a whole set of findFirst() methods
and one of them simply takes a predicate and returns an Element:

@warn_unused_result
func findFirst(@noescape predicate: Self.Generator.Element throws -> Bool)
rethrows -> Self.Generator.Element? {
    for element in self where try predicate(element) {
        return element
    }
    return nil
}

If I understand correctly, you propose to check the type of the passed-in
Element in the predicate, like this:

let result1 = containerView.subviews.findFirst { ($0 as? NSButton)?.state
== NSOnState }

But that is cumbersome and has a very significant drawback: The type of
result1 is now NSView, not NSButton.
That’s the whole point for the generic version of the method in my
original message: the predicate doesn’t have to check the type, and the
result of findFirst() has the type the caller specifies.
So to get the same result as with my generic method (but uglier and slower
due to more type casts) you’d have to do this:

let result2 = containerView.subviews.findFirst { ($0 as? NSButton)?.state
== NSOnState } as? NSButton

I think that’s not very nice compared to how the two generic versions in
question look:

let result3 = containerView.subviews.findFirst(NSButton.self, matching: {
$0.state == NSOnState })
or:
let result4: NSButton? = containerView.subviews.findFirst { $0.state ==
NSOnState }


(Marco Masser) #5

OK, thank you for your thoughts on this!

I thought I’d mention that I got the idea for dropping the type parameter and shifting that to the caller by reading a blog post by Guille Gonzalez:
https://medium.com/@gonzalezreal/ios-cell-registration-reusing-with-swift-protocol-extensions-and-generics-c5ac4fb5b75e

The very last code snippet in that blog post does exactly that:

let cell: BookCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)

···

On 2016-01-20, at 13:23, zhaoxin肇鑫 <owenzx@gmail.com> wrote:

Then I think the function with NSButton.self is the best. It makes least efforts for a programmer to get no mistakes.