# grace makes the type checker write your JSON schema

> grace is a Haskell domain-specific language for prompting models, and its unusual trick is that the type checker reasons backwards from how a prompt result is used, infers a JSON schema from that usage, and hands the schema to the model, which means a prompt can be as short as a type annotation or disappear entirely.

**Gabriella439/grace** — A prompt engineering functional programming language

- Repository: https://github.com/Gabriella439/grace
- Website: https://trygrace.dev/
- Stars: 623 · Forks: 48
- Language: Haskell
- License: BSD-3-Clause
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/gabriella439-grace

## A language for talking to models, written in Haskell

Grace, short for Fall-from-Grace, is a domain-specific programming language for prompting models, and it is well suited to building and auto-generating elaborate prompt chains. The implementation is Haskell, and the repository carries the usual Haskell project furniture, a grace.cabal file, a Setup.hs, a flake.nix, an hlint configuration and a stylish-haskell configuration, plus directories for the compiler, the prelude, the prompts and a doctest suite.

The model support is integrated rather than imported. You do not need to install any dependencies or import anything to get started, because everything you need is built directly into the language. The built-in prompt function is the entry point, and the key is a value rather than an environment lookup in the example, taken from a file path in the source.

The most important fact for a newcomer is where to start. You can use Grace in your browser without installing anything by visiting trygrace.dev, and that website includes an interactive tutorial and is the recommended way to both learn and get started. The repository reinforces that with a try-grace/ directory and a website/ directory, and the examples folder includes a learn-in-y-minutes.ffg file, so the project ships several ways in.

The name comes from a character in Planescape Torment, Fall-from-Grace, following the author's habit of naming programming language projects after characters, and the topic tags on the repository put it in the same family as prompt engineering and interpreters.

## A type annotation is an output format

The prompt function takes a key and a text, and the result is text. What makes Grace different is what you can do with that result afterwards.

Give the call a type annotation and the output is structured to match it:

```haskell
  >>> prompt{ key, text: "Generate a list of names" } : List Text
  [ "Ava Thompson"
  , "Liam Patel"
  , "Sophia Martinez"
  …
  , "Jackson Rivera"
  , "Zoe Wilson"
  , "Aiden Park"
  ]
```

The type is not decoration. It tells the interpreter what shape to constrain the model's output to, which is why the result comes back as a list of names rather than as prose with a bullet list in it.

And if the type is self-explanatory enough, the prompt text can go too:

```haskell
  >>> prompt{ key } : List { name: Text }
  [ { "name": "Alice" }
  , { "name": "Bob" }
  , { "name": "Charlie" }
  , { "name": "Diana" }
  , { "name": "Evan" }
  ]
```

That is the sharpest idea in the project. A model asked to produce a list of records with a name field will produce prose unless something constrains it, and here the constraint is the type rather than an instruction in the prompt. The prompt text becomes a fallback for cases where the type alone does not say enough.

The type in that example is a record type with one field, written as a brace with a field name and a field type, and the same notation reappears throughout the documentation, including in the generated code examples later.

## Reasoning backwards from how the value is used

The last example goes further and omits the type as well, because the type can be inferred from the use of the result:

```haskell
  >>> for { name } of prompt{ key } in "Hello, ${name}!"
  [ "Hello, Alice!"
  , "Hello, Bob!"
  , "Hello, Carol!"
  , "Hello, Dave!"
  , "Hello, Eve!"
  ]
```

The type checker reasons backwards from the usage, and the README walks through each step. The name variable must be Text because it is interpolated into the string. The prompt function must generate a List because the program loops over its output with a for and of loop. Each element of the List has the type { name: Text } because the loop destructures each element with { name }. And therefore the prompt function outputs a value of type List { name: Text }, which reads as a list of records each with a name field containing Text.

The interpreter then converts that Grace type into a JSON schema:

```json
  {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": ["name"],
      "additionalProperties": false
    }
  }
```

The last line of the chain is the one that matters. The model infers from that JSON schema alone, without any additional prompt, that it should generate a JSON-encoded list of names. So the schema is doing the work that an instruction paragraph usually does, and the instruction is what you write only when the type is not enough.

This is the design's centre of gravity, and it is a trade. In exchange for the compiler doing the schema work, the output is constrained to a shape, and a model asked for names will give you five of them rather than the forty that a plain prose request produced.

## import prompt asks for code instead of text

The same mechanism generates code. You prefix the prompt keyword with import to ask the model to generate a Grace expression of any type:

```haskell
  >>> import prompt{ key, text: "increment" }
  \n -> n + 1
```

And an explicit type annotation guides the generated code:

```haskell
  >>> import prompt{ key, text: "increment" } : { input: Natural } -> { output: Natural }
  \{ input } -> { "output": input + 1 }
```

Again, when the type is informative enough the prompt text can be omitted, and the README shows an example where a key is bound and the prompt is inlined into a larger expression.

Inferred types guide code generation as well. The README notes that the model infers the type of the upper function needs to be Text to Text and generates a function matching that type:

```haskell
  >>> let upper = import prompt{ key, text: "uppercase" } in "Hello, ${upper "gabby"}!"
  "Hello, GABBY!"
```

The one detail in the documentation worth calling out is the possibility of generated code that itself prompts a model. The README points at an example where a generated function calls prompt with a job description and returns a boolean field, and the comment on it is that the model can generate code which itself prompts a model, with the single word Neat. It is a small example of a prompt chain, which is what the project says it is designed for, since elaborate prompt chains are the stated use case.

