Lambda as a top-level data type
- Dominant language
- Bicep
- Stars
- 3.6k
- Forks
- 830
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 79
Description
# Is your feature request related to a problem? Please describe.
I maintain a set of modules that export a set of types that overlap in their interfaces, both in terms of data structure and functionality.
While I can declare the type data structures in a way that lets me use "duck typing" to unify them:
```bicep
// interface.bicep
type MyInterface = {
sharedField: string
}
// a.bicep
type A = {
sharedField: string
aField: bool
}
// b.bicep
type B = {
sharedField: string
bField: object
}
```
Which allows me to pass either an `A` or a `B` into a method that expects an `Interface`. And if i'm missing a field it expects, i will get errors letting me know that i'm passing in an incompatible object.
```bicep
func interfaceCall(interface MyInterface) any => doStuff(interface.sharedField)
var a = {
sharedField: 'a'
otherField: false
}
// success
interfaceCall(a)
// build errors
interfaceCall({})
```
However, if I want `Interface` to define not just the properties, but also member methods, there's really not a way to do this today. There's no way to express that the interface expects a method to be present. Best I can do is to export the same function name in all my interface-bound modules.
```bicep
// a.bicep
type A = { /* same as above*/ }
@export()
func interfaceCall(interface MyInterface) any => // A-specific operation
// b.bicep
@export()
type B = { /* same as above*/ }
@export()
func interfaceCall(b B) any => // B-specific operation
```
But now, I have to know what import brought in what functions in order to call them
```bicep
// main.bicep
import * as A from './a.bicep'
import * as B from './b.bicep'
param MyInterface x
param B b
x.interfaceCall(x) // syntax error
A.interfaceCall(x) // maybe correct? I don't actually know what x is
B.interfaceCall(b) // correct
```
# Describe the solution you'd like
I would love to hear what the thoughts are on adding `lambdas` to the top-level data type set, since they are already present in the built-in functions like `map`.
* signature could be `lambda(paramType*) outputType`
* same limitations as `func` would be reasonable:
* no `var` references
* no resource references
Using this proposal I can express the examples above as
```bicep
// interface.bicep
@export()
type MethodType = lambda(MyInterface) any
@export()
type Type = {
sharedField: sharedFieldType
method: MethodType
}
// a.bicep
import * as Interface from './interface.bicep'
type A = {
sharedField: string
method: Interface.MethodType
aField: bool
}
func interfaceCall(a A) any => // A-specific operation
@export()
func new(o object) A => union({method: interfaceCall}, o)
// b.bicep
import * as Interface from './interface.bicep'
type B = {
sharedField: string
method: Interface.MethodType
bField: object
}
func interfaceCall(b B) any => // B-specific operation
@export()
func new(o object) B => union({method: interfaceCall}, o)
// main.bicep
import * as A from './a.bicep'
import * as B from './b.bicep'
module _ './my-module.bicep' = {
name: '_'
params:{
interface: A.new( {
sharedField: 'a'
aField: true
})
}
}
// my-module.bicep
import * as Interface from './interface.bicep'
param interface Interface
// doesn't have to know what concrete type the object is
var result = interface.method(interface)
```
Contributor guide
Assessment
This issue has not been assessed yet.