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

Improve usability of context dictionaries passed to Grammar.Generate #40

Open
maetl opened this issue Nov 27, 2022 · 1 comment
Open

Comments

@maetl
Copy link
Contributor

maetl commented Nov 27, 2022

This is something that came up from working through the documentation guides, converting existing Ruby and JS examples to C#.

The dynamic context feature, where map structures passed to generate get converted to temp rules in the registry at runtime is very much an API feature that emerged from dynamic language thinking and template engine APIs. It’s an important feature to have because it opens up a whole lot of useful patterns that would not be possible with purely static grammars, but translating it to C# well is not as obvious/direct as just flinging around a bunch of untyped data structures as certain other languages encourage.

Example of one of the documentation examples translated to a C# test:

using User = System.Collections.Generic.Dictionary<string, string>;

// etc...

    [Test]
    public void AppWelcomeMessageExample()
    {
      Grammar greeting = new Grammar(G => {
        G.Start(new[] { "Hi {username}", "Welcome back {username}", "Hola {username}" });
      });

      User user = new User {
        { "name", "Erika" }
      };

      Result result = greeting.Generate(new Dictionary<string, string[]> {
        { "username", new[] { user["name"] }}
      });

      Assert.That(result.Text, Does.EndWith("Erika"));
    }

// etc...

A couple of things here which make the API more complicated to use:

  • Dynamic context must specify uniform branches explicitly with string[]—there is no shortcut for a uniform branch with one entry to be input as a bare string, as supported by the static rule API. This is available implicitly in the dynamic language ports, and its absence could be confusing here if people are moving from JavaScript to C#.
  • Weighted probability branches are unsupported. This is understandable from the perspective of keeping the implementation simple and it is probably an edge case here. But it is available implicitly in the dynamic language ports.

An example from the JS port that doesn’t currently work in C#:

import calyx from "calyx"

const manu = calyx.grammar({
  backyard: ["house sparrow", "blackbird", "starling"],
  coastal: ["black-backed gull", "red-billed gull"],
  forest: ["tūī", "korimako", "pīwakawaka"]
})

const result = manu.generate({
  start: {
    "{backyard}": 3,
    "{coastal}": 2,
    "{forest}": 1
  }
})

console.log(result.text)

To improve the usability here, perhaps we could provide a builtin Context type which does a little bit of polymorphism housekeeping and internal wrangling on the key types to support simpler injection of dynamic state, rather than the brute force hammer solution of something like Dictionary<string, object> which potentially opens up a whole new area of bugs and mistakes.

@maetl
Copy link
Contributor Author

maetl commented Nov 27, 2022

A potential implementation concept using the dynamic type from .Net 4. The polymorphism currently built-in to the Grammar.Rule method does a lot of the heavy-lifting here. Not without its problems, and still needs to be tested/verified in Unity, but is at least a starting point for thinking about the direction for this feature.

using NUnit.Framework;
using Calyx;
using Context = System.Collections.Generic.Dictionary<string, dynamic>;

public class DynamicContext
{
  public Grammar BuildGrammar(Context context)
  {
    Grammar objGrammar = new Grammar(seed: 1234);

    foreach(var contextRule in context) {
      objGrammar.Rule(contextRule.Key, contextRule.Value);
    }

    return objGrammar;
  }
}

namespace Calyx.Test.Experimental
{
  public class DynamicContextTest
  {
    [Test]
    public void SupportsPolymorphicBranchTypesInContext()
    {
      DynamicContext experiment = new DynamicContext();
      Grammar grammar = experiment.BuildGrammar(new Context {
        { "fixedBranch", "one" },
        { "uniformBranch", new[] { "one", "two", "three" } },
        { "weightedBranch", new Dictionary<string, int> {
            { "one", 4 },
            { "two", 2 },
            { "three", 1 }
          }
        }
      });

      Result fixedResult = grammar.Generate("fixedBranch");
      Result uniformResult = grammar.Generate("uniformBranch");
      Result weightedResult = grammar.Generate("weightedBranch");

      Assert.That(fixedResult.Text, Is.EqualTo("one"));
      Assert.That(uniformResult.Text, Is.EqualTo("three"));
      Assert.That(weightedResult.Text, Is.EqualTo("two"));
    }
  }
}

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

1 participant