Erlang values

primitive ErlangValues
  """
  Takes a value out of a term, keeping the term model out of the caller's code.

  Each function answers `None` when the term is of another kind, or when there
  is no term at all, so a lookup and the read that follows are one expression
  and two clauses:

  ```pony
  match ErlangValues.integer(config.get(ErlangBinary("timeout")))
  | let timeout: I64 => use(timeout)
  | None => use(5_000)
  end
  ```

  Nothing here casts: the type is known once the second clause is reached, and
  a value of the wrong kind is a `None` rather than an error.
  """

  fun atom_name(term: (ErlangTerm | None)): (String | None) =>
    """The name of an atom."""
    match term
    | let atom: ErlangAtom => atom.name
    else None
    end

  fun bytes(term: (ErlangTerm | None)): (String | None) =>
    """The bytes of a binary, as they arrived."""
    match term
    | let binary: ErlangBinary => binary.value
    else None
    end

  fun chars(term: (ErlangTerm | None)): (String | None) =>
    """The bytes of a string, which is a charlist on the wire."""
    match term
    | let string: ErlangString => string.value
    else None
    end

  fun integer(term: (ErlangTerm | None)): (I64 | None) =>
    """
    The value of an integer that fits into 64 bits.

    A wider integer is an `ErlangBigInteger` and answers `None` here; read it
    through `decimal()` instead of narrowing it silently.
    """
    match term
    | let number: ErlangInteger => number.value
    else None
    end

  fun float(term: (ErlangTerm | None)): (F64 | None) =>
    """The value of a float."""
    match term
    | let number: ErlangFloat => number.value
    else None
    end

  fun pid(term: (ErlangTerm | None)): (ErlangPid | None) =>
    """The pid itself, ready to send to."""
    match term
    | let found: ErlangPid => found
    else None
    end