Making functions generic on optionals


(Kenny Leung) #1

Hi All.

In the code below, I have the method

public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor]

In order to make life easier, I would like to make sortString optional, but then I would have to make the return type optional to to be able to return nil if the argument is nil. To get around this, I’ve added a stub method

public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]?

that declares the argument to be optional and the return type to be optional.

I get the feeling that there is a way to not have to write a stub method every time I want to do this. I also get the feeling that I should be able to accomplish it through generics. But I do not know how to write the declaration for such a method. Can anyone help?

Thanks!

-Kenny

extension NSSortDescriptor {

    public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor] {
        var descriptors = [NSSortDescriptor]()
        let components = sortString.split("[, ]+")

        for i in 0.stride(to: components.count, by: 2) {
            let key = components[i]
            let direction = components[i + 1]
            var descriptor :NSSortDescriptor?

            if SORT_STRINGS_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true)
            } else if SORT_STRINGS_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false)
            } else if SORT_STRINGS_CASEINSENSITIVE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "caseInsensitiveCompare:");
            } else if SORT_STRINGS_CASEINSENSITIVE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "caseInsensitiveCompare:");
            } else if SORT_DATE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "compare:");
            } else if SORT_DATE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "compare:");
            }

            if let nnDescriptor = descriptor {
                descriptors.append(nnDescriptor)
            } else {
                throw NSSortDescriptorError.UnsupportedSortDirection
            }
        }

        return descriptors
    }

    public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]? {
        guard let sortString = sortString else {return nil}
        return try self.sortDescriptorsFromString(sortString)
    }

}


(Jordan Rose) #2

The smallest way to write this is at the call site:

sortStringOpt.map { NSSortDescriptor.sortDescriptorsFromString($0) }

But really I'm a little curious about why you're thinking about "every time [you] want to do this". Does this really come up that often? In this particular case, why would you ever not have a sort string?

Jordan

···

On Feb 5, 2016, at 13:53, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi All.

In the code below, I have the method

public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor]

In order to make life easier, I would like to make sortString optional, but then I would have to make the return type optional to to be able to return nil if the argument is nil. To get around this, I’ve added a stub method

public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]?

that declares the argument to be optional and the return type to be optional.

I get the feeling that there is a way to not have to write a stub method every time I want to do this. I also get the feeling that I should be able to accomplish it through generics. But I do not know how to write the declaration for such a method. Can anyone help?

Thanks!

-Kenny

extension NSSortDescriptor {

    public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor] {
        var descriptors = [NSSortDescriptor]()
        let components = sortString.split("[, ]+")

        for i in 0.stride(to: components.count, by: 2) {
            let key = components[i]
            let direction = components[i + 1]
            var descriptor :NSSortDescriptor?

            if SORT_STRINGS_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true)
            } else if SORT_STRINGS_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false)
            } else if SORT_STRINGS_CASEINSENSITIVE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "caseInsensitiveCompare:");
            } else if SORT_STRINGS_CASEINSENSITIVE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "caseInsensitiveCompare:");
            } else if SORT_DATE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "compare:");
            } else if SORT_DATE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "compare:");
            }

            if let nnDescriptor = descriptor {
                descriptors.append(nnDescriptor)
            } else {
                throw NSSortDescriptorError.UnsupportedSortDirection
            }
        }

        return descriptors
    }

    public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]? {
        guard let sortString = sortString else {return nil}
        return try self.sortDescriptorsFromString(sortString)
    }

}

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


(Brent Royal-Gordon) #3

    public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]? {
        guard let sortString = sortString else {return nil}
        return try self.sortDescriptorsFromString(sortString)
    }

}

This is equivalent to (something like):

  return try sortString.map(self.sortDescriptorsFromString)

You can always use that directly at the call sites where you have an optional String.

···

--
Brent Royal-Gordon
Architechies


(Kenny Leung) #4

Hi Jordan.

Thanks for the response.

The smallest way to write this is at the call site:

sortStringOpt.map { NSSortDescriptor.sortDescriptorsFromString($0) }

Sorry, but I don’t understand this solution. What type is sortStringOpt?

But really I'm a little curious about why you're thinking about "every time [you] want to do this". Does this really come up that often? In this particular case, why would you ever not have a sort string?

In particular, sortString might come from user input, so it may be nil.

In general, “every time I want to do this” was referring to providing a piece of API that could take optional or non-optional inputs without either burdening the provider with writing a stub method or the client with having to always deal with an optional return from the method.

-Kenny

···

On Feb 5, 2016, at 2:34 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Jordan

On Feb 5, 2016, at 13:53, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi All.

In the code below, I have the method

public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor]

In order to make life easier, I would like to make sortString optional, but then I would have to make the return type optional to to be able to return nil if the argument is nil. To get around this, I’ve added a stub method

public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]?

that declares the argument to be optional and the return type to be optional.

I get the feeling that there is a way to not have to write a stub method every time I want to do this. I also get the feeling that I should be able to accomplish it through generics. But I do not know how to write the declaration for such a method. Can anyone help?

Thanks!

-Kenny

extension NSSortDescriptor {

