Swift from Two to Three: Currying Favor

First entry into the “Swift 2 to 3” challenge comes from Cristian Filipov, who brings me a routine to extract words from phone numbers.

There are several points of interest in this port, which I’ll get to in just a second, but since his code didn’t arrive with the vital isWord(string:) function, I had to build one myself.

Building isWord

It’s in Cocoa because I find that writing OS X Command Line apps are the most congenial way to build small Swift 3 apps. At this point, playgrounds (as you see, his code was originally intended for playground use with the in-line markup) do not support Swift 3. That should change within the next two weeks but I didn’t want to wait for WWDC to start showing tricks of the transition trade.

So here’s the function in question:

import Cocoa
func isWord(string: String) -> Bool {
    // If it's a number, nope
    if let _ = Int(string) { return false }

    // Use NSSpellChecker to find misspelling
    // if it fails, the string is proper
    let range = NSSpellChecker.shared()
        .checkSpelling(of: string, startingAt: 0)
    return range.location == NSNotFound
}

Currying

Christian’s code heavily depends on currying, which is one of the easiest points of transition in Swift 2 to Swift 3 code. Here’s an example:

func transform<T: Hashable, V>(dict: [T:V])(element: T) -> V? {
    return dict[element]
}

Establishing the dictionary is curried from looking up an element in the dictionary. How to fix?

  1. Place an arrow between each curried element
  2. Use “return { argument in...}” closures for each curry

Here’s the fixed example

func transform<T: Hashable, V>(dict: [T:V]) -> (element: T) -> V? {
    return {
        element in
        return dict[element]
    }
}

Mandated Function Parens

This is one of the Swift 3 changes I was really excited to see adopted. Prior to Swift 3, you could specify types of T -> U. Now it’s uniformly (T) -> U. So this example:

func not<T>(pred: T -> Bool)(e: T) -> Bool {
    return !pred(e)
}

becomes the following (with some bonus curry update):

func not<T>(pred: (T) -> Bool) -> (e: T) -> Bool {
    return {
        e in
        return !pred(e)
    }
}

Taking Charge of Parameter Labels

With Swift 3’s new mandated first parameter labels, it’s time to toss away duplicated names and add in first parameter labels that normally defaulted away. Starting here:

func phoneWords(minLength minLength: Int)(number: String) -> [String] {
    if number.characters
        .filter(("2"..."9").contains)
        .count >= minLength {
        return phoneWords(number)
    } else {
        return [number]
    }
}

Ending up here:

func phoneWords(minLength: Int) -> (number: String) -> [String] {
 return {
     number in
     if number.characters
          .filter(("2"..."9").contains)
          .count >= minLength {
            return phoneWords(number: number)
        } else {
            return [number]
        }
    }
}

Other tweaks

I had to fix the isSeparator closure (needed a closure and a first parameter), and add in one or two more parentheses around parameter types but other than the mystifying spellchecker that insists that “eloydqs” is a word just like “flowers”, everything seems to work beautifully

Screen Shot 2016-06-03 at 2.55.34 PM

You can find the diffed/forked gist here.

Got a short standalone Swift 2.2 code sample that needs updating to new syntax and concepts? Send it along and I may do a post on it. Please no requirements outside the sample, and keep the code short. Code that introduces new kinds of issues (for example, currying is pretty much already covered now) will be preferred.

9 Comments

  • Wow, don’t know how I missed that. Updated the gist to include the isWord function I forgot to include: https://gist.github.com/cfilipov/1ece2ca2973a4fa4c712

    • You can also use UIKit’s text checking, would simplify

      • Yeah, I like your method better, just felt the need to correct the accidental omission.

  • By the way, I thought this was no longer supposed to be possible in Swift 3:

    .map(String.init)

    Doesn’t that essentially make use of currying?

    • “Curried function syntax has been deprecated, and is slated to be removed in Swift 3.0.” Not all currying functionality and not this particular use, just the declaration functionality syntax.

  • I swear I saw mention of this specific use being deprecated too. But I can’t find it now so I must be mistaken.

  • Erica, how can I filter a string in Swift 3.0 for only English Words and names? Any points in the right direction?

    • Depends if it is single words, multiple words, case insensitive, dictionary membership, etc. Cocoa and Cocoa Touch have a number of classes that can help including NSLinguisticTagger and NSSpellChecker

      • Thanks Erica, will check those out!