Current section

Files

Jump to
aja lib io.ex
Raw

lib/io.ex

defmodule Aja.IO do
@moduledoc ~S"""
Some extra helper functions for working with IO data,
that are not in the core `IO` module.
"""
# TODO: Link about cowboy/mint, benchmarks with Jason
# TODO bench then inline
@doc ~S"""
Checks if IO data is empty in "constant" time.
Should only need to loop until it finds one character or binary to stop,
unlike `IO.iodata_length(iodata) == 0` which needs to perform the complete loop
to compute the length first.
## Examples
iex> Aja.IO.iodata_empty?(["", []])
true
iex> Aja.IO.iodata_empty?('a')
false
iex> Aja.IO.iodata_empty?(["a"])
false
iex> Aja.IO.iodata_empty?(["", [], ["" | "c"]])
false
## Rationale
Even if `IO.iodata_length/1` is a very efficient BIF implemented in C, it has a linear
algorithmic complexity and can become slow if invoked on an IO list with many elements.
This is not a far-fetched scenario, and a production use case can easily include
"big" IO-lists with:
- JSON encoding to IO-data of long lists / nested objects
- loops within HTML templates
"""
@spec iodata_empty?(iodata) :: boolean
def iodata_empty?(iodata)
def iodata_empty?(binary) when is_binary(binary), do: binary === ""
def iodata_empty?([]), do: true
def iodata_empty?([head | _]) when is_integer(head), do: false
def iodata_empty?([head | rest]) do
# optimized `and`
case iodata_empty?(head) do
false -> false
_ -> iodata_empty?(rest)
end
end
@doc """
Converts the argument to IO data according to the `String.Chars` protocol.
Leaves lists untouched without any validation, calls `to_string/1` on everything else.
This is the function invoked in string interpolations within the [i sigil](`Aja.sigil_i/2`).
Works with both [IO data](https://hexdocs.pm/elixir/IO.html#module-io-data) and
[Chardata](https://hexdocs.pm/elixir/IO.html?#module-chardata),
depending on the type of the `data` parameter.
## Examples
iex> Aja.IO.to_iodata(:foo)
"foo"
iex> Aja.IO.to_iodata(99)
"99"
iex> Aja.IO.to_iodata(["abc", 'def' | "ghi"])
["abc", 'def' | "ghi"]
"""
@compile {:inline, to_iodata: 1}
@spec to_iodata(String.Chars.t() | iodata | IO.chardata()) :: iodata | IO.chardata()
def to_iodata(data) when is_list(data) or is_binary(data) do
data
end
def to_iodata(data) do
String.Chars.to_string(data)
end
end