nim-lang

Unpack multiple variables from sequence


I am expecting the code below to print chr7.

import strutils

var splitLine = "chr7    127471196  127472363  Pos1  0  +".split()
var chrom, startPos, endPos = splitLine[0..2]
echo chrom

Instead it prints @[chr7, 127471196, 127472363].

Is there a way to unpack multiple values from sequences at the same time?

And what would the tersest way to do the above be if the elements weren't contiguous? For example:

var chrom, startPos, strand = splitLine[0..1, 5]

Gives the error:

read_bed.nim(8, 40) Error: type mismatch: got (seq[string], Slice[system.int], int literal(5))
but expected one of:
system.[](a: array[Idx, T], x: Slice[system.int])
system.[](s: string, x: Slice[system.int])
system.[](a: array[Idx, T], x: Slice[[].Idx])
system.[](s: seq[T], x: Slice[system.int])

  var chrom, startPos, strand = splitLine[0..1, 5]
                                         ^

Solution

  • This can be accomplished using macros.

    import macros
    
    macro `..=`*(lhs: untyped, rhs: tuple|seq|array): auto =
      # Check that the lhs is a tuple of identifiers.
      expectKind(lhs, nnkPar)
      for i in 0..len(lhs)-1:
        expectKind(lhs[i], nnkIdent)
      # Result is a statement list starting with an
      # assignment to a tmp variable of rhs.
      let t = genSym()
      result = newStmtList(quote do:
        let `t` = `rhs`)
      # assign each component to the corresponding
      # variable.
      for i in 0..len(lhs)-1:
        let v = lhs[i]
        # skip assignments to _.
        if $v.toStrLit != "_":
          result.add(quote do:
            `v` = `t`[`i`])
    
    macro headAux(count: int, rhs: seq|array|tuple): auto =
      let t = genSym()
      result = quote do:
        let `t` = `rhs`
        ()
      for i in 0..count.intVal-1:
        result[1].add(quote do:
          `t`[`i`])
    
    template head*(count: static[int], rhs: untyped): auto =
      # We need to redirect this through a template because
      # of a bug in the current Nim compiler when using
      # static[int] with macros.
      headAux(count, rhs)
    
    var x, y: int
    (x, y) ..= (1, 2)
    echo x, y
    (x, _) ..= (3, 4)
    echo x, y
    (x, y) ..= @[4, 5, 6]
    echo x, y
    let z = head(2, @[4, 5, 6])
    echo z
    (x, y) ..= head(2, @[7, 8, 9])
    echo x, y
    

    The ..= macro unpacks tuple or sequence assignments. You can accomplish the same with var (x, y) = (1, 2), for example, but ..= works for seqs and arrays, too, and allows you to reuse variables.

    The head template/macro extracts the first count elements from a tuple, array, or seqs and returns them as a tuple (which can then be used like any other tuple, e.g. for destructuring with let or var).