Pseudotype proposal : typealias are too weak, inline classes are too verbose

I’m use to create Value Object with only 1 value just to add type & have safer code (for example having typed ID prevent from passing wrong parameter).

actual

class People(val id:PeopleId, ...) //for the example
class PeopleId(val value:String)
class ContractId(val value:String)

fun findContract(id:ContractId):Contract? = ...

val people = People(PeopleId("peopleId")) // wrapping feels too verbose... especially when heavy used
findContract(people.id) // this does not compile, that's pretty much the goal

But there’s a minor problem with the heap, wrapping is noisy and class declaration is noisy too

Proposal :

pseudotype.(or… inlinetype or whatever keyword feels better) (this is not inspired from css :smiley:)

first the example

class People(val id:PeopleId, ...)
pseudotype PeopleId = String 
pseudotype ContractId = String

fun findContract(contract:ContractId):Contract? = ...

val people = People("peopleId") //good, no noise
findContract(people.id) //this does NOT compile

explanation :

  • looks like typealias, does not affect runtime
  • like typealias a pseudotype can be assigned from/to its inline type. this means People("peopleId"), val id:PeopleId = "peopleid" or val x:String = people.id are ok. (this also means findContract(people.id as String) would compile… but c’mon!)
  • unlike typalias a pseudotype can only be assigned from/to its inline type and NOT from other pseudotype: findContract(people.id) will not compile then.

edit:

after answering to first answer I found that maybe this proposal could also be considered as an evolution of typealias:

  • we can consider this as a problem that is more about assigning a typealias to another
  • so:
    • first generating a warning when assigning a typealias to another
    • then transforming this warning to an error

this would do the trick… And it’s even better : no keyword added, still the casting (as typealias2) workaround to compile old code

Actual not working solution 1 : typealias

typealiases don’t prevent type error, they are just (guess what…) alias not answering my need (and not intended to).

class People(val id:PeopleId, ...) //for the example
typealias PeopleId = String //good, no noise
typealias ContractId = String

fun findContract(contract:ContractId):Contract? = ...

val people = People("peopleId") //good, no noise
findContract(people.id) //this compile & this is why typealias is "too weak" for this need

Incoming not working solution 2 : inline class

Today I saw inline class (I think this concept comes from scala… not sure). They seems very powerfull… But overkill for my need & does not solve the need (once again, it’s not intended to… I think).

class People(val id:PeopleId, ...) //for the example
inline class PeopleId(val value:String) //even more verbose...
inline class ContractId(val value:String)

fun findContract(id:ContractId):Contract? = ...

val people = People(PeopleId("peopleId")) // still too verbose
findContract(people.id) // inline fix the heap problem... But only it

Honnestly I’m quite surprise this need is not elegantly covered by kotlin (which is meant to be pragmatic).

What do you guys (edit: and girls :stuck_out_tongue_winking_eye: ) think ?

So if I understand you correctly, you want an inline class with an implicit cast to it’s inline type. I’m not sure this is a good idea for kotlin. First it would add a new type of class which is basically identical to an inline class, so you don’t solve any new problems.
The only thing you want to add to inline classes is implicit type conversion which goes against one of kotlins design principles.
I don’t know whether you tried using inline classes in the situation you describe. I personally use inline classes to wrap around opengl texture/shader/etc ids and it works great.
In my (so far still a bit limited) experience I only have to wrap and unwrap values at 2 points. The few generation functions and the point where I actually access the id when interacting with opgengl. The second one is not really to verbose (texture.id instead of in your proposal just texture). And the first case only ever happens at one or 2 points in my code per inline class. I just add the required constructors.

If Kotlin did not have inline classes I would argue for your proposal (although I suspect it would turn into inline classes, because kotlins design is against implicit casts), but we already have them and I don’t see why we need a second implementation of them.

1 Like

So if I understand you correctly, you want an inline class with an implicit cast to it’s inline type.

From compiler point of view yes, but no class would exist at runtime

First it would add a new type of class which is basically identical to an inline class, so you don’t solve any new problems.

the goal is not to add feature/function in the new type, just to use type safety to make the code safer, if you want to add function in your new type, then a class or an inline class is what you are looking for

The only thing you want to add to inline classes is implicit type conversion which goes against one of kotlins design principles.

it’s not a convertion, because they are the same type (exactly like typealias are not convertion). You can even consider it has a “less tolerant typealias” from a “convertion” point of view. Maybe it could even be integrated into typealias… But it would need to add warning first in next version, then transforming warning in error in another version.

I don’t know whether you tried using inline classes in the situation you describe. I personally use inline classes to wrap around opengl texture/shader/etc ids and it works great.

You probably add methods/functions to make your code bettter. So (inline) classes are what you need… but this proposal is not about this, it’s about considering a very classic use case of use of typing (value object id in ddd for example).

In my (so far still a bit limited) experience I only have to wrap and unwrap values at 2 points.
Yes… But you are not considering tests.

If Kotlin did not have inline classes I would argue for your proposal (although I suspect it would turn into inline classes, because kotlins design is against implicit casts), but we already have them and I don’t see why we need a second implementation of them.

Inline classes answer the problem of “heap pollution” , but they don’t solve the “readability” point of view.

I edit the topic and added