    public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor] {
        var descriptors = [NSSortDescriptor]()
        let components = sortString.split("[, ]+")

        for i in 0.stride(to: components.count, by: 2) {
            let key = components[i]
            let direction = components[i + 1]
            var descriptor :NSSortDescriptor?

            if SORT_STRINGS_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true)
            } else if SORT_STRINGS_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false)
            } else if SORT_STRINGS_CASEINSENSITIVE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "caseInsensitiveCompare:");
            } else if SORT_STRINGS_CASEINSENSITIVE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "caseInsensitiveCompare:");
            } else if SORT_DATE_ASCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: true, selector: "compare:");
            } else if SORT_DATE_DESCENDING.contains(direction) {
                descriptor = NSSortDescriptor(key: key, ascending: false, selector: "compare:");
            }

            if let nnDescriptor = descriptor {
                descriptors.append(nnDescriptor)
            } else {
                throw NSSortDescriptorError.UnsupportedSortDirection
            }
        }

        return descriptors
    }

    public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]? {
        guard let sortString = sortString else {return nil}
        return try self.sortDescriptorsFromString(sortString)
    }

}

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


(Kenny Leung) #5

Thanks for your answers.

After some googling and experimenting, I get it now:

    func testOptionalMap() {
        do {
            var sortString :String?
            var sortDescriptors :[NSSortDescriptor]?

            sortString = "name,up"
            try sortDescriptors = sortString.map {try NSSortDescriptor.sortDescriptorsFromString($0)}
            XCTAssertNotNil(sortDescriptors);
            if let sortDescriptor = sortDescriptors?[0] {
                assert(sortDescriptor, "name", true)
            }

            sortString = nil
            try sortDescriptors = sortString.map {try NSSortDescriptor.sortDescriptorsFromString($0)}
            XCTAssertNil(sortDescriptors);
        } catch {
            XCTFail()
        }
    }

I commented out the stub method, yet I can still send an optional into sortDescriptorsFromString.

I have to say, though, this is a very unfriendly way to solve this problem.

- since optionals are so hidden syntax wise, you could naturally assume that map belongs to String, and not Optional
- reading the code, it’s not obvious what the map function is actually doing here. You really have to squint your eyes to realize that

“Optional.map will only execute the code in the block if the thing it’s wrapping is not nil, and $0 is the thing it’s wrapping, unwrapped"

So I’ll try rephrasing my question:

Is there way to declare a method such that, if the argument is optional, the return value is optional, but if the argument is not optional, the return value is also not optional? The argument and the return values are not the same type.

Thanks!

-Kenny

···

On Feb 5, 2016, at 14:45, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi Jordan.

Thanks for the response.

On Feb 5, 2016, at 2:34 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

The smallest way to write this is at the call site:

sortStringOpt.map { NSSortDescriptor.sortDescriptorsFromString($0) }

Sorry, but I don’t understand this solution. What type is sortStringOpt?

But really I'm a little curious about why you're thinking about "every time [you] want to do this". Does this really come up that often? In this particular case, why would you ever not have a sort string?

In particular, sortString might come from user input, so it may be nil.

In general, “every time I want to do this” was referring to providing a piece of API that could take optional or non-optional inputs without either burdening the provider with writing a stub method or the client with having to always deal with an optional return from the method.

-Kenny

Jordan

On Feb 5, 2016, at 13:53, Kenny Leung via swift-users <swift-users@swift.org> wrote:

Hi All.

In the code below, I have the method

public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor]

In order to make life easier, I would like to make sortString optional, but then I would have to make the return type optional to to be able to return nil if the argument is nil. To get around this, I’ve added a stub method

public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]?

that declares the argument to be optional and the return type to be optional.

I get the feeling that there is a way to not have to write a stub method every time I want to do this. I also get the feeling that I should be able to accomplish it through generics. But I do not know how to write the declaration for such a method. Can anyone help?

Thanks!

-Kenny

extension NSSortDescriptor {

   public class func sortDescriptorsFromString(sortString :String) throws -> [NSSortDescriptor] {
       var descriptors = [NSSortDescriptor]()
       let components = sortString.split("[, ]+")

       for i in 0.stride(to: components.count, by: 2) {
           let key = components[i]
           let direction = components[i + 1]
           var descriptor :NSSortDescriptor?

           if SORT_STRINGS_ASCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: true)
           } else if SORT_STRINGS_DESCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: false)
           } else if SORT_STRINGS_CASEINSENSITIVE_ASCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: true, selector: "caseInsensitiveCompare:");
           } else if SORT_STRINGS_CASEINSENSITIVE_DESCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: false, selector: "caseInsensitiveCompare:");
           } else if SORT_DATE_ASCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: true, selector: "compare:");
           } else if SORT_DATE_DESCENDING.contains(direction) {
               descriptor = NSSortDescriptor(key: key, ascending: false, selector: "compare:");
           }

           if let nnDescriptor = descriptor {
               descriptors.append(nnDescriptor)
           } else {
               throw NSSortDescriptorError.UnsupportedSortDirection
           }
       }

       return descriptors
   }

   public class func sortDescriptorsFromString(sortString :String?) throws -> [NSSortDescriptor]? {
       guard let sortString = sortString else {return nil}
       return try self.sortDescriptorsFromString(sortString)
   }

}

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

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