14

I use typealiases in my Kotlin code a lot, but I wonder if I can enforce type-safety on them.

typealias Latitude = Double
typealias Longitude = Double

fun someFun(lat: Latitude, lon: Longitude) {...}

val lat: Latitude = 12.34
val lon: Longitude = 56.78
someFun(lon, lat) // parameters are in a wrong order, but the code compiles fine

It would be nice if I could somehow prevent implicit casting between typealiases, helping to avoid such issues.

Of course, there is a problem, that the operations on basic types would be not available for typealiases, but it can be solved with extension functions (or casts).

I don't want to use data classes, holding a single field, because it seems a bit of overkill, especially for primitive types (or maybe I am wrong and they'll be optimized out?)

So the question: can I somehow enforce type-safety for typealiases?

ekaerovets
  • 1,038
  • 10
  • 17
  • Currently only way is to create explicit type. `class Latitude : Double`. Or am I missing something here? The compiler will then enforce the typing, but you can still treat it as a Double in most places. – Mikezx6r Apr 25 '18 at 22:27
  • @Mikezx6r yes, you're missing several things: 1) You can't inherit from primitive types, 2) You can't inherit from final classes 3) This approach has runtime overhead – ekaerovets Apr 25 '18 at 22:46

4 Answers4

12

Update for Kotlin 1.3

Inline classes are already available as of Kotlin 1.3 and currently are marked as experimental. See the docs

Original answer

Unfortunately you can't avoid this currently. There is a feature in progress - inline classes (#9 in this document), which will solve the problem with the runtime overhead, while enforcing compile time type-safety. It looks quite similar to Scala's value classes, which are handy if you have a lot of data, and normal case classes will be an overhead.

Daniel Alexandrov
  • 1,249
  • 8
  • 13
  • A nice document. #4 would be a killer-feature :) Still, I don't understand why inline classes should provide the runtime overhead. Also, my problem could also be solved with smth like `strict typealias Longitude = Double` , where strict would mean that only explicit casts are allowed on the type. – ekaerovets Apr 25 '18 at 18:07
  • There isn't any runtime overhead with inline classes, because the values are never boxed/unboxed, after the compiler finishes with the type checks on the AST, the values are left with the underlying type, so in your case both values will still be Long when the VM executes the code. What I referred to as a runtime overhead are case classes in Scala, when used just as type safe wrapper of a single value (and this is the main reason for Scala's SIP-15). – Daniel Alexandrov Apr 25 '18 at 18:39
0

Unfortunately this is not possible with typealiases. The kotlin reference says:

Type aliases do not introduce new types. They are equivalent to the corresponding underlying types. When you add typealias Predicate<T> and use Predicate<Int> in your code, the Kotlin compiler always expand it to (Int) -> Boolean. Thus you can pass a variable of your type whenever a general function type is required and vice versa:

typealias Predicate<T> = (T) -> Boolean

fun foo(p: Predicate<Int>) = p(42)

fun main(args: Array<String>) {
    val f: (Int) -> Boolean = { it > 0 }
    println(foo(f)) // prints "true"

    val p: Predicate<Int> = { it > 0 }
    println(listOf(1, -2).filter(p)) // prints "[1]"
}

See kotlin type aliases.

tl;dr You have to use (data) classes

As the name typealias implies, a typealias is only a alias and not a new type. In your example Latitude and Longitude are Ints, independet from their names. To make them typesafe you have to declare a type. In theory you could inherit new types from Int. Since Int is a final class this is not possible. So its reqiuered to create a new class.

pixix4
  • 1,041
  • 1
  • 10
  • 13
  • I've read this more than once. Questions are why it's done this way and how to bypass this limitation. – ekaerovets Apr 25 '18 at 17:15
  • I added a short explanation why it's not possible. Hope it helps you :) – pixix4 Apr 25 '18 at 17:26
  • Int is not a final class, it's a primitive. Int? is a final class. – ekaerovets Apr 25 '18 at 17:28
  • In kotlin `Int` is a final class. It extands `Number` and implements `Comparable`. Its declared in `kotlin.Primitives.kt` – pixix4 Apr 25 '18 at 17:31
  • This would be a possible solution too. You could create classes that extand `Number`. But I dont now how this interoperability and compatibility of such classes are. – pixix4 Apr 25 '18 at 17:33
-1

By defining Latitude and also Longitude as aliases for Double it can be seen as transitive aliases, i.e. you defined Latitude as an alias for Longitude and vice versa. Now, all three type names can be used interchangeably:

val d: Double = 5.0
val x: Latitude = d
val y: Longitude = x

You could, as an alternative, simply use parameter names to make clear what is being passed:

fun someFun(latitude: Double, longitude: Double) {
}

fun main(args: Array<String>) {
    val lat = 12.34
    val lon = 56.78
    someFun(latitude = lon, longitude = lat)
}
s1m0nw1
  • 56,594
  • 11
  • 126
  • 171
-1

I was recently struggling with similar case. Inline classes are not the solution cause it forces me to use property wrapper.

Hopefully for me I've managed to solve my problem by inheritance delegation.

class ListWrapper(list: List<Double>): List<Double> by list

This approach allows us to operate directly on ListWrapper as on regular List. Type is strictly identified so it might be passed via the Koin dependency injection mechanism for example.

We can go even deeper:

class ListListWrapper(list: ListWrapper): ListWrapper by list

but this require us to "open" the parent class with reflection cause `@Suppress("FINAL_SUPERTYPE") does not work.

Unfortunately with primitives there is other issue, cause they somehow providing only empty private constructor and are initialized with some undocumented magic.

majkrzak
  • 877
  • 2
  • 7
  • 22
  • Don't blame me that it is out of topic, focus rather on the"smart" person who marked my question as duplicate of this one – majkrzak Jan 30 '20 at 15:25