Alexito's World

A world of coding 💻, by Alejandro Martinez

Idiomatic Kotlin, in Swift

In this post I'm gonna explore the talk Idiomatic Kotlin by Dimitry Jemerov. I'm gonna do it by showing how each of the topics the author talks about could be done in Swift.

This post doesn't pretend to be a full featured compression between Kotlin and Swift. Also, my intention is not to criticise any of the languages but just present how things can be done in Swift 4. Both are great modern language with a lot of similarities so it's always interesting which different decisions have been taken during their evolution.

This all comes from my own curiosity of seeing Kotlin progress what the Kotlin community considers idiomatic. Specially since both communities had to unlearn a lot of baggage that came from their predecesor languages.

At the time of writing this there is no video available of the talk, luckily for me the slides are quite self-explanatory. But there is a chance that I'm understanding the purpose of some slide in the wrong way, apologies in advance.

Let's begin!

Expressions

This part will show probably one of the biggest differences in the language design. The eternal "statements vs. expressions" discussion.

Use 'when' as expression body

Great start because as far as I know there is no functionality similar in Swift.

Obviously you can use a switch instead of a when:

func parseNum(number: String) -> Int? {
    switch number {
    case "one":
        return 1
    case "two":
        return 2
    default:
        return nil
    }
}

That's fine, but the interesting part is the usage as an expression body. This comes from the Kotlin feature:

When a function returns a single expression, the curly braces can be omitted and the body is specified after a = symbol.

One advadge of that is that Kotlin infers the return type. You can get closer to that in Swift by declaring a local closure instead of a function.

let parseNum = { (number: String) in
    return 1
}

Swift can infer return types in simple cases like the above one, where there is only a single possible return type. But when the body is more complex and different branches return different types the compiler doesn't pick by default a "common parent type".

  error: unable to infer complex closure return type; add explicit type to disambiguate
 let parseNum = { (number: String) in
  switch number {
   case "one":
          return 1
      case "two":
    return 2
   default:
    return nil
  }
 }

Use 'try' as expression body

We have the same situation but instead of a switch now we want to use a try.

But let's keep going to understand more about this limitation.

Use 'try' as expression

Exploring this case explains why the previous ones don't work in Swift.

My assumption is that this boils down to these statements being also expressions in Kotlin. This means that the "When a function returns a single expression" feature can work with when and try precicely because they are a single expression.

In Swift they are only statements. This (lack of) duality is one of the big topics that every language has to answer.

Nonetheless, let's take a look at what we can do.

First we need to assume we have a Int.init(String) that throws because the standard Swift API returns an Optional, which is what we want to acomplish in this example so let's just pretend we don't have it.

  func tryParse(number: String) -> Int? {
 error: consecutive statements on a line must be separated by ';'
  let n = do {
   return Int(throwingFromString: number)
  } catch {
   return nil
  }
  print(n)
  return n
 }

As you can see the compiler doesn't like this. It's do is an statement not an expression so the parsing fails.

But you can use a closure that is inmediatly called to simulate it.

  func tryParse(number: String) throws -> Int? {
  let n: Int? = {
   do {
    return try Int(throwingFromString: number)
   } catch {
    return nil
   }
  }()
  print(n)
  return n
 }

Even using this "workaround" the type of the variable needs to be declared as the body is complex enough that the compiler can't infer the return type.

Use elvis with the return and throw

This one is interesting because bubbling up the errors is the default behaviour in the language so it's even nicer by default in Swift.

