Same `repr` but different spacing in `math.cases`?

I’m trying to write my custom cases function with mat-like syntax.

#let mycases(..array) = math.cases(..array.pos().map(it => (it.at(0) + "," + $&$.body + it.slice(1).join(", "))))
$ mycases(1, 2; 3, 4) $
#let u = $ mycases(1, 2; 3, 4) $
#repr(u)

$ cases(1\,&2, 3\,&4) $
#let v = $ cases(1\,&2, 3\,&4) $
#repr(v)

However, there is no spacing before align-point(& in math mode) in mycases, but there is if I directly call cases. The repr function seems to show that the result code are the same. I have no idea why they look different.

I’m not sure why, but i’ve also noticed that using repr is not 100% reliable, as it doesn’t distinguish between some internal states. One example, as you noticed, is "," and $\,$.body. It’s weird, because doing #{repr(u) == repr(v)} evaluates to true, but #{u == v} evaluates to false.

Luckily, the fix is simple: just use math commas, i.e. $\,$.body, instead of "," :)

#let mycases(..array) = math.cases(
  ..array
  .pos()
  .map(it => (
    it.at(0) + $\,&$.body + it.slice(1).join($\,$.body)
  ))
)

and we see that now, #{u == v} evaluates to true.

Thank you for the explanation and the fix!

Still curious about what’s the difference and if there is a more “internal” version of repr for debug use.

Representation Function – Typst Documentation :

This function is for debugging purposes. Its output should not be considered stable and may change at any time.

To be specific, having the same repr does not guarantee that values are equivalent, and repr is not a strict inverse of eval.

2 Likes

I’ve actually seen that in the document. I understand that some functions, like sequence(...) or align-point(), can’t be handled by eval. If we’re not concerned with eval, I’d just love a more verbose printing function to make debugging easier. :smile:

Here’s an alternate repr function that shows the distinction. It is not fully fleshed out and doesn’t handle all indentation cases!

#let sequence = [].func()
#let math-symbol = $,$.func()  // the function type called "symbol" below

#let vrepr(elt, indent: 0) = {
  let vrepr = vrepr.with(indent: indent + 1)
  if type(elt) == array {
    let children = elt.map(vrepr)
    let child-len = calc.max(0, ..children.map(c => c.len()))
    let long-form = child-len > 20 or children.len() > 5
    "("
    if long-form {
      "\n"
      for child in children {
        "  " * indent
        child
        ",\n"
      }
      "  " * (indent - 1)
    } else {
      children.join(", ")
    }
    ")"
    return
  }
  if type(elt) != content {
    return std.repr(elt)
  }
  if elt.func() == text {
    repr(elt)
  } else if elt.func() == sequence {
    "sequence"
    vrepr(elt.children)
  } else {
    let fields = elt.fields()
    let newline = fields.len() > 1
    if newline {
      "\n"
      "  " * indent
    }
    vrepr(elt.func())
    if fields.len() == 0 {
      "()"
    } else if fields.len() == 1 {
      "("
      fields.pairs().map(((k, v)) => {k; ": "; vrepr(v)}).join()
      ")"
    } else {
      indent += 1
      "(\n"
        fields.pairs().map(((k, v)) => {
          "  " * indent
          k
          ": "
          vrepr(v, indent: indent)
        }).join(",\n")
      "\n)"
    }
  }
}


#let mycases(..array) = math.cases(..array.pos().map(it => (it.at(0) + "," + $&$.body + it.slice(1).join(", "))))

#let u = $ mycases(1, 2; 3, 4) $
#vrepr(u)
#let v = $ cases(1\,&2, 3\,&4) $
#vrepr(v)

#repr[*how* are &\&]

#vrepr[*how* are &\&]

Picture below: vrepr for cases and mycases with difference highlighted (the symbol content function, which is not the same as std.symbol). The highlight is something I added.

Next: repr vs vrepr for another piece of content.

1 Like