Reference<T>: Type safe "deferred" references #269

Open
opened 2025-12-30 01:23:00 +01:00 by adam · 1 comment
Owner

Originally created by @HT154 on GitHub (Jan 28, 2025).

One common pattern in infrastructure-as-code systems like Terraform and Pulumi is "references". A reference provides a way to refer to a resource (or a property thereof) that exists at a time after Pkl evaluation completes.

Example: The pulumi aws-yaml-static-website example creates a aws:s3:BucketV2 and creates a aws:s3:BucketPolicy that both directly references the bucket and a property of the bucket (its arn). In the case of the Pulumi YAML runtime, references are rendered as String sequences beginning with ${ and ending with }.

It would be useful for a Pkl module representing the aws:s3:BucketPolicy resource to define its bucket like so:

bucket: String | // accept a bucket name directly
  Reference<String> | // accept a reference to a string, possibly from another resource or function's output
  Reference<Resource> // accept a reference to a aws:s3:BucketV2 or another resource, using its ID (a String)

There should be an API within the standard library for representing and producing such references. How a reference is rendered or otherwise consumed would be module-dependent. At minimum, the Reference API should look something like this:

// members must be functions to avoid ambiguities between reading reference info and construction property "sub-references"
class Reference<T> {
  /// The Class that this underlying Reference points to.
  function getRootClass(): Class<T>

  /// The concrete value of the "root" of the reference.
  function getRootData(): Any

  /// The "path" of properties/keys referenced from the "root" value.
  function getPath(): List<PropertyReference|SubscriptReference>
}

class PropertyReference {
  property: String
}

class SubscriptReference {
  key: Any
}

function Reference<T>(root: T): Reference<T> = new {
  // clazz = root.getClass()
  // data = root
}

Reference should also exhibit covariance: given a class T and a subclass class U extends T, Reference<U> should be a subtype of Reference<T>.

It may also be beneficial to add syntax sugar for producing references, so these two expressions would be equivalent:

Reference(someValue).propA["key1"].propB[0]
&someValue.propA["key1"].propB[0]

This requires a SPICE to be written to iterate on the design.

Originally created by @HT154 on GitHub (Jan 28, 2025). One common pattern in infrastructure-as-code systems like Terraform and Pulumi is "references". A reference provides a way to refer to a resource (or a property thereof) that exists at a time after Pkl evaluation completes. Example: The pulumi [aws-yaml-static-website example](https://github.com/pulumi/examples/blob/master/aws-yaml-static-website/Pulumi.yaml) creates a `aws:s3:BucketV2` and creates a `aws:s3:BucketPolicy` that both directly references the bucket and a property of the bucket (its `arn`). In the case of the Pulumi YAML runtime, references are rendered as String sequences beginning with `${` and ending with `}`. It would be useful for a Pkl module representing the [`aws:s3:BucketPolicy`](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketpolicy/) resource to define its `bucket` like so: ```pkl bucket: String | // accept a bucket name directly Reference<String> | // accept a reference to a string, possibly from another resource or function's output Reference<Resource> // accept a reference to a aws:s3:BucketV2 or another resource, using its ID (a String) ``` There should be an API within the standard library for representing and producing such references. How a reference is rendered or otherwise consumed would be module-dependent. At minimum, the `Reference` API should look something like this: ```pkl // members must be functions to avoid ambiguities between reading reference info and construction property "sub-references" class Reference<T> { /// The Class that this underlying Reference points to. function getRootClass(): Class<T> /// The concrete value of the "root" of the reference. function getRootData(): Any /// The "path" of properties/keys referenced from the "root" value. function getPath(): List<PropertyReference|SubscriptReference> } class PropertyReference { property: String } class SubscriptReference { key: Any } function Reference<T>(root: T): Reference<T> = new { // clazz = root.getClass() // data = root } ``` `Reference` should also exhibit covariance: given a `class T` and a subclass `class U extends T`, `Reference<U>` should be a subtype of `Reference<T>`. It may also be beneficial to add syntax sugar for producing references, so these two expressions would be equivalent: ```pkl Reference(someValue).propA["key1"].propB[0] &someValue.propA["key1"].propB[0] ``` This requires a SPICE to be written to iterate on the design.
Author
Owner

@bioball commented on GitHub (Jan 28, 2025):

Thanks for getting the ball rolling.

The basic idea here is that a Reference<T> would have the same property accessors as T, except all accessors continue to give you a Reference.

class Person { pet: Pet }

class Pet { name: String }

hidden bob: Reference<Person> = makeRef(Person, "bob")

petName = bob.pet.name // `petName is a Reference<String>`

Users would use output converters when rendering this. For example, maybe something like:

output {
  renderer = new YamlRenderer {
    converters {
      [Reference] = (it) -> "%{" + it.getPath().join(".") + "}"
    }
  }
}

Produces:

petName: %{bob.pet.name}
@bioball commented on GitHub (Jan 28, 2025): Thanks for getting the ball rolling. The basic idea here is that a `Reference<T>` would have the same property accessors as `T`, except all accessors continue to give you a `Reference`. ```pkl class Person { pet: Pet } class Pet { name: String } hidden bob: Reference<Person> = makeRef(Person, "bob") petName = bob.pet.name // `petName is a Reference<String>` ``` Users would use output converters when rendering this. For example, maybe something like: ```pkl output { renderer = new YamlRenderer { converters { [Reference] = (it) -> "%{" + it.getPath().join(".") + "}" } } } ``` Produces: ```yaml petName: %{bob.pet.name} ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/pkl#269