Let's say I would have this function:
enum HttpMethod: String { case get, post, put, patch, delete }
func makeRequest(
method: HttpMethod,
baseURL: URL,
pathSegments: [CustomStringConvertible] = [],
headers: [String:String] = [:],
) -> Response {} // body is not important
What I would like, if it were possible, is to have an expression macro called #curry that does something like this:
// Every underscore signifies that that parameter
// should still be fillable in the newly generated function
let makeRequestToMyApi = #curry(makeRequest(
method: _,
baseURL: "https://example.com/api",
pathSegments: _,
headers: _,
))
let me = makeRequestToMyApi(
method: .get,
pathSegments: ["users", "me"],
headers: ["Authorization": "Bearer blabla"],
)
And if a parameter that has a default value defined in the function definition (e.g. headers), is not given an underscore during the currying, then it should be assumed that that parameter should just be filled with its default parameter. And therefor no longer available to get filled later. Like so, for example:
let makeRequestToMyApi = #curry(makeRequest(
method: _,
baseURL: "https://example.com/api",
pathSegments: _,
// notice that headers is missing here,
// since it was not marked with an _,
// it will be assigned its default value
))
let me = makeRequestToMyApi(
method: .get,
pathSegments: ["users", "me"],
// This would cause an error, because headers is not available to fill,
// because headers was already assigned its default value of [:]
headers: ["Authorization": "Bearer blabla"],
)
Is it possible to write an expression macro that does exactly this?
It's not possible to make a macro for this because it's not possible to write it by hand. Consider a simpler function:
I want to partially apply
x, leaving a function(y: String) -> String:With this, I could call:
But that's not valid Swift. It'll give two errors:
And then:
Swift does not support argument labels in function types.
So there is no kind of expression macro that can build this. There's nothing it can expand to.
I've been thinking a lot about how this might be written as a declaration macro, but even if it is possible (I'm still exploring), it's not going to be in the spirit of your example. It might be possible to have a macro that works like this:
And that might expand to, in the best of possible worlds:
But even if that's possible, you'll notice that it declares a function. That means it has to be done at compile time, so
"https://example.com/api"has to be a string literal in your source code.I don't think anything more dynamic than that can exist. Again, not because of macros but because of the syntax of closures. You just can't have named parameters in them, which blows up your goal.
It might be possible to construct an expression macro that returns a closure that takes a tuple of the rest of the parameters, but that would be pretty bad for Swift. And in your specific example would break the default parameters. It might be interesting to try to build (and I'm continuing to mess around with it), but I can't see how it would be useful.
After some more messing around, I'm even more convinced this is impossible, and that the "why" is important.
Consider this code:
Now try to get to the following macro syntax to generate
fx:A lot of this is surprisingly possible. I've gotten this far (code at the end):
I'm sure with a few more minutes, I could get the
y:in the signature. That's not hard.But then we come to the complete roadblock. The
Stringtype foryand the return type-> String. These aren't expressed anywhere in the code calling the macro, so they're not possible to generate.At macro expansion time, there's no context about other functions that exist. So there's no way to know the type of
f. There's no way to interrogate its signature. It may not even be known at this point. It's all just tokens. I don't think it's possible to get much further, and I don't see that changing since this is a pretty big part of how Swift macros work.Anyway, here's the code that gets this far. It is terrible code. I was just working out if it's possible, not writing code you should emulate.
That said, this is just the extent of what is possible with a freestanding macro. I believe this is solvable with an attached macro, but it's even more useless for this purpose. You have to declare it as part of the function's definition. For example:
I believe this (or something like it) is possible, but it's definitely not what you want.