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

Proposal: Fixing generic templates over the Ledger API #3504

Closed
cocreature opened this issue Nov 18, 2019 · 8 comments
Closed

Proposal: Fixing generic templates over the Ledger API #3504

cocreature opened this issue Nov 18, 2019 · 8 comments
Labels
component/daml-engine DAML-LF Engine & Interpreter component/daml-lf DAML-LF language Language team work

Comments

@cocreature
Copy link
Contributor

cocreature commented Nov 18, 2019

Current Status

If you define a generic template in DAML, we currently create a record with the corresponding type parameters and the name of the generic templates. Once you create an instance of that generic template, we will create a new record in DAML-LF with the type parameters specialized to the ones in the instance and with the name of the instance. In DAML-LF those two records are unrelated and only the monomorphized record is a template. In DAML, the monomorphized record does not exist. We only create a type synoynym for the specialized version of the generic record.

The functions for interacting with the generic template take care of monomorphizing things as necessary, e.g., create in DAML generates a DAM-LF function that takes the generic record, converts it to the monomorphic record and then calls the DAML-LF create primitive. Similarly for exercise and other methods.

However, this falls apart once you interact with a generic template via the Ledger API. E.g., if you a choice returning a value of type type FooA = Foo A in DAML, the DAML-LF return type will be Foo A. However, that type is not a template, so if you try to pass the result of the choice to a create it will complain about a mismatch (if you omit the record id, you get lucky but imho that is more of an accident). It is even worse, if you have a choice accepting a ContractId FooA in DAML. The DAML-LF choice accepts a ContractId (Foo A) which passes the LF typechecker. However, if you try to exercise this choice, the engine will throw an error complaining that Foo A can’t possibly be a template since it has a type parameter so a contract id of that type doesn’t make sense.

Effectively, this makes generic templates pretty much unusable over the Ledger API so we need to do something.

Proposed Changes

DAML-LF

  • We separate the name of a template in its from the underlying record type. The underlying record type is allowed to have type parameters but all of them must be fixed.
  • Defining a template creates a synonym of the name of the template in DAML-LF for the specialized record type. Note that the synonym can be in a different package and module than the record type.
  • When specifying the template id in the Ledger API you must use the name given in the template definition, not the record identifier.
  • As a record during typechecking, validation in the engine, … the name given in the template is interchangeable with the record type. This includes the record id on a create.
  • Both ContractId (Foo X) and ContractId FooX are valid over the ledger API.
  • There is no unique mapping from Foo X to FooX in general as there can be two templates for the same record type specialization in different modules and/or different packages.
  • The primitives in DAML-LF, most notably create and exercise will continue to require a template id instead of the record it.

Open Questions

  • Should we also lift restrictions for non-generic templates, i.e., record types without type parameters? In particular, do we keep the restriction that template name and constructor type constructor are identical and must be defined in the same module? Personally, I think we should to keep things as simple as possible and not complicate non-generic templates.
  • How do we handle this in codegens? Currently, codegens automatically decode to the high-level ContractId type which provides exercise. Given that the mapping from Foo X to FooX is not unique, this is no longer possible. The codegen can only decode to ContractId (Foo X) and then the user has to make a choice which template they want to use which could either be done by something like constructor ContractId<FooX>(ContractId<Foo<X>>) or a static method on the FooX class with a signature like exercise(ContractId<Foo<X>>, …).

Affected Components

  • LF conversion in damlc.
  • LF typechecker in Haskell and Scala.
  • Engine validation logic.
  • Codegens.

cc @gerolf-da @remyhaemmerle-da @hurryabit @bame-da @tweber-da

@cocreature cocreature added component/daml-engine DAML-LF Engine & Interpreter component/daml-lf DAML-LF language Language team work labels Nov 18, 2019
@bame-da
Copy link
Contributor

bame-da commented Nov 18, 2019

I can't think of anything better so it gets my thumbs up.

As for the open questions:

  1. I think we should keep non-generic templates as they are now.
  2. I think we should assume that CodeGen only has one template instance per record in scope in a given run. Let it error out if it finds several.

@hurryabit
Copy link
Contributor

I fully support the plan. I agree with @bame-da on 1 in that we should keep non-generic templates as they are now. There should be an easy path into using DAML where things that are only required for using advanced features impact you negatively. Regarding 2, let's do what we just agreed on the whiteboard.

@cocreature
Copy link
Contributor Author

For the codegen our plan is:

  • Assume that we have only one template instance for a given type specialization in the DAR. Error out if that is not the case.
  • Extend all (so even the non-generic) template classes with static exerciseX and create methods that take the record as the argument.
  • For generic templates we generate one generic class for the record and a monomorphic class with only static methods for each template instance. The monomorphic class extends the corresponding generic class.
  • The methods on the monomorphic template FooInt will except something like Foo<? extends Int> and ContractId<Foo<? extends Int>>.

Given the assumption in the codegen that there is only one instance in a DAR, should we enforce in DAML-LF that there is only one instance per package (i.e. per DALF)? We already have package-wide checks for name collisions so this shouldn’t be “less” modular than we already are.

@bame-da
Copy link
Contributor

bame-da commented Nov 19, 2019

@hurryabit @cocreature do we still need

Assume that we have only one template instance for a given type specialization in the DAR. Error out if that is not the case.

The only thing that varies between template instances are the choices (though in practice even that is unlikely). The choices are now defined on a static class names like the template instance so two template instances no longer collide.

@cocreature
Copy link
Contributor Author

That’s a good point but on the other hand we have also gone away from your proposal of resolving from Record<Foo> to RecordFoo implicitly and the user has to make that choice which I’m not sure what was you intended?

@bame-da
Copy link
Contributor

bame-da commented Nov 19, 2019

True, but I've come around that that's unavoidable. The developer overhead of having to write RecordFoo.create(r) over r.create() and RecordFoo.exerciseBaz(cid, args) over cid.exerciseBaz(args) is acceptable imo.

@cocreature
Copy link
Contributor Author

Great, I completely agree with that so sounds like we’re all on the same page now 👍

@cocreature
Copy link
Contributor Author

Closing since we decided to postpone generic templates for now, see #3606

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component/daml-engine DAML-LF Engine & Interpreter component/daml-lf DAML-LF language Language team work
Projects
None yet
Development

No branches or pull requests

3 participants