# `RDF.Description`
[🔗](https://github.com/rdf-elixir/rdf-ex/blob/v3.0.1/lib/rdf/model/description.ex#L1)

A set of RDF triples about the same subject.

`RDF.Description` implements:

- the `RDF.Data.Source` protocol
- Elixir's `Enumerable` protocol
- Elixir's `Collectable` protocol
- Elixir's `Inspect` protocol
- Elixir's `Access` behaviour

# `input`

```elixir
@type input() ::
  RDF.Star.Statement.coercible()
  | {RDF.Star.Statement.coercible_predicate(),
     RDF.Star.Statement.coercible_object()
     | [RDF.Star.Statement.coercible_object()]}
  | %{
      required(RDF.Star.Statement.coercible_predicate()) =&gt;
        RDF.Star.Statement.coercible_object()
        | [RDF.Star.Statement.coercible_object()]
    }
  | [
      RDF.Star.Statement.coercible()
      | {RDF.Star.Statement.coercible_predicate(),
         RDF.Star.Statement.coercible_object()
         | [RDF.Star.Statement.coercible_object()]}
      | t()
    ]
  | t()
```

# `predications`

```elixir
@type predications() :: %{
  required(RDF.Star.Statement.predicate()) =&gt; %{
    required(RDF.Star.Statement.object()) =&gt; nil
  }
}
```

# `t`

```elixir
@type t() :: %RDF.Description{
  predications: predications(),
  subject: RDF.Star.Statement.subject()
}
```

# `add`

```elixir
@spec add(t(), input(), keyword()) :: t()
```

Add statements to a `RDF.Description`.

Note: When the statements to be added are given as another `RDF.Description`,
the subject must not match subject of the description to which the statements
are added. As opposed to that `RDF.Data.merge/2` will produce a `RDF.Graph`
containing both descriptions.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P1, EX.O1})
    ...> |> RDF.Description.add({EX.P2, EX.O2})
    RDF.Description.new(EX.S, init: [{EX.P1, EX.O1}, {EX.P2, EX.O2}])

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O1})
    ...> |> RDF.Description.add({EX.P, [EX.O2, EX.O3]})
    RDF.Description.new(EX.S, init: [{EX.P, EX.O1}, {EX.P, EX.O2}, {EX.P, EX.O3}])

# `canonical_hash`

```elixir
@spec canonical_hash(
  t(),
  keyword()
) :: binary()
```

Returns a hash of the canonical form of the given description.

See `RDF.Dataset.canonical_hash/2` for more information.

## Example

    iex> RDF.Description.new(EX.S, init: {EX.p(), EX.O})
    ...> |> RDF.Description.canonical_hash()
    "4a883e60f7b38b89b72492f16114ea62cf9a21d0e232d066b1f59ef61c69ea12"

# `change_subject`

```elixir
@spec change_subject(t(), RDF.Star.Statement.coercible_subject()) :: t()
```

Changes the subject of a description.

# `count`

# `delete`

```elixir
@spec delete(t(), input(), keyword()) :: t()
```

Deletes statements from a `RDF.Description`.

Note: When the statements to be deleted are given as another `RDF.Description`,
the subject must not match subject of the description from which the statements
are deleted. If you want to delete only a matching description subject, you can
use `RDF.Data.delete/2`.

## Options

- `:on_graph_mismatch` - Controls behavior when deleting quads with a graph name
  (since descriptions don't belong to a specific graph):
  - `:warn` (default) - Log a warning and proceed with deletion
  - `:ignore` - Silently ignore the graph name and proceed
  - `:skip` - Skip the statement (don't delete)
  - `:error` - Raise an `ArgumentError`

  Note: A quad with `nil` as graph name is not considered a mismatch.

# `delete_predicates`

```elixir
@spec delete_predicates(
  t(),
  RDF.Star.Statement.coercible_predicate()
  | [RDF.Star.Statement.coercible_predicate()]
) :: t()
```

Deletes all statements with the given properties.

# `describes?`

```elixir
@spec describes?(t(), RDF.Star.Statement.subject()) :: boolean()
```

Checks if a `RDF.Description` has the given resource as subject.

## Examples

      iex> RDF.Description.new(EX.S1, init: {EX.p1, EX.O1})
      ...> |> RDF.Description.describes?(EX.S1)
      true
      iex> RDF.Description.new(EX.S1, init: {EX.p1, EX.O1})
      ...> |> RDF.Description.describes?(EX.S2)
      false

# `empty?`

```elixir
@spec empty?(t()) :: boolean()
```

Returns if the given `description` is empty.

Note: You should always prefer this over the use of `Enum.empty?/1` as it is significantly faster.

# `equal?`

```elixir
@spec equal?(t(), t()) :: boolean()
```

Checks if two `RDF.Description`s are equal.

Two `RDF.Description`s are considered to be equal if they contain the same triples.

# `fetch`

```elixir
@spec fetch(t(), RDF.Star.Statement.coercible_predicate()) ::
  {:ok, [RDF.Star.Statement.object()]} | :error
```

Fetches the objects for the given predicate of a Description.

When the predicate can not be found `:error` is returned.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.p, EX.O}) |> RDF.Description.fetch(EX.p)
    {:ok, [RDF.iri(EX.O)]}
    iex> RDF.Description.new(EX.S, init: [{EX.P, EX.O1}, {EX.P, EX.O2}])
    ...> |> RDF.Description.fetch(EX.P)
    {:ok, [RDF.iri(EX.O1), RDF.iri(EX.O2)]}
    iex> RDF.Description.new(EX.S) |> RDF.Description.fetch(EX.foo)
    :error

# `first`

```elixir
@spec first(t(), RDF.Star.Statement.coercible_predicate(), any()) ::
  RDF.Star.Statement.object() | nil
```

Gets a single object for the given predicate of a Description.

When the predicate can not be found, the optionally given default value or `nil` is returned.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O}) |> RDF.Description.first(EX.P)
    RDF.iri(EX.O)
    iex> RDF.Description.new(EX.S) |> RDF.Description.first(EX.foo)
    nil
    iex> RDF.Description.new(EX.S) |> RDF.Description.first(EX.foo, :bar)
    :bar

# `get`

```elixir
@spec get(t(), RDF.Star.Statement.coercible_predicate(), any()) ::
  [RDF.Star.Statement.object()] | any()
```

Gets the objects for the given predicate of a Description.

When the predicate can not be found, the optionally given default value or `nil` is returned.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O}) |> RDF.Description.get(EX.P)
    [RDF.iri(EX.O)]
    iex> RDF.Description.new(EX.S) |> RDF.Description.get(EX.foo)
    nil
    iex> RDF.Description.new(EX.S) |> RDF.Description.get(EX.foo, :bar)
    :bar

# `get_and_update`

```elixir
@spec get_and_update(
  t(),
  RDF.Star.Statement.coercible_predicate(),
  ([RDF.Star.Statement.Object] -&gt; {[RDF.Star.Statement.Object], t()} | :pop)
) :: {[RDF.Star.Statement.Object], t()}
```

Gets and updates the objects of the given predicate of a Description, in a single pass.

Invokes the passed function on the objects of the given predicate; this
function should return either `{objects_to_return, new_object}` or `:pop`.

If the passed function returns `{objects_to_return, new_objects}`, the return
value of `get_and_update` is `{objects_to_return, new_description}` where
`new_description` is the input `Description` updated with `new_objects` for
the given predicate.

If the passed function returns `:pop` the objects for the given predicate are
removed and a `{removed_objects, new_description}` tuple gets returned.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O})
    ...> |> RDF.Description.get_and_update(EX.P, fn current_objects ->
    ...>      {current_objects, EX.New}
    ...>    end)
    {[RDF.iri(EX.O)], RDF.Description.new(EX.S, init: {EX.P, EX.New})}
    iex> RDF.Graph.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
    ...> |> RDF.Graph.description(EX.S)
    ...> |> RDF.Description.get_and_update(EX.P1, fn _ -> :pop end)
    {[RDF.iri(EX.O1)], RDF.Description.new(EX.S, init: {EX.P2, EX.O2})}

# `include?`

```elixir
@spec include?(t(), input(), keyword()) :: boolean()
```

Checks if the given `input` statements exist within `description`.

# `intersection`

```elixir
@spec intersection(t(), t() | RDF.Graph.t() | RDF.Dataset.t() | RDF.Graph.input()) ::
  t()
```

Returns a new description that is the intersection of the given `description` with the given `data`.

The `data` can be given in any form an `RDF.Graph` can be created from.
When a `RDF.Dataset` is given, the aggregated description of the subject of
`description` is used for the intersection.

## Examples

    iex> EX.S
    ...> |> EX.p(EX.O1, EX.O2)
    ...> |> RDF.Description.intersection(EX.S |> EX.p(EX.O2, EX.O3))
    EX.S |> EX.p(EX.O2)

    iex> EX.S
    ...> |> EX.p(EX.O1, EX.O2)
    ...> |> RDF.Description.intersection({EX.Other, EX.p, EX.O2, EX.O3})
    RDF.Description.new(EX.S)

# `map`

