-
Notifications
You must be signed in to change notification settings - Fork 759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal - Support user defined functions in Bicep #9239
Comments
Since I promised some code pointers:
The validation checks I can think we'd need are:
|
I really like this proposal! Thoughts below:
|
I would want to export functions from one bicep file to be reused (import) in another bicep file. I also think the syntax |
It might be beneficial to declare a functions return type as well.
|
FWIW: I would prefer the Also regarding the reference of parameters and variables: If the function references them then they could be added to the parameter list automatically and patched in for all the call sites by the compiler, couldn't they? Sure more implementation work by the compiler but should these ARM restriction ever be removed then the bicep implementation can adapt without users having to change all their templates. Right now you have have to map them as parameters explicitly. If this becomes unnecessary then I suspect a bicep warning will be added that this is unnecessary and since we will want to run under "treat warnings as errors" setup this will start breaking our pipelines and then forces us to needlessly touch gazillions of templates. So the trade-off here would be an increase in implementation complexity by a handful of people vs thousands or tens-of-thousands of templates having to be change manually. My main use-case currently is using consistent naming conventions in templates where resource names are parametrized by 3-4 parameters/variables and all but one just get passed in a template parameters (the one that changes between each callsite is typically the abbreviation of the specific component name). |
I did a bit of prototyping here: https://github.com/Azure/bicep/compare/ant/exp/func - I left various Regarding declaring types: I think to support the current behavior, we would need to force users to declare the output type of a function. |
See https://github.com/Azure/bicep/blob/ant/exp/func/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep for a syntax example. Notes/limitations: * UDFs in JSON require declaring the return type, so I've modified the spec to require the user to declare the return type. * User-defined types in UDF params & outputs are unsupported. * UDFs are currently quite limited - they can't call UDFs, or access outer scoped variables. * This is currently behind an experimental feature flag ("userDefinedFunctions") Closes #447 Closes #9239 ###### Microsoft Reviewers: [Open in CodeFlow](https://portal.fabricbot.ms/api/codeflow?pullrequest=https://github.com/Azure/bicep/pull/10465)
Proposal - Support user defined functions in Bicep
Problem statement
In most scenarios I can use variables to store the output of a complex expression and use that output in several places. However, when using complex expressions inside a lambda expression where there is a dependency on a local variable, I would need to re-use the expression, not the output of the expression. This would be a great fit for user defined functions (UDF).
Background
We already have support for UDF in ARM templates. The problem with UDF in ARM in my opinion is that they are lengthy and quite complex to define. With bicep they could be a lot shorter and still give us the same functionallity. There are also a few limitations documented. I see the "limitation" that a UDF cannot access parameters or variabels outside its own scope as a positive thing since depending on external scope would make them harder to move between templates.
When would I use functions instead of a variable?
Essentially any time I want to re-use the result of an expression I use a variable, if I want to re-use the actual expression but get different output each time based on input, I need a function.
When do I need functions?
When I need to re-use an expression inside a loop or a lambda where the input will differ on each iteration, a variable won't work if I need to use one of the iterators as input.
Potential Solution
Building an implementation that would compile into ARM UDF would be fairly simple and enable reusing expressions even inside lambda expression without any changes to ARM.
This implementation could be iterated on in the future and be replaced or complemented with an implementation based on for examples lambdas, but for now, I think a solution based on ARM UDF will be more than enough.
A function is defined with the following syntax:
<keyword> <identifier> (params tuple) => <expression>
example:
func getName (myInput string, count int) => '${replace(myInput, '-', '')}-${count}'
This would generate the following ARM:
The namespace for a UDF is not exposed at all in bicep, a generic namespace is used in the compiled ARM template. I'm not sure how this would be handled on decompile, but I suggest ignoring UDF namespaces on decompile.
Example today:
In this simplified example, I'm using the same replace(split())-expression twice. It would be handy if I could define it once in a function.
What I would want to do:
The text was updated successfully, but these errors were encountered: