Kotlin vs Swift: implementing a method that accepts a limited set of polymorphic equitable types

At Avo we work on code generation based on tracking plans.
Recently we added a new interface for calling events - before we were generating a method for each event in the tracking plan, i. e. there were methods `Avo.userSubscribed(userType, subscriptionType)` and `Avo.commentAdded(authorId, mentionedUserIds)`.
With the new interface we create a type for each event, i.e. `UserSubscribed` with `userType` and `subscriptionType` as fields and `CommentAdded` with `authorId` and `mentionedUserIds` fields, and a new method called `track` that expects instances of those types, like this  `Avo.track(UserSubscribed(userType, subscriptionType))`

Why?

This change makes Avo more flexible to use in large codebases, for example you can build the `UserSubscribed` events in different parts of you app gradually and pass it around.
You can also build a simple test implementation of the new `track` interface that does not require mocking.

What are the challenges?

Firstly, we don't want users of the generated code to pass unexpected things to the track method. Only the events defined in the tracking plan are expected.
Secondly, we want the events to be meaningfully comparable.

Solution

Let's see what both languages can offer us to solve this little challenge.


1. Defining the event types.

The ways of choice to define a limited set of types are:

In Kotlin - sealed classes. Sealed classes are abstract classes and you can define their child classes only in the same file. User's won't be able to create their implementation of our sealed class, exactly as we want. This also makes the compiler know the exact set of descendants of our sealed class.

sealed class AvoEvent {
       data class UserSubscribed(val userType: String, val subscriptionType: String): AvoEvent()
       data class CommentAdded(val authorId: String, val mentionedUserIds: List<String>): AvoEvent()
}


In Swift - enums. Enums in Swift are more powerful than in most other similar languages. Each enum case can have any number of variously typed parameters, which is nice for our case. User's are not able to add cases outside of the enum.

public enum AvoEvent: Equatable {
   case userSubscribed(userType: String, subscriptionType: String);
   case commentAdded(authorId: String, mentionedUserIds: [String]);
}


Here things are quite even, since classes are a bit more flexible (more on it in the next section) and also in Kotlin you use common `class` interface for most things, while in Swift you have to use different entities - `classes` / `structs` and `enums`. On the other hand Swift code looks cleaner.

2. Making the types equitable

In Kotlin we use data classes. Every data class automatically gets `equals` method implementation without the need to write any code based on the primary constructor values.
In Swift we set our enum to conform the `Equitable` protocol. In modern Swift if all the used types of a thing we add `Equitable` protocol to are `Equitable` you don't need to write any implementation. This is similar for both languages.
Everything is great until we get a enum member with an `Any` type parameter.
The problem in Swift is that the `Any` type is not `Equitable`. And once it appears you have to implement the compare method (`==`) manually. Moreover, once you have the compare method you have to manually implement comparison of each enum case. (In Kotlin for example we can implement the equality method on a separate single child class of our sealed class, that's the advantage of sealed classes over enums I mentioned in the previous section).
In Kotlin `Any` type is equitable out of the box. Since it's has `equals` method it is designed to be.

This point is won by Kotlin by far.

3. Picking what to do based on the provided parameter

Here everything is quite simple:
In Kotlin we use `when` statement.
In Swift we use `switch` statement.
The good thing about the `switch` statements in Swift is that they are required to be exhaustive. This makes perfect sense in a statically typed languages - I want to be sure that if I add a new case to my enum and not add it to the `switch` I'm alarmed by the compiler.
Unfortunately it is not the case in Kotlin. When you use `when` as a statement, not assigning it's result to a variable or using it in some other way, which is a default way for those who come from Java and many other C-like languages, it is not required to be exhaustive. You can use `when` as expression and then it becomes `exhaustive`, but that's not enough. Here is a bit more on that topic.

In this part I give the point to Swift.

Conclusion


All in all, it is a draw in out little face off, Kotlin is a winner on the data structure design side and Swift wins in the operator design.

Latest

Stay In Touch

We send out a curated newsletter each week with industry trends, best practices in analytics, and Avo product updates.

Stay In Touch

We send out a curated newsletter each week with industry trends, best practices in analytics, and Avo product updates.

keep reading

Next up for you