Feature Request: Will Kotlin support C++ macro? (#define)


#1

Hello everyone,

in my job, suddenly I find the need for C++ like macro. The case is that whenever I create a command A, I always want to put a command B after it. For example, if I want to send a command like CreateUser(id = randomId(), name = “HELL”) then I want to send a command right after it like DisableNameDuplicateCheckingForUser(id = the previous id generated from randomId()), but in that case I need to save the randomId() somewhere and use it again, which is not good. I can create a function wrapper, but the truth is the parameter for CreateUser is super long (more than 10 parameters. each with a long name), so it is not good.

If possible, I would like to have something like

macro CreateUserWrapper(id: String , PARAMS) {
CreateUser(id, PARAMS_EXPANSION)
DisableNameDuplicateCheckingForUser(id)
}

What do you think?


#2

I personally don’t like macros. In my experience they are used for 2 reasons. First to get around badly designed code and the second is getting around problems of C/C++. The reason why they are used so much in C is that thx to the language you can not do some stuff without it (or copy pasting a lot of code). Why don’t you just use

fun CreateUserWrapper(id: String, params: ...) : User{
    val user = CreateUser(id, params)
    DisableNameDuplicateCheckingForUser(id)
    return user // I guess you want to return this
}

You need to pass the id to the wrapper so I don’t get the advantage of a macro? What do you gain? If you want the function inlined just define it as inline. Or do I miss something obvious here?


#3

There is a trick to get almost macro behaviour, and it is through private inline functions taking lambda parameters. You put all the non-shared code in the lambda and the rest in the inline function. You then expose the variations you want given the correct types.


#4

There are (vague) plans to add tool friendly meta programming to Kotlin. See this presentation by @abreslav.


#5

Oh no, only not that. In my opinion, C-like Macros are a terrible legacy of procedural era. @Wasabi375 clearly showed that they could be easily replaced by global or local function. If you need macros in kotlin, it is a good indication that there is something wrong with your code.

Luckily, JetBrains team clearly stated that thy are not going to support macros because it is very hard (if not impossible) to provide good tooling for them. Meta-programming is on its way, but your example does not need it.


#6

C macro - is absolutely infernal evil. Every one macro complicate code exponentially.


#7

Yes, what I want is to create something like that the syntax “params: …”, since there maybe a lot of parameters and I do not want to rewrite all of them. Is there any way to achieve that?


#8

I don’t really know what you are trying to do? Could you give an entire example of what you need and how you would like to use it? Maybe than we can think of a “better” way.


#9

Something like this, I have a function with 10 parameters:

fun f (p1: String, p2: String, p3: String, p4: String, p5: String, p6: String, p7: String, p8: String, p9: String, p10: String)

Now, I want to write a wrapper for it, but I only need p1 and not the rest, so I want to write wrapper g like this

fun g(p1: String, params…) {
f(p1, *params) // *params will expand params into p2 to p10
}

Do you think that there is a way to do it?


#10

I see your problem, but let me tell you, even though macros would technically be able to solve this problem, they would not be a good solution.

The problem is that it gets really hard to understand the code you write once you use macro-magic like that. There are a few option to solving this. If all of the parameters are of the same type you could use varargs and the spread operator but I would also advice against this, as the length of arguments can no longer be checked by the compiler.
My advice would be to just create a data class with all the parameters. Than you can just pass the data object instead of having to pass a lot of arguments.


#11

You can also use map to pass parameters groovy/python-style by they name. Kotlin map parameter delegates for that.


#12

I found a simple trick, declare the following code as a global scope (outside any class):

class ExtensionModifier(id: String)
val Any.cpp get() = ExtensionModifier("cpp")
infix fun <T> ExtensionModifier.macro(body: () -> T) = body()

Then, you can declare a macro with the following syntax:

fun <macroName>(<<macroParameterName>: <macroParameterClass>, ...>) = cpp macro {<body>}

Example:

fun CreateUserWrapper(id: String, vararg params: Any) = cpp macro {
    CreateUser(id, *params)
    DisableNameDuplicateCheckingForUser(id)
}

#13

I don’t know what you try to achieve with this code. This does exactly the same thing as

fun CreateUserWrapper(id: String, vararg params: Any) {
    CreateUser(id, params)
    DisableNameDuplicateCheckingForUser(id)
}

Your macro code just adds some additional classes and function calls to the generated bytecode without any advantages.
The original problem is that CreateUser is defined as something like this

fun CreateUser(id: String, arg1: A, arg2: B, ...) {}

and the wrapper only needs to know about the id. The rest is just supposed to be passed along. The reason you don’t want to use vararg for this, is that this way you no longer have compile time type checking.


#14

Ok, I misunderstood the problem. Now I can say it’s just impossible in Kotlin, because Kotlin has no preprocessor!


#15

A function/method with more than three required params is often a code smell. A method with 10+ params is definitely a code smell and might as well be named:

fun iAmACodeSmell(fixMe: String, saveYourself: String, iHaveABadFeelingAboutThis: String ...) { 
    // ... 
}

Joking aside, have you considered using the builder pattern for createUser?
Why are you wanting to pass in the params as a collection of Strings? That’s not checked at all by the compiler.
Have you considered using default arguments?

Here’s an example where a default value, null, and an empty string are used as defaults.

fun createUserWrapper(p1: String,                    // required
                p2: String = "default value", // Optional: default value
                p2: String? = null,           // Optional: null
                p3: String = "",              // Optional: Empty string
                p4: String? = null, 
                p5: String? = null, 
                p6: String? = null {

    // Set any defaults if needed. (Maybe set the nulls to some value?)
    // Call the original method
    f(p1, p2, p3, p4, p5, p6)
    // Add your extra calls anywhere in the method.
}

fun main(args: Array<String>) {
    // Call it like this
    createUser("Some P1 value")
    // Or like this
    createUserWrapper(
        p1 = "p1 value",
        p2 = "Some p2 value",
        p6 = "I'm p6"
    )
}

EDIT: Changed the example method name to createUserWrapper and added a comment where one might put extra calls like disableNameDuplicateCheckingForUser(id)


#16

Am I the only one who read the hole topic before answering? The problem is that the op wants to wrap a create function inside another function doing a few extra checks as well. Your pattern with optional arguments does not help at all as he still would need to recreate the whole parameter list.
IMO the best way to solve this is to just create an class UserBuilder which contains the fields passed to the create user function. You can than pass this one to both the wrapper and the createUser function instead. Yes you need to create an additional class and instance of it, but that way you save the long parameter list.


#17

You got me, I may have skimmed over some parts :wink:

I think your original reply would have worked fine:

OP agrees that would be great to just pass params But it would be difficult without some kind of “params expansion” since the massive number of params is tedious to write out:

What I meant to say in my post was two things:

  • If you can, get rid of the method with 10 parameters (coders should never have to call something like that).
  • If you can’t get rid of it, try the builder pattern or default arguments in the wrapper method.

To solve the problem of writing out a ridiculously long list of parameters over and over again, you could:

  • Use default arguments
  • Use a UserBuilder
  • Use a UserParams class/builder

My example with default arguments would work since you could add the additional calls within the wrapper method. Unfortunately, you’d have to write out all the parameters again but you’d also have to do that when creating a builder.