Getting Started

Creating a module

To get started, let’s create a module, gleamgen’s highest level building block. Almost all gleam code you’ll generate with gleamgen should ultimately be a part of a module. Modules make heavy use of Gleam’s use syntax, so you’ll want to create one in a block or its own function. The simplest possible module consists of just one component: its end, module.eof

import gleamgen/module

pub fn main() {
  let mod = {
    module.eof()
  }
}

Adding Content

Let’s add something to our module before we convert it into text. We can create a constant with module.with_constant. When used as a use expression, it takes two parameters: its definition (where we provide its name greeting and set it to public), and its contents (which we set to the string literal "hello").

import gleamgen/expression
import gleamgen/module
import gleamgen/module/definition

pub fn main() {
  let mod = {
    use greeting <- module.with_constant(
      definition.new("greeting") |> definition.with_publicity(True),
      expression.string("hello"),
    )

    module.eof()
  }
}

We’ll later use the greeting variable on the left side of the use expression to reference this constant.

Rendering

First though, let’s actually display this text.

import gleamgen/expression
import gleamgen/module
import gleamgen/module/definition
import gleamgen/render

pub fn main() {
  let mod = {
    use greeting <- module.with_constant(
      definition.new("greeting") |> definition.with_publicity(True),
      expression.string("hello"),
    )
    module.eof()
  }

  mod
  |> module.render(render.default_context())
  |> render.to_string()
}

To do so, we must “render” the module by passing it to module.render. The other argument taken by this function is the render context. In this case, the default context is perfect, but one could also use create a context with a custom render config (see gleamgen/render).

This returns functions value of the render.Rendered type. This type allows us to view potential errors in the code generation process. However, here we’re just interested in the output, so we pipe it directly to render.to_string.

This will print out the following:

pub const greeting = "hello"

Adding a function

Let’s make this module more interesting. To define a function, you can use module.with_function.

import gleamgen/expression
import gleamgen/function
import gleamgen/module
import gleamgen/module/definition
import gleamgen/parameter
import gleamgen/render
import gleamgen/type_

pub fn main() {
  let mod = {
    use greeting <- module.with_constant(
      definition.new("greeting") |> definition.with_publicity(True),
      expression.string("hello"),
    )

    use greet_user <- module.with_function(
      definition.new("greet_user"),
      function.new1(
        param1: parameter.new("user", type_.string),
        returns: type_.string,
        handler: fn(user) {
          expression.concat_string(
            expression.string("hello "), 
            user,
          )
        },
      ),
    )

    module.eof()
  }

  mod
  |> module.render(render.default_context())
  |> render.to_string()
}

Functions are defined using the same definition type as constants. However, they also contain the information about the function itself. Here, we create this with function.new1 to specify our function takes exactly one parameter.

We define that parameter using the parameter type (which we could also use to add labels), where we provide the parameter name and type.

After specifiying the return type, we start actually defining the function in the handler argument. We can treat this exactly like the function we’re defining: its only argument is the parameter we specified earlier.

fn(user) {
  expression.concat_string(
    expression.string("hello "), 
    user,
  )
}

But wait! We already defined a greeting earlier. Let’s use that instead (and add a space to it to ensure legibility). This is as sample as using the greeting variable defined by our use expression earlier.

fn(user) {
  expression.concat_string(
    expression.concat_string(greeting, expression.string(" ")),
    user,
  )
}

This generates the following code:

pub const greeting = "hello"

fn greet_user(user: String) -> String {
  greeting <> " " <> user
}

Type Safety

This function definition is actually already type safe! Here, gleamgen uses phantom types to ensure that the function returns a string. To be confident in this, we ensure that the expression returned by the function is of type Expression(String), matching the return type type_.GeneratedType(String).

Try changing the parameter or return type of this example. You’ll get a compile-time type error!

Of course, generating custom code by nature requires flexibility, so there are times this system can be an impediment. For that gleamgen includes a number of “dynamic” functions, which allow you to avoid the phantom type checking or create functions or custom tyeps with a variable number of parameters.

Importing

Now, let’s print this greeting! To do that, we need to be able to import the gleam/io module. With gleamgen, this is simple:

use io_mod <- module.with_import(import_.new(["gleam", "io"]))

io_mod contains a stored reference to this import. If we never use it, by default this import statement will not be rendered in the final code. This is useful if only certain code paths make use of a module.

To use the module reference here, we can use import_.value_of_type. This function takes the name of what we want to import (println), and its type. io.println has a type of fn(String) -> Nil, which we can represent with type_.function1(type_.string, type_.nil). However, because we’re referencing an existing function, here it would be cleaner to use type_.reference

With this, we can create a simple hello world program:

// ..
let io_println =
  import_.value_of_type(io_mod, "println", type_.reference(io.println))
use _main <- module.with_function(
  definition.new("main") |> definition.with_publicity(True),
  function.new0(returns: type_.nil, handler: fn() {
    expression.call1(io_println, expression.string("Hello, World!"))
  }),
)
// ..

Blocks

Let’s say we want to store a name in a local variable before we generate a reference to it. We can do this with the block.with_let_declaration function.

let main_function = fn() {
  use name <- block.with_let_declaration(
    "name",
    expression.string("Viktor"),
  )
  todo as "the rest of the function"
} 

Let’s also make a variable for the full greeting, and add a comment describing it.

let main_function = fn() {
  use name <- block.with_let_declaration(
    "name",
    expression.string("Viktor"),
  )
  use <- block.with_comments(["A greeting for the given name"])
  use greeting <- block.with_let_declaration(
    "greeting",
    expression.call1(greet_user, name),
  )
  todo as "the rest of the function"
}

With that, we can add a main function to our code, including an extra empty line before the print function. Here’s the final code:

import gleam/io
import gleamgen/expression
import gleamgen/expression/block
import gleamgen/function
import gleamgen/import_
import gleamgen/module
import gleamgen/module/definition
import gleamgen/parameter
import gleamgen/render
import gleamgen/type_

pub fn main() {
  let mod = {
    use io_mod <- module.with_import(import_.new(["gleam", "io"]))
    use greeting <- module.with_constant(
      definition.new("greeting") |> definition.with_publicity(True),
      expression.string("hello"),
    )

    use greet_user <- module.with_function(
      definition.new("greet_user"),
      function.new1(
        param1: parameter.new("user", type_.string),
        returns: type_.string,
        handler: fn(user) {
          expression.concat_string(
            expression.concat_string(greeting, expression.string(" ")),
            user,
          )
        },
      ),
    )

    let io_println =
      import_.value_of_type(io_mod, "println", type_.reference(io.println))

    let main_function = fn() {
      use name <- block.with_let_declaration(
        "name",
        expression.string("Viktor"),
      )
      use <- block.with_comments(["A greeting for the given name"])
      use greeting <- block.with_let_declaration(
        "greeting",
        expression.call1(greet_user, name),
      )
      use <- block.with_empty_line()
      expression.call1(io_println, greeting)
    }

    use _main <- module.with_function(
      definition.new("main") |> definition.with_publicity(True),
      function.new0(returns: type_.nil, handler: main_function),
    )

    module.eof()
  }

  mod
  |> module.render(render.default_context())
  |> render.to_string()
}

And here’s the code that ultimately generates:

import gleam/io

pub const greeting = "hello"

fn greet_user(user: String) -> String {
  greeting <> " " <> user
}

pub fn main() -> Nil {
  let name = "Viktor"
  // A greeting for the given name
  let greeting = greet_user(name)

  io.println(greeting)
}

Next Steps

Checkout the examples folder or gleamgen’s tests!

Search Document