Skip to content
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

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

Open
HT154 opened this issue Jan 28, 2025 · 1 comment
Open

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

HT154 opened this issue Jan 28, 2025 · 1 comment

Comments

@HT154
Copy link
Contributor

HT154 commented 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.

@bioball
Copy link
Contributor

bioball commented 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}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants