Current section
Files
Jump to
Current section
Files
lib/caffeine.ex
defmodule Caffeine do
@moduledoc """

A stream library with an emphasis on simplicity
"""
defmodule Stream do
@moduledoc """
Find the API under this module
"""
@typedoc """
The Caffeine.Stream data structure
"""
@opaque t :: nonempty_improper_list(term, (() -> t)) | []
@doc """
A special value whose presence signals the end of a stream
"""
@spec sentinel() :: []
def sentinel do
[]
end
@doc """
A predicate to test for the sentinel value
"""
@spec sentinel?(t) :: boolean
def sentinel?([]) do
true
end
def sentinel?([_ | x]) when is_function(x, 0) do
false
end
@spec construct?(t) :: boolean
def construct?([_ | x]) when is_function(x, 0) do
true
end
def construct?([]) do
false
end
@doc """
A stream whose head is the element _e_ and whose tail _s_ is the expression to generate successive elements
**Warning:** don't evaluate the expression _s_ before passing it to construct/2 otherwise it's no longer lazy.
"""
@spec construct(term, Macro.t()) :: t
defmacro construct(e, s) do
quote do
[unquote(e) | fn -> unquote(s) end]
end
end
@doc """
Extracts _n_ consecutive elements from the stream
A list of less than _n_ elements out if it reaches the sentinel.
"""
@spec take(t, integer) :: list
def take([], _) do
[]
end
def take(_, 0) do
[]
end
def take(x, n) when is_integer(n) and n > 0 do
[head(x) | take(tail(x), n - 1)]
end
@doc """
A simple map
The output stream is the input stream w/ the function _f_ applied to each of the elements.
"""
@spec map(t, (term -> term)) :: t
def map([], _) do
Caffeine.Stream.sentinel()
end
def map(s, f) when is_list(s) and is_function(tl(s)) and is_function(f, 1) do
construct(f.(head(s)), map(tail(s), f))
end
@doc """
The first element, if any, of the stream
"""
@spec head(t) :: term
def head(x) do
hd(x)
end
@doc """
The stream, if any, succeeding the first element
"""
@spec tail(t) :: t
def tail(x) do
release(tl(x))
end
@spec release((() -> t)) :: t
defp release(x) do
x.()
end
end
end