PassiveSupport.Item (passive_support v0.8.4)

Functions for handling arbitrary-shape values.

Link to this section Summary

Functions

Returns true for empty enumerables, empty tuples, whitespace-only strings, nil, and false; returns false for any other value.

Traverses into item through an arbitrary path of keys and indices.

Returns the value for any present?/1 value and nil for any blank?/1 value.

Returns true of any value that is not blank?/1

Calls fun.(item), returning item with any transformations done therein.

Calls fun.(item) and returns item unaltered.

Link to this section Types

Link to this type

key_or_index()

Specs

key_or_index() :: atom() | integer() | String.t()

Specs

t() :: any()
Link to this type

traversable()

Specs

traversable() :: tuple() | list() | map() | struct()

Link to this section Functions

Specs

blank?(t()) :: boolean()

Returns true for empty enumerables, empty tuples, whitespace-only strings, nil, and false; returns false for any other value.

Note that while a string containing only whitespace can be considered blank, a charlist of the same nature will return false. Because charlists are represented internally as lists of integers, a charlist of whitespace would be indescernible from a list of numeric integers, neither of which would be individually considered blank, and therefore should not be regarded as blank in tandem.

Examples

iex> blank?({})
true
iex> blank?(%{})
true
iex> blank?(MapSet.new())
true
iex> blank?(0)
false
iex> blank?(nil)
true
iex> blank?(false)
true
iex> blank?("  ")
true
iex> blank?('  ') # [32, 32]
false
iex> blank?(" hi ")
false
Link to this function

dig(item, path \\ [], default \\ nil)

Specs

dig(traversable(), key_or_index() | [key_or_index()], t()) :: t()

Traverses into item through an arbitrary path of keys and indices.

Think of dig/3 as an extension of the concept at the heart of Access.get/3. There are times when the data you're working with is a deeply-nested structure, and your bigger concern is what a specific part of that data looks like. In cases where you are working with one sort of data type (e.g., maps, structs, tuples, or lists), then you know you can use the same function or macro to dig down through each successive layer of nested data. However, when working with disparate data types, keeping track of the structure of each level of the nested data becomes an exercise in both patience, and trial and error.

The assurance dig/3 provides is that, until arriving at a leaf node within item or fully traversing through path, the function will retrieve the element of item at the next point within path, dispatching to the correct function for doing so based on the structure of the data at the current level of traversal. If dig arrives at a nil value at any point in traversal, the remainder of the path is dropped, and the value provided as default (which, by custom, defaults to nil itself) is immediately returned to the caller.

Note: The only event in which this function will raise an exception is when it attempts to traverse the path of a scalar value. To guarantee consistency in the traversal rules, trying to traverse a tuple with an out-of-bounds index results in nil being returned instead of an ArgumentError being raised. Similarly, attempting to dig into a struct at a keyword that it doesn't define will return nil instead of raising a KeyError. For exception propagation, use dig!/3 instead.

Examples

iex> pets = %{
...>   cats: [
...>     %{name: "Lester", favorite_toy: "feather"},
...>     %{name: "Janice", favorite_toy: "laser pointer"}
...>   ],
...>   dogs: [
...>     %{name: "Scrump", favorite_toy: "rope"},
...>     %{name: "Stitch", favorite_toy: "ball"}
...>   ],
...>   hyenas: [
...>     %{"what is this" => "oh no", "hyenas can't be pets" => "too wild"}
...>   ]
...> }
iex> dig(pets, [:dogs])
[%{name: "Scrump", favorite_toy: "rope"}, %{name: "Stitch", favorite_toy: "ball"}]
iex> dig(pets, [:cats, 1])
%{name: "Janice", favorite_toy: "laser pointer"}
iex> dig(pets, [:hyenas, 0, "what is this"])
"oh no"
iex> dig(pets, [:dogs, 0, :favorite_food, :ingredients])
nil

You can also use dig through lists of nested data structures by passing an empty list [] as a part of path.

iex> connor_family = [
...>  {"Sarah", %{favorite_movies: ["Dr. Strangelove", "China Town", "Citizen Kane"]}},
...>  {"John", %{favorite_movies: ["Tron", "Star Wars", "The Fifth Element"]}},
...>  {"Cameron", %{favorite_movies: ["Robocop", "Logan's Run", "The Animatrix - The Second Renaissance Part 2"]}}
...> ]
iex> dig(connor_family, [[], 1, :favorite_movies, 2])
["Citizen Kane", "The Fifth Element", "The Animatrix - The Second Renaissance Part 2"]

Specs

presence(t()) :: t() | none()

Returns the value for any present?/1 value and nil for any blank?/1 value.

Examples

iex> presence({})
nil
iex> presence(%{})
nil
iex> presence(nil)
nil
iex> presence(false)
nil
iex> presence([false])
[false]
iex> presence("  ")
nil
iex> presence(" hi ")
" hi "

Specs

present?(t()) :: boolean()

Returns true of any value that is not blank?/1

Examples

iex> present?({})
false
iex> present?(%{})
false
iex> present?(MapSet.new(1..10))
true
iex> present?(0)
true
iex> present?(nil)
false
iex> present?(false)
false
iex> present?("  ")
false
iex> present?(' ')
true
iex> present?(" hi ")
true

Calls fun.(item), returning item with any transformations done therein.

Basically, I got tired of not being able to pipe a value into arbitrary positions of other functions.

Examples

iex> ", " |> Item.tap(&Enum.join(1..10, &1))
"1, 2, 3, 4, 5, 6, 7, 8, 9, 10"

iex> false |> Item.tap(&(unless &1, do: "oh!"))
"oh!"

iex> [5, 4, 3, 2, 1] |> hd |> Item.tap(&(&1 * &1))
25

iex> "hello world!" |> Item.tap(&Regex.scan(~r/lo?/, &1))
[["l"], ["lo"], ["l"]]

Calls fun.(item) and returns item unaltered.

This function mimics the tee program in shell systems, allowing a copy of the passed-in item to be sent to another function amid a pipeline of other functions that are transforming it or handing it off for another value.

Examples

1..10 |> Enum.to_list |> tee(&Logger.info(inspect(&1, label: "`1..10` as a list")))
# (… output from Logger …)
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

some_data |> tee(&IO.inspect(&1, label: "encoding as json")) |> Jason.encode!
# => JSON-encoded data