How to create "nothing" instead of an "empty thing"?

I have the following (specific) problem which has its cause in a more general problem: I would like to create a grid, where one or more rows are only printed depending on some condition.

E.g. in the following example we have a grid with four rows. The third row should only be printed if some condition is true. If the condition is false, a grid with only three rows should be printed:

#grid(
      columns: (auto, auto),
      row-gutter: 1em,
      column-gutter: 1em,
      align: (right, left),
      "hallo1", "world1",
      "hallo2", "world2",
      if (false) {
        "hallo3", "world2"
      },
      "hallo4", "world4",
)

For the sake of simplicity I’ve just used false as a condition within the if-statement.

Unfortunately the result is:
image

I.e. Typst produces a grid with four rows, where the content of the third row is sort of an empty element (presumably with height 0). But as row-gutter is 1em, we have a vertical gap of that size.

There is a work-around in this case: I can set row-gutter to 0em and use a box in each row to give it the desired vertical space as follows:

#grid(
      columns: (auto, auto),
      row-gutter: 0em,
      column-gutter: 1em,
      align: (right, left),
      box("hallo1", height: 16pt), "world1",
      box("hallo2", height: 16pt), "world2",
      if (false) {
        box("hallo3", height: 16pt)
      },
      if (false) {
        text("world3")
      },
      box("hallo4", height: 16pt), "world4",
)

This leads to the desired output, but the code necessary to produce it is not ideal (to put it mildly):
image

In my opinion, the basic problem is that the if-statement produces some sort of empty element, if the condition evaluates to false. In my opinion it should produce “nothing”, i.e. it should render a grid which consists of only three rows.

Is there any way to achieve this in Typst or is this a known behaviour? … and if so, why is Typst implemented this way?

No comment on the implementation reasons, but the way I’ve approached this in the past is to make an array of rows, do some higher-order functions on the array to manipulate and remove rows, then flatten and spread the array into the table arguments.

#grid(
  columns: (auto, auto, auto),
  row-gutter: 1em,
  column-gutter: 1em,
  align: (right, center, left),
  ..(
    ("apples", "and", "bananas"),
    ("this", "or", "that"),
    ("tea", "for", "two"),
    ("raining cats", "and", "dogs"),
    ("all", "or", "nothing"),
    ("up", "to", "you")
  )
  .filter(row => row.at(1) != "or")
  .flatten()
)

image

An if with no else evaluates to none if the condition is false. To remove elements of the array that evaluate to none, that’s also just a .filter:

#grid(
  columns: (auto, auto, auto),
  row-gutter: 1em,
  column-gutter: 1em,
  align: (right, center, left),
  ..(
    ("apples", "and", "bananas"),
    {if (false) {("this", "or", "that")}},
    ("tea", "for", "two"),
    {if (false) {("raining cats", "and", "dogs")}},
    ("all", "or", "nothing"),
    ("up", "to", "you")
  )
  .filter(row => row != none)
  .flatten()
)
1 Like

You don’t need to construct and filter an array, you can just spread the whole if statement, which contains the row as an array:

#grid(
    columns: (auto, auto),
    row-gutter: 1em,
    column-gutter: 1em,
    align: (right, left),

    "hallo1", "world1",
    "hallo2", "world2",
    ..if false { ("hallo3", "world2") },
    "hallo4", "world4",
)

This works because when spreading ..none, you don’t pass an “empty thing” to the grid function, but instead pass “nothing” as you intended.

3 Likes

Typst is expression-based, meaning that everything is an expression that gets evaluated to something. Other languages that do this include Scala, Rust, Kotlin, etc. (cf. Expression-oriented programming language - Wikipedia)
In such languages, an if statement also gets evaluated. In Typst’s case, it takes the value of the block which was chosen by the condition. For example:

#let a = if cond {3} else {4}

Here a would be 3 if cond is true, and 4 otherwise
In your case, you haven’t specified any else clause. As such, if the condition fails, the statement is evaluated to none

#let b = if cond {3}

Here b equals 3 if cond is true, and none otherwise

Consequently, if your condition is false, then the optional row’s cells are replaced by a single none value, which is interpreted as an empty cell by grid

4 Likes

Thanks to all for the really good answers!

1 Like