DEV Community

Alex Verein
Alex Verein

Posted on • Originally published at avo.app

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()
}
Enter fullscreen mode Exit fullscreen mode

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]);
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

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.

Results

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.

Top comments (0)