Implicit conversions between type aliases

How it comes that Kotlin allows implicit conversions between type aliases. For example:

typealias A1 = (Int) -> Int
typealias A2 = (Int) -> Int

fun main(args: Array<String>) {
    val f1: A1 = { it }
    val f2: A2 = f1
}

compiles without a warning, but from programmer point of view A1 and A2 are distinct types. On the other hand I cannot assign Int to Long variable:

 val i1: Int = 1
 val i2: Long = i1 // error

I appreciate Kotlin type safety and I would like it to also extend on type aliases. I guess current behavior is inconsistent with the rest of the language. Am I wrong?

Aliases are just aliases, they’re the same type so they’re assignable. Int and Long are different types, so they need a conversion.

2 Likes

This implicit conversion becomes very useful when it comes to anonymous (lambda) functions.

But I agree with the original author that the problem exits: why we have type safety when we cast interfaces/classes but not for functions? May be there should be/is additional syntax I’m missing?

Here is an example that shows that type safety is important for classes/interfaces in Kotlin even for «compatible» types:

class C1(x: Int)
class C2(x: Int)
fun fooC1(): C1? = null
fun fooC2(): C2? = null

object O1 {val x: Int = 1}
object O2 {val x: Int = 2}
fun fooO1(): O1? = null
fun fooO2(): O2? = null

interface I1
interface I2
fun fooI1(): I1? = null
fun fooI2(): I2? = null

fun x() {
    val c1: C1? = fooC1()
    val c2: C2? = fooC1() // Type error!

    val o1: O1? = fooO1()
    val o2: O2? = fooO1() // Type error!

    val i1: I1? = fooI1()
    val i2: I2? = fooI1() // Type error!
}

Is there any way to reach the same level of type safety for a top level function declaration?

There is the same level of type safety. It’s just that (Int) → Int is the same type as (Int) → Int so obviously it’s assignable. In your example code you don’t use type aliases. If you’d have used them, they would’ve been assignable again.

You’re missing the problem. What you don’t like is the behavior of type aliases, not the behavior of function declarations.

This is not true. I like behavior of type aliases in they current form. The goal of my post was to emphasize that there is a disbalance in the language how strong types are supported for classes/objects/interfaces and functions. First have more fine grained type support.

I do not know how to solve this problem (introduce funtype :slight_smile: ?) but for a lot of people when they start to use typealiases for functions it becomes a valid question: what is the way to make function conversion explicit? So getOranges will not be occasionally used in the place where getApples is expected.

Int is the same as Int and typealiasing Int to Money and to Weight won’t prevent you from assigning Money to Weight.

(Int) → Int is the same as (Int) → Int and typealiasing it to A1 and to A2 won’t prevent you from assigning A1 to A2.

There is absolutely no difference here. It looks like you either want to subclass function types or have something like SAM classes supported.

You should vote for SAM support in Kotlin: https://youtrack.jetbrains.com/issue/KT-7770 SAM is exactly the “funtype” you are looking for.

1 Like

The traditional name for the feature you expected typealias to be is a “typedef”. An “alias”, or in this case a “typealias”, works just as the English word does – that is, to the compiler the typealias will actually be the same thing simply called by another name (like a nickname).

(Any issue tracker for a typedef feature open?)

(PS. Actually I’m lying a bit above
 Unfortunately the Kotlin compiler doesn’t always support typealiases properly, meaning it isn’t exactly the same thing as writing out the aliased type :frowning: )

Issue tracker or KEEP or official statement/plan

Although less general than ‘typedefs’, it was mentioned in the recent survey of possible future features that ‘inline or value classes’ could be used to represent units of measurement, custom strings or dates and even unsigned integers (see item 9 in this list).

As such, they would be distinct from their underlying types without the need for a wrapper object and could have their own properties or methods.

Moreover, as long as this was limited to a single immutable data field, it could be implemented without waiting for value types to be added to Java which is probably several years down the track, if indeed they are ever added at all.

It’s a feature which I personally would love to see as we could do quite a lot with it and was the fifth most popular (out of 20) with only a few people against.

Its prospects look good because Kotlin Native needs unsigned types for a good C interop story and I doubt whether JB would want to restrict new language features to a single target.

I’m curious as to why you think that inline classes are less general than typedefs? I was under impression that reverse is true (that is, inline classes are strictly more powerful than typedefs).

I meant less general in scope.

Some languages (Nim for example) allow you to create distinct types from any other base type no matter how many fields it has.

If I’ve understood it correctly, under the ‘inline or value class’ proposal you’ll only be able to create a distinct type with a single immutable data field though you’ll then be able to split this up into smaller types and expose those as properties.

Incidentally, although I think that when @clubin referred to ‘typedefs’ he was talking about distinct types, in the only languages I know (C/C++) where that word is actually used it is, of course, a misnomer. Apart from some minor differences, a typedef is just a type alias (not a type definition) and so, yes, the proposal is more powerful than that.

Disclaimer: we have not committed yet to add any kind of inline classes to Kotlin, so everything I write below is purely hypothetical.

However, Kotlin’s inline clases are designed provide exactly the same features that Nim’s distinct type provides. Nim’s definition of

type
  Dollar = distinct int

is semantically equivalent to Kotlin’s:

inline class Dollar(val amount: Int)

In both cases there is no limit on what other type (beyond Int) can be used as a base on which this new type is defined.

P.S. In projects where performance is not of a paramount importance you can already get the functionality of distinct type in Kotlin just by writing as in the example above, but without inline modifier.

2 Likes

@Elizarov, thanks for clarifying how the inline class proposal is intended to work.

I won’t get too excited in case it isn’t implemented but I’m hopeful :slight_smile:

Ah, but in the wondrous language I’m currently involved with that I won’t mention in this forum at the moment, the typedefs are appropriately strong, indeed creating type-system differentiable types
 :star_struck:

Thanks for the reminder about C++'s particularly weak typedef implementation, though, as it seems that I’ve been [mostly] uninvolved with C++ long enough that I’m finally starting to forget some of the smaller details of exactly how it handled some things


Hypothetically speaking, of course, how would an inline class be represented to the Java side? I would like to write something like this in Kotlin:

inline class Millis(val amount: Long)

@WorkerThread
fun isTimeToEat(db: DatabaseHandle, nowMs: Millis): Boolean {
    

    val someLongAsSeconds = db.getMeSomeRawLongsPlease()
    // Can't write this due to 'distinct' types!
    // return nowMs > someLongAsSeconds
    return Long(nowMs) / 1_000 > someLongAsSeconds
}

Would the Java side see a new Millis type or just the plain old Long (or long)? I’m very interested in this feature, since another useful thing I could do is mark the longs coming from the database as distinct primary key values to avoid mixing them.

Feature 9

https://drive.google.com/file/d/0BwAovUlww0CmVmNQTXd4TTdKYUU/view

Maybe some approach making use of delegates can get something accomplished that is close to what typedefs are, e.g. methods have different signatures and therefore require parameters of different types, but inside those methods the declared different types in the signature do not matter as delegates just forward calls to the “original”.