```elixir
@spec map(t(), RDF.Star.Statement.term_mapping()) :: map()
```

Returns a map of a `RDF.Description` where each element from its triples is mapped with the given function.

The subject is not part of the result. If you want the subject in an outer map,
just put the description in a graph and use `RDF.Graph.map/2`.

The function `fun` will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:predicate` or `:object`, while
`rdf_term` is the RDF term to be mapped. When the given function returns
`nil` this will be interpreted as an error and will become the overhaul result
of the `map/2` call.

Note: RDF-star statements where the object is a triple will be ignored.

## Examples

    iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
    ...> |> RDF.Description.map(fn
    ...>      {:predicate, predicate} ->
    ...>        predicate
    ...>        |> to_string()
    ...>        |> String.split("/")
    ...>        |> List.last()
    ...>        |> String.to_atom()
    ...>    {_, term} ->
    ...>      RDF.Term.value(term)
    ...>    end)
    %{p: ["Foo"]}

# `new`

```elixir
@spec new(
  RDF.Star.Statement.coercible_subject() | t(),
  keyword()
) :: t()
```

Creates an `RDF.Description` about the given subject.

The created `RDF.Description` can be initialized with any form of data which
`add/2` understands with the `:init` option. Additionally, a function returning
the initialization data in any of these forms can be as the `:init` value.

## Examples

    RDF.Description.new(EX.S)
    RDF.Description.new(EX.S, init: {EX.S, EX.p, EX.O})
    RDF.Description.new(EX.S, init: {EX.p, [EX.O1, EX.O2]})
    RDF.Description.new(EX.S, init: [{EX.p1, EX.O1}, {EX.p2, EX.O2}])
    RDF.Description.new(EX.S, init: RDF.Description.new(EX.S, init: {EX.P, EX.O}))
    RDF.Description.new(EX.S, init: fn -> {EX.p, EX.O} end)

# `pop`

```elixir
@spec pop(t()) :: {RDF.Star.Triple.t() | [RDF.Star.Statement.Object] | nil, t()}
```

Pops an arbitrary triple from a `RDF.Description`.

# `pop`

```elixir
@spec pop(t(), RDF.Star.Statement.coercible_predicate()) ::
  {[RDF.Star.Statement.object()] | nil, t()}