## A command line with a REPL and a formatter

The Haskell package builds a grace executable with a small command line API:

```bash
$ grace --help
Usage: grace COMMAND
  Command-line utility for the Grace language

Available options:
  -h,--help                Show this help text

Available commands:
  interpret                Interpret a Grace file
  text                     Render a Grace text literal
  format                   Format Grace code
  builtins                 List all built-in functions and their types
  repl                     Enter a REPL for Grace
```

The interpret subcommand runs a Grace file, and the example in the README is a small greeting program that loops over prompt results:

```bash
$ grace interpret ./greet.ffg
```

You can also specify a dash to process standard input instead of a file, which is the quickest way to check an expression:

```bash
$ grace interpret - <<< '2 + 2'
```

The repl subcommand is for interactive use, and the example shows a key bound to a file path and a prompt annotated with a record type of two text fields, returning a list of records.

The other two commands are worth noticing because most languages have them and this one is young. text renders a Grace text literal, format formats Grace code, and builtins lists all built-in functions and their types, which is a documentation command that doubles as a discoverability tool for a language whose surface is still small enough to print.

## The examples folder is the tutorial, and it is unusually varied

The examples directory lists a good spread of what the language can do, and the file names describe the techniques rather than the domains. There is chaining.ffg, code.ffg, eval.ffg, history.ffg, prompt.ffg, tools.ffg, transform-text.ffg, emotion-wheel.ffg, poem.ffg, mustache.ffg, taxes.ffg and users.ffg, plus a learn-in-y-minutes.ffg and a tutorial directory, and a bluesky directory.

That list tells you the shape of the language better than a feature list would. Chaining and history suggest that prompts are values you can pass around and compose, which is the whole premise of building a prompt chain language. tools and eval suggest a runtime with effects. The domain files, the poem, the taxes, the emotion wheel, the mustache template, suggest that the intended use is generating structured artefacts rather than chatting.

The repository layout is consistent with that. There is a src/ directory for the interpreter, a prelude/ for the standard definitions, a prompts/ directory, a ghc/ and ghcjs/ directory for the two compilation targets the names suggest, a doctest/ directory so the examples in the documentation are executed, and a tasty/ directory for the test framework.

## BSD-3-Clause terms, no releases, and how this compares to the ordinary approach

The licence is BSD-3-Clause, with the LICENSE file at the repository root, and the project carries the usual open source furniture around it, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md, an .envrc for direnv, a .gitattributes and the flake.nix for a reproducible development environment. There are no GitHub releases, and the repository is not archived with the last push on 2026-08-31.

The comparison worth making is with the ordinary way of doing this, which is to write the JSON schema by hand, put it in a file, and call the API from your code, in Haskell or in anything else. That approach is shorter for a single prompt, has no new language to learn, and has no dependency on how a type checker infers a schema. It is also the approach that requires you to keep the schema and the code that consumes the output in step by hand, which is exactly the duplication Grace is built to remove.

The difference in approach is that Grace moves the schema into the type system. A function whose result is consumed as a List of records with one name field has a schema, by construction, and a change in how the value is used changes the schema. The model prompt then becomes a fallback for the cases where the type does not carry the intent, and the README's own examples show it being dropped entirely.

The cost is that the model is still the weak link. A schema constrains the shape of an answer and not its truth, and the shorter the prompt, the more the schema has to carry. A team that wants the model's freedom and a human reading the result will keep the prompt text; a team that wants a program that consumes structured data will drop it.

## Conclusion

grace fits a Haskell developer who has written the same JSON schema three times for three different prompts and wants the program to carry the schema instead of a configuration file, and it fits anyone who wants to try a language without installing anything, since trygrace.dev runs the whole thing in the browser with a tutorial. It does not fit a team that does not want a language at all, since the ordinary approach of hand-writing a schema and calling an API is shorter for a single prompt, and the type inference only pays off across a program. Before adopting it, read the prompt{ key } examples in order, since the language's semantics are carried by them, and expect the model to be the weak link, since the schema constrains the shape of the output and not its truth.

## FAQ

### What is grace and how do I try it?

Grace is a domain-specific Haskell language for prompting models, and you can use it in your browser without installing anything at trygrace.dev, where an interactive tutorial is included and is the recommended way to get started.

### How does grace decide what shape the model output should have?

The type checker reasons backwards from how the value is used, infers a Grace type such as List { name: Text }, and the interpreter converts that type into a matching JSON schema which constrains the model's output.

### Do I have to write the prompt text in grace?

Not always. If the type is sufficiently self-explanatory the prompt can be omitted, as in prompt{ key } : List { name: Text }, and if the type can be inferred from how the result is used then even the type annotation can be omitted.

### Can grace generate code instead of text?

Yes. Prefixing the prompt keyword with import asks the model for a Grace expression of any type, with an explicit type annotation able to guide the generation, and the generated code can itself contain a prompt call.

### Which commands does the grace command line tool provide?

The available commands are interpret to interpret a Grace file, text to render a Grace text literal, format to format Grace code, builtins to list all built-in functions and their types, and repl to enter a REPL, plus the -h and --help options.

## Sources

- [Gabriella439/grace on GitHub](https://github.com/Gabriella439/grace)
- [Issues](https://github.com/Gabriella439/grace/issues)
- [License: BSD-3-Clause](https://github.com/Gabriella439/grace/blob/main/LICENSE)
- [Project website](https://trygrace.dev/)
- [README](https://github.com/Gabriella439/grace/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gabriella439-grace
