# `Latu.Result.Literal`
[🔗](https://github.com/zero-one-group/latu/blob/v0.4.0/lib/latu/result/literal.ex#L1)

A literal the server sent, back into an Elixir term.

Public because `latu_ml` reads it: an ML attribute — a coefficient, an intercept, a vector —
comes back from the server as a literal, not as a frame. `Latu.Plan.lit/1` is the other
direction, and the two are deliberately not symmetric: the encoder picks one Spark type per
Elixir type, while this has to read back every arm Spark can produce.

Transcribed from PySpark's `LiteralExpression._to_value`, with three departures recorded in
`docs/deviations.md`: Latu decodes `specialized_array`, which PySpark refuses; Latu reads the
`data_type` field in preference to the `struct_type` field 4.1 deprecated, which PySpark still
reads exclusively; and a UDT comes back as a `Latu.Result.UDT` where PySpark raises.

# `value`

```elixir
@spec value(Latu.Protocol.Spark.Connect.Expression.Literal.t() | nil) ::
  {:ok, term()} | {:error, Latu.Error.t()}
```

The Elixir term a literal carries, or `{:error, _}` naming the arm that has no term.

Integers come back as integers whatever their width, and both `float` and `double` as floats,
because Elixir has neither a byte nor a 32-bit float — the same collapse
`Latu.Column.lit/1` applies going out. A `day_time_interval` comes back as a count of
**microseconds**; Elixir's `Duration` is the obvious upgrade and waits on someone wanting
it.

A struct literal becomes a map with atom keys, as `Latu.collect/2` does for a row. A
UDT-typed one becomes a `Latu.Result.UDT` instead, since it names a class and not fields.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