```

Pops the objects of the given predicate of a Description.

Removes the objects for the given `predicate` from `description`.

Returns a tuple containing a list the objects for the given predicate
and the updated description without the respective statements.
`nil` is returned instead of the objects if `description` does
not contain any statements with the given `predicate`.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O})
    ...> |> RDF.Description.pop(EX.P)
    {[RDF.iri(EX.O)], RDF.Description.new(EX.S)}

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O})
    ...> |> RDF.Description.pop(EX.Missing)
    {nil, RDF.Description.new(EX.S, init: {EX.P, EX.O})}

# `predicates`

```elixir
@spec predicates(t()) :: MapSet.t()
```

The set of all properties used in the predicates within a `RDF.Description`.

## Examples

    iex> RDF.Description.new(EX.S1, init: [
    ...>   {EX.p1, EX.O1},
    ...>   {EX.p2, EX.O2},
    ...>   {EX.p2, EX.O3}])
    ...> |> RDF.Description.predicates()
    MapSet.new([EX.p1, EX.p2])

# `put`

```elixir
@spec put(t(), input(), keyword()) :: t()
```

Adds statements to a `RDF.Description` and overwrites all existing statements with already used predicates.

Note: As it is a destructive function this function is stricter in its handling of
`RDF.Description`s than `add/3`. The subject of a `RDF.Description` to be put must
match. If you want to overwrite existing statements with those from the description of
another subject, you'll have to explicitly change the subject with `change_subject/2`
first before using `put/3`.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.P, EX.O1})
    ...> |> RDF.Description.put({EX.P, EX.O2})
    RDF.Description.new(EX.S, init: {EX.P, EX.O2})

# `rename_resource`

```elixir
@spec rename_resource(t(), RDF.Resource.coercible(), RDF.Resource.coercible()) :: t()
```

Replaces all occurrences of `old_id` in `description` with `new_id`.

# `statement_count`

```elixir
@spec statement_count(t()) :: non_neg_integer()
```

Returns the number of statements of a `RDF.Description`.

# `statements`

# `subject`

```elixir
@spec subject(t()) :: RDF.Star.Statement.subject()
```

Returns the subject IRI or blank node of a description.

# `take`

```elixir
@spec take(t(), [RDF.Star.Statement.coercible_predicate()] | Enum.t() | nil) :: t()
```

Creates a description from another one by limiting its statements to those using one of the given `predicates`.

If `predicates` contains properties that are not used in the `description`, they're simply ignored.

If `nil` is passed, the description is left untouched.

# `triples`

```elixir
@spec triples(
  t(),
  keyword()
) :: [RDF.Star.Triple.t()]
```

The list of all triples within a `RDF.Description`.

When the optional `:filter_star` flag is set to `true` RDF-star triples with a triple as subject or object
will be filtered. So, for a description with a triple as a subject you'll always get an empty list.
The default value of the `:filter_star` flag is `false`.

# `update`

```elixir
@spec update(
  t(),
  RDF.Star.Statement.coercible_predicate(),
  RDF.Star.Statement.coercible_object() | nil,
  ([RDF.Star.Statement.object()] -&gt;
     [RDF.Star.Statement.coercible_object()]
     | RDF.Star.Statement.coercible_object()
     | nil)
) :: t()
```

Updates the objects of the `predicate` in `description` with the given function.

If `predicate` is present in `description` with `objects` as value,
`fun` is invoked with argument `objects` and its result is used as the new
list of objects of `predicate`. If `predicate` is not present in `description`,
`initial` is inserted as the objects of `predicate`. The initial value will
not be passed through the update function.

The initial value and the returned objects by the update function will be automatically
coerced to proper RDF object values before added.

## Examples

    iex> RDF.Description.new(EX.S, init: {EX.p, EX.O})
    ...> |> RDF.Description.update(EX.p, fn objects -> [EX.O2 | objects] end)
    RDF.Description.new(EX.S, init: [{EX.p, EX.O}, {EX.p, EX.O2}])
    iex> RDF.Description.new(EX.S)
    ...> |> RDF.Description.update(EX.p, EX.O, fn _ -> EX.O2 end)
    RDF.Description.new(EX.S, init: {EX.p, EX.O})

# `update_all_objects`

```elixir
@spec update_all_objects(
  t(),
  (RDF.Star.Statement.predicate(), RDF.Star.Statement.object() -&gt;
     [RDF.Star.Statement.coercible_object()]
     | RDF.Star.Statement.coercible_object()
     | nil)
) :: t()
```

Updates all objects in `description` with the given function.

`fun` is invoked for each object in `description` with the predicate and the object as two arguments.
If `nil` is returned by `fun`, the respective object will be removed from `description`.
The returned values by the update function will be coerced to proper RDF object values before added.

## Examples

    iex> EX.S |> EX.p1(1) |> EX.p2([2, 3])
    ...> |> RDF.Description.update_all_objects(fn _predicate, object ->
    ...>      RDF.XSD.Numeric.add(object, 1)
    ...>    end)
    EX.S
    |> EX.p1(2)
    |> EX.p2([3, 4])

# `update_all_predicates`

```elixir
@spec update_all_predicates(
  t(),
  ({RDF.Star.Statement.predicate(), [RDF.Star.Statement.object()]} -&gt;
     [RDF.Star.Statement.coercible_object()]
     | RDF.Star.Statement.coercible_object()
     | nil)
) :: t()
```

Updates all predications in `description` with the given function.

`fun` is invoked with a tuple `{predicate, objects}` for each predicate in `description`.
If `nil` is returned by `fun`, the respective predications will be removed from `description`.
The returned values by the update function will be coerced to proper RDF object values before added.

## Examples

    iex> EX.S |> EX.p1(1) |> EX.p2([2, 3])
    ...> |> RDF.Description.update_all_predicates(fn {_predicate, objects} -> ["foo" | objects] end)
    EX.S
    |> EX.p1([1, "foo"])
    |> EX.p2([2, 3, "foo"])

# `values`

```elixir
@spec values(
  t(),
  keyword()
) :: map()
```

Returns a map of the native Elixir values of a `RDF.Description`.

The subject is not part of the result. It can be converted separately with
`RDF.Term.value/1`, or, if you want the subject in an outer map, just put the
description in a graph and use `RDF.Graph.values/2`.

When a `:context` option is given with a `RDF.PropertyMap`, predicates will
be mapped to the terms defined in the `RDF.PropertyMap`, if present.

Note: RDF-star statements where the object is a triple will be ignored.

## Examples

    iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
    ...> |> RDF.Description.values()
    %{"http://example.com/p" => ["Foo"]}

    iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
    ...> |> RDF.Description.values(context: %{p: ~I<http://example.com/p>})
    %{p: ["Foo"]}

# `without_quoted_triple_objects`

```elixir
@spec without_quoted_triple_objects(t()) :: t()
```

Removes all objects from a description which are quoted triples.

---

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