Annotating Extension Functions to more controlled usage

I was recently faced with a task of converting an Int to a more readable String value. The Int that I was working with was from a library, more specifically - Google’s MLKit library. The Ints in question were Barcode formats (eg. Barcode.FORMAT_QR_CODE, Barcode.FORMAT_PDF417…)

To make life easier I made the following extension function:

fun Int.toFriendlyName(): String {
   return when (this) {
      is Barcode.FORMAT_QR_CODE -> "Qr Code"
      is Barcode.FORMAT_PDF417 -> "PDF417"
      else -> "Not supported"
   }
}

But I have a problem with this approach - who is to say that a developer won’t misuse this extension function with any Int?

...
val sillyUsage = 3.toFriendlyName()
...

If I look into the source code (from the MLKit Library) for getting the barcode format, the Java code looks like this:

@Barcode.BarcodeFormat
public int getFormat() {
    int var1 = this.zza.zzf();
    if (var1 <= 4096) {
        if (var1 == 0) {
            return -1;
        }
    } else {
        var1 = -1;
    }

    return var1;
}

Given that there is an annotation (@Barcode.BarcodeFormat) for the getFormat() method, would it not be useful to use it in the extension function to limit its usage? For example:

fun @Barcode.BarcodeFormat Int.toFriendlyName(): String {
   return when (this) {
      is Barcode.FORMAT_QR_CODE -> "Qr Code"
      is Barcode.FORMAT_PDF417 -> "PDF417"
      else -> "Not supported"
   }
}

Of course, the above demonstration might not be syntactically correct… but hopefully the idea that I am proposing still makes sense.

Ideally, this would mean that if a developer then tried

...
val sillyUsage = 3.toFriendlyName()
...

that there would be an IDE warning saying that is not a valid function call.

Looking forward to hearing your thoughts, or even if I have been silly and missed something out.

Thank you in advance :slight_smile:

I’m a bit confused. Where did you get the getFormat() function and what does it do? Same goes for the Barcode.BarcodeFormat annotation.
Where is it defined and what does it do?


Based on what I think you want to achieve kotlin already kinda has a feature that allows to restrict extension functions to more specific types. Inline classes allow to write wrappers around other types to contain more context about how to use them. This is used for unsigned types for example.
However they are currently in an experimental state and I think they are going to be replaced with value classes (I’m not 100% up to date with the current plans regarding this).

1 Like

The getFormat() function and Barcode.BarcodeFormat annotation are from the Google MLKit Library. Apologies for not making that clear, I have updated my post :slight_smile:

Basically there is a callback in the MLKit library that returns a Barcode when an image is captured - this Barcode can be queried with the getFormat() method.

I’ll need to check this out, do you have any resources for this area?

Thanks for replying :smiley:

Ok, so you have a ‘Barcode’ class with a ‘getFormat()’ function and the return value of that function is annotated with @Barcode.BarcodeFormat.
My question is, what is kotlin supposed to do with that information. What does the BarcodeFormat annotation mean and how is kotlin supposed to know about it?
It looks (to me) like you want to create a new type (using annotation). This function toFriendlyName() can only be called on Ints that are also annotated with @BarcodeFormat. I hope I understand you correctly.
The problem with that is that you want the functionallity of a type without actually creating one. It should somehow work using annotations. While maybe possible it would just duplicate functionality of the typesystem and probably not be as powerful and stable.

The idea using inline classes is something like this

inline class BarcodeFormat(val value: Int) 
fun BarcodeFormat.toFriendlyName(): String = this.value.yourIntToFrindlyNameImplementation()

Sure you have to either change the library so that getFormat returns a BarcodeFormat or you have to call the wrapper on your end BarcodeFormat(myBarcode.getFormat()) but now you can be sure that toFriendlyName is only called on a barcode format and not just a random integer.

There are lots of articles out there about inline classes. For a real documentation you have to look into it’s KEEP since it’s still an experimental feature:

Same goes for value classes, which are an adapt inline classes in a way that makes the future proof for project valhalla (a change to the jvm, which will allow something similar).

1 Like

You are an actual star, thank you so much! :star2:

I just want to add that inline classes are still experimental. The idea of using wrapper classes to better communicate the semantic of a value, however, works perfectly well with regular classes. Using an inline class is just a minor optimization, and is probably not going to make a noticeable difference anyway in most cases.

BTW, the problem you are experiencing is sometimes called Primitive Obsession. I recommend that you look it up yourself.

3 Likes