Skip to content

Make Production<T> a covariant interface. #26

@teo-tsirpanis

Description

@teo-tsirpanis

Background and motivation

It has been asked on Discord to support "upcast[ing] productions in a nonterminal", avoiding on fusers boilerplate upcasting to a common ancestor that is shared by a nonterminal containing the production.

Here is a real-world example of this boilerplate. Each production builder is finished with essentially dummy fusers1:

https://github.com/Kuinox/KuiLang/blob/bf6d6283df1b8fec0c397ab0f9f22c52acd96558/KuiLang/KuiLang.cs#L180-L183

This has been recognized as a real usability issue of Farkle, and can be solved pretty easily.

API Proposal

namespace Farkle.Builder;

- public sealed class Production<T> {}
+ public interface Production<out T> {}

Production<T> will continue having no members, reflecting the builder's philosophy that its objects are black boxes. The I prefix will not be added to signify that implementing this interface from user code will not make sense; just like with designtime Farkles. 2

API Usage

With the proposed change the code snippet above could be simplified to this (methodDeclaration and fieldDeclaration return different descendants of Statement.Definition):

var definition = Nonterminal.Create<Statement.Definition>("Method or Field Declaration List",
    methodDeclaration.AsIs(),
    fieldDeclaration.AsIs()
);

Much terser and more intuitive (and a tiny bit more performant because AsIs is used). A dummy prototype of this API shape compiles as expected.

Alternative Designs

We could go further and implement Production<T> on DesigntimeFarkle<T>, making the example above to:

var definition = Nonterminal.Create<Statement.Definition>("Method or Field Declaration List",
    methodDeclaration,
    fieldDeclaration
);

It would save even more characters, but it's not orthogonal to the distinction between designtime Farkles and productions; it's just a syntax convenience that needs a strong justification to puncture the architecture. And anyone interested can create a method that emulates the call above.

Risks

  • It's a breaking change.
    • We don't care, a new major version is underway, everything is subject to change, and this is not source-breaking.
  • Users might implement it by mistake.
    • It will be documented that implementing it from user code is not allowed, just like designtime Farkles.

Footnotes

  1. The fuser was subsequently eliminated by calling AsIs with an explicit type parameter (that was smart!) but only works on the AsIs extension method of designtime Farkles; not the AsIs instance method of production builders. Even then, some boilerplate on each production remains.

  2. The Farkle.PostProcessor<T> interface is an exception to this rule and makes sense to implement it from user code. It has been renamed to IPostProcessor<T> on Farkle 7.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions