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.
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?
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.
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.
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?
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.
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.
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.
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:
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)
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.
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.