This will just make the current method throw with the same error.

  func process(number: String) throws {
  let n = try Int(throwingFromString: number)

But if you want to throw a custom error that gives the proper context to the calling site, which is often recommended as the "upper" layers don't have to be aware of implementation details, it gets cumbersome.

  error: expected expression after operator
 let a = try? Int(throwingFromString: number) ?? throw IntParsingError.parseFailure

As an aside, I really like how Rust allows you to compose and transform the errors so things like this can be done in a simple way.

And the last case, the return is not supported, again, because is not an expression.

  // error: expected expression after operator
 let m = try? Int(throwingFromString: number) ?? return

Of course all this cases can simple be rewritten as if let or guard let which is the idiomatic Swift way of doing it.

Use range checks instead of comparison pairs

This one just works in Swift with a slightly different syntax.

   ("A"..."Z").contains(c)

In Swift ranges are just types so you just need to create a range between the 2 Characters and use the contains method.

Classes and Functions

Don't create classes just to put functions in

Swift supports free functions so this can be easily accomplished. Still, with a lack of better namespacing tools, people usually creates types with static methods in them.

We usually also appreciate having the methods in the relevant types (good for autocomplete in IDEs) so we usually use extensions to retrofit functionality. This is preferable than polluting the global namespace with unrelated functions.

  extension String {
  var isPhoneNumber: Bool {
  return count == 7 // ...
  }
 }

Use extension functions copiously and avoid using member extension functions

Objective-C developers have been using extensions (categories) for years, so using them in Swift feels really natural. They are even more powerful with the great generic type system so they are even better than ever!

The interesting thing is that this is one of the thigns that Java programmers tend to not like that much, and I was really surprised by this! The first reaction they have is that classes are gonna become bloated with methods and, of course, that will break their beloved mantra that everything should be small and do one simple thing, etc. I didn't even think about this until the Java developers raised it to me while exploring Kotlin.

In practice, from my experience, this has never become a problem in Objective-C/Swift. People doesn't seem to overuse it. Just use it when it makes sense for the original type to have the desired functionality. No need for more mantras about this.

Don't use member extensions with containing class as the receiver

Swift syntax doesn't allow for this so it's not a real problem.

Consider extracting non-essential API of classes into extensions

This is something that the Swift community has fully embraced, thanks to being able to delcare different types in the same file and the convinience of extensions. We tend to split a type functionality, specially around protocol conformance, in different extensions declared in the same file. (don't mention private vs. fileprivate!)

Use default parameter values instead of overloads wherever possible

This is also something that the Swift comunity has fully embraced as one of the great features of the language. The standard library uses this feautere a lot and so it does the rest of the Swift user code.

  func hello(salute: String = "Hello", name: String) {
  print("\(salute) \(name)!")
 }
 hello(name: "Alex")
 hello(salute: "Hola", name: "Alex")

Use lateinit for properties that can't be initialised in a constructor

Swift offers Implicitly Unwrapped Optionals (IUO) for this. Annotating them at the declaration site (with a !) you can use the type as if was non-optional but you can skip the required assignment at initialization time. This is usually discouraged but it's a useful future for when is absolutly needed, as in the example or as with our beloved Storyboards.

     struct State {
        let data: String
    }
    class Test {
        var state: State!

        func setup() {
            state = State(data: "")
        }
    }

Swift also provides lazy stored properties whcih defer their initialization until the first use. This is useful when you have properties that take a non-trivial ammount of time to initialize and they are not used always.

     class DataManager {
        // DataImporter takes a nontrivial amount of time to initialize
        lazy var importer = DataImporter()
    }

Hopefully the property behaviours discussion would continue some day so we can have this kind of property extensions at the library level.

Use type aliases for functional types and collections

Not much to add here. Swift offers the same functionality even with the same syntax. It is really encouraged to use it for any complex generic type and protocols, not only for functional types and collections.

  typealias Addressbook = [Person]
 typealias Observer = (A) -> ()

Use data classes to return multiple values

In Swift this can be done using tuples (with or without names), if it's a one off return type that is only used in one function, or you can use structs if it makes sense to have a full type to define that return value.

     func namedNum() -> (Int, String) {
        return (1, "one")
    }
    namedNum().0

Tuples with parameter names look the same at usage side as a full declared type, so they are really useful for quick return types.

  func namedNum2() -> (number: Int, name: String) {
  return (1, "one")
 }
 namedNum2().number

But Swift types, specially structs, are really easiy to define and don't need boilerplate for the simple cases like this "data class". So the cost of writing a type to combine values is not really that big.

     struct NamedNumber {
        let number: Int
        let name: String
    }
    func namedNum3() -> NamedNumber {
     return NamedNumber(number: 1, name: "one")
    }
    namedNum3().number

Use destructuring in loops

Same recommendation in Swift ;)

  for n in ["a", "b"] {}
 for (i, n) in ["a", "b"].enumerated() {}
 for (key, value) in ["a": 1, "b": 2] {}

Use destructuring with lists

Sadly Swift doesn't support destructuring in list by default so the usual (head, tail) functional pattern can't be done. Extensions can be used to help with this but is not something that you usually see.

  // let (head, tail) = "test.txt".split(separator: ".")
 let head = "test.txt".split(separator: ".").first
 let tail = "test.txt".split(separator: ".").dropFirst()

Use copy method for data classes

This reminds me of NSCopying in Objective-C. In Swift, if what you want is a value type, copy is the default behaviour. And thanks to the mutability behaviour in Swift you can pick what is appropiate for the each type. This is one of the most important aspects of the language.

The Standard Library

An important thing to consider here is how the Swift Standard Library goal is defined.

The Swift standard library encompasses a number of data types, protocols and functions, including fundamental data types (e.g., Int, Double), collections (e.g., Array, Dictionary) along with the protocols that describe them and algorithms that operate on them, characters and strings, and low-level primitives (e.g., UnsafeMutablePointer).

It tries to be minimal in purpose, an easy way of seeing it is that the Standard Library has the basic functionality to interoperate with the languate runtime and make the language itself useful. Anything that is not necessary is left out for the other Swift Core Libraries (Foundation, GCD...) and, ultimately, to the rest of the community with the Swift Package Manager.

With this in mind is easier to understand why some of the specific methods provided in other standard libraries are not provided in this one.

Use coerceIn to ensure a number is in range

A range version exist.

  (5..<100).clamped(to: 1..<10)

But a clamp function for numbers is not in the standard library yet. SE-0177 was returned for revision. For now we need to add this functionality with our own extensions for now.

     extension Comparable {
        func clamped(to range: ClosedRange<Self>) -> Self {
            if self > range.upperBound {
                return range.upperBound
            } else if self < range.lowerBound {
                return range.lowerBound
            } else {
                return self
            }
        }
    }
    100.clamped(to: 0...50)

Use apply for object initialization

This is not part of the standard library. There was a discussion started by Erica Sadun about "setup closures" a long time ago but it didn't move forward.

Use filterIsInstance to filter a list by object type

   ![](filterisinstance.png)

You rarely see Swift code that uses Any so this use case doesn't need to be solved by a specific method in the standard library.

In any case, if needed, it can be simply done with a filter.

  let list = Array<Any>(["a", 2, "b"])
 list.filter({ $0 is String })

If your codebase actually needs this you can simply provide an extension, and thanks to the constraints in the extension this will only be available when it makes sense.

  extension Array where Element: Any {
  func filterIsInstance<Type>() -> [Type] {
   return flatMap({ $0 as? Type })
  }
 }
 let strings: [String] = list.filterIsInstance()

Use mapNotNull to apply a function and select items for a which it returns a non-null value

Our flatMap friend is here!

This is a hot topic in Swift. I wrote about it a while ago in The unexpected but convenience case of flatMap in Swift.

We have this weird incarnation of flatMap that gives us this behaviour, and it works fine and is really useful! You should use it when appropriate ;) (in fact I used it in the filterIsInstance implementation)

The issue is that people without a functional background doesn't really understand straightaway "map vs. flatMap" and having this flatMap with a weird behaviour doesn't help. That's why recent discussions in swift-evolution are talking about renaming it. SE-0187 has been accepted but the new name has not bee approved yet. Funny enough mapNotNull has been proposed as one of the alternatives.

  ["1", "2", "a"].flatMap(Int.init) // [1, 2]

Use compareBy for multi-step comparisons

The standard library doesn't provide this specific functiionality. In practice this doesn't happen that often as your types will conform to Comparable and for one-offs you can write a closure with the comparator.

But if you really want this feature it can be easily extended and made fully type safe in Swift with KeyPath's, generics and extensions. I recommend you to read Sort descriptors in Swift by Chris Eidhof.

Use groupBy to group items in a collection

This is one function that I missed a while ago and even wrote about it in Group by in Swift 2.0

In Swift 4.0 Dictionary has an init method that can perform the grouping.

  struct Person {
  let name: String
  let priority: Int
 }
 let people = [
  Person(name: "Alex", priority: 1),
  Person(name: "Anna", priority: 1),
  ...
 ]
 let dict = Dictionary<String, [Person]>.init(grouping: people) {
  $0.name.first.map(String.init) ?? ""
 }

Use String methods for string parsing

The API in Swift is rich enough do to a lot of parsing functionality, you can use split to mimic the example. It doesn't have the specific methods but in any case I won't recommend using that substring parsing for paths, better to just import Foundation and use the URL type.

Conclusion

As you can see both languages are very similar. In this post I mainly highlighted missing features from Swift simply because the functionality analyzed is from Kotlin, and not the other way around.

That said, Swift is still a young language and has a lot of things ahead of it. Hopefully the community can come closer together and fill the gaps that the great Core Team doesn't have time to full-fill.

If you liked this article please consider supporting me