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

Functions for working with RDF data structures.

This module provides a rich API for RDF data structures built on top of
the minimal `RDF.Data.Source` protocol, similar to how Elixir's `Enum`
module builds on the `Enumerable` protocol.

# `count`

```elixir
@spec count(RDF.Data.Source.t()) :: non_neg_integer()
```

Returns the count of primary elements relative to the structure type.

This provides a consistent "size" metric that is meaningful for each structure level.

- `:description`: counts predicates (with non-empty object sets)
- `:graph`: counts subjects (descriptions)
- `:dataset`: counts graphs

## Examples

    iex> desc = EX.S |> EX.p(EX.O1) |> EX.q(EX.O2)
    iex> RDF.Data.count(desc)
    2  # 2 predicates

    iex> graph = RDF.Graph.new([{EX.S1, EX.p, EX.O}, {EX.S2, EX.p, EX.O}])
    iex> RDF.Data.count(graph)
    2  # 2 subjects

    iex> dataset = RDF.Dataset.new([{EX.S, EX.p, EX.O, EX.G1}, {EX.S, EX.p, EX.O, EX.G2}])
    iex> RDF.Data.count(dataset)
    2  # 2 graphs

# `default_graph`

```elixir
@spec default_graph(RDF.Data.Source.t()) :: RDF.Data.Source.t()
```

Returns the default graph from the RDF data structure.

## Examples

    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.default_graph(desc)
    RDF.graph({EX.S, EX.p, EX.O})

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, nil}])
    iex> RDF.Data.default_graph(dataset)
    RDF.graph({EX.S, EX.p, EX.O})

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.default_graph(dataset)
    RDF.graph()

# `delete`

```elixir
@spec delete(
  RDF.Data.Source.t(),
  RDF.Statement.t() | [RDF.Statement.t()] | RDF.Data.Source.t()
) ::
  RDF.Data.Source.t()
```

Deletes matching statements from the data structure.

Returns a new data structure with the specified statements removed.
The resulting structure maintains the same type and metadata (like graph names)
as the original.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.delete(graph, {EX.S1, EX.p, EX.O1})
    RDF.graph({EX.S2, EX.p, EX.O2})

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.delete(graph, [{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    RDF.graph()

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.delete(graph, EX.S1 |> EX.p(EX.O1))
    RDF.graph({EX.S2, EX.p, EX.O2})

# `describes?`

```elixir
@spec describes?(RDF.Data.Source.t(), RDF.Resource.coercible()) :: boolean()
```

Returns `true` if the data describes the given subject.

Checks if any statements with the given subject exist in the data structure.

## Examples

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.describes?(graph, EX.Alice)
    true

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.describes?(graph, EX.Unknown)
    false

# `description`

```elixir
@spec description(RDF.Data.Source.t(), RDF.Resource.coercible()) ::
  RDF.Data.Source.t()
```

Returns the description of a specific subject in the RDF data structure.

If the subject doesn't exist, an empty description for that subject is returned.
Use `description/3` to provide a custom default value.

## Examples

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.description(graph, EX.Alice)
    EX.Alice |> EX.knows(EX.Bob)

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.description(graph, EX.Charlie)
    RDF.description(EX.Charlie)

# `description`

```elixir
@spec description(RDF.Data.Source.t(), RDF.Resource.coercible(), default) ::
  RDF.Data.Source.t() | default
when default: term()
```

Returns the description of a specific subject, or a default value if not found.

## Examples

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.description(graph, EX.Alice, nil)
    EX.Alice |> EX.knows(EX.Bob)

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.description(graph, EX.Charlie, nil)
    nil

    iex> graph = RDF.graph([{EX.Alice, EX.knows, EX.Bob}])
    iex> RDF.Data.description(graph, EX.Charlie, :not_found)
    :not_found

# `descriptions`

```elixir
@spec descriptions(RDF.Data.Source.t()) :: [RDF.Data.Source.t()]
```

Returns all descriptions in the data structure.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p(), EX.O1}, {EX.S2, EX.p(), EX.O2}])
    iex> RDF.Data.descriptions(graph)
    [EX.p(EX.S1, EX.O1), EX.p(EX.S2, EX.O2)]

    iex> dataset = RDF.dataset([{EX.S1, EX.p(), EX.O1, nil}, {EX.S2, EX.p(), EX.O2, EX.G}, {EX.S3, EX.p(), EX.O3, EX.G}])
    iex> RDF.Data.descriptions(dataset)
    [EX.p(EX.S1, EX.O1), EX.p(EX.S2, EX.O2), EX.p(EX.S3, EX.O3)]

# `each`

```elixir
@spec each(RDF.Data.Source.t(), (RDF.Statement.t() -&gt; any())) :: :ok
```

Invokes the given function for each statement in the data structure.

The function is invoked with each statement as its only argument and
 always returns `:ok`. It is useful for executing side effects on the data.

## Examples

    RDF.Data.each(graph, &IO.inspect/1)
    # prints each statement
    # => :ok

# `empty?`

```elixir
@spec empty?(RDF.Data.Source.t()) :: boolean()
```

Returns true if the given RDF data structure is empty.

## Examples

    iex> RDF.Data.empty?(RDF.graph())
    true

    iex> RDF.Data.empty?(RDF.graph([{EX.S, EX.p, EX.O}]))
    false

# `equal?`

```elixir
@spec equal?(RDF.Data.Source.t(), RDF.Data.Source.t()) :: boolean()
```

Checks equality of two data structures.

Supports cross-structure comparisons with special rules:

- description equals graph if graph contains only that description
- graph equals dataset if dataset contains only that graph
- otherwise compares statement sets

## Examples

    iex> desc1 = EX.S |> EX.p(EX.O)
    iex> desc2 = EX.S |> EX.p(EX.O)
    iex> RDF.Data.equal?(desc1, desc2)
    true

    iex> desc = EX.S |> EX.p(EX.O)
    iex> graph = RDF.graph([{EX.S, EX.p(), EX.O}])
    iex> RDF.Data.equal?(desc, graph)
    true

    iex> graph = RDF.graph([{EX.S, EX.p(), EX.O}])
    iex> dataset = RDF.dataset([{EX.S, EX.p(), EX.O, nil}])
    iex> RDF.Data.equal?(graph, dataset)
    true

# `filter`

```elixir
@spec filter(RDF.Data.Source.t(), (RDF.Statement.t() -&gt; boolean())) ::
  RDF.Data.Source.t()
```

Filters statements in the data structure based on a predicate function.

Returns a new data structure of the same type containing only the statements
for which the predicate function returns a truthy value.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p1, EX.O1}, {EX.S2, EX.p2, EX.O2}])
    iex> RDF.Data.filter(graph, fn {_s, p, _o} -> p == EX.p1 end)
    RDF.graph([{EX.S1, EX.p1, EX.O1}])

    iex> dataset = RDF.dataset([{EX.S, EX.p1, EX.O, EX.G}, {EX.S, EX.p2, EX.O, nil}])
    iex> RDF.Data.filter(dataset, fn {_s, p, _o, _g} -> p == EX.p1 end)
    RDF.dataset([{EX.S, EX.p1, EX.O, EX.G}])

# `graph`

```elixir
@spec graph(RDF.Data.Source.t(), RDF.Resource.coercible() | nil) ::
  RDF.Data.Source.t()
```

Returns a specific graph from the RDF data structure.

If the graph doesn't exist, an empty graph is returned.
Use `graph/3` to provide a custom default value.

## Examples

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.graph(dataset, EX.Graph1)
    RDF.graph({EX.S, EX.p, EX.O}, name: EX.Graph1)

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.graph(dataset, EX.NonExistent)
    RDF.graph(name: EX.NonExistent)

# `graph`

```elixir
@spec graph(RDF.Data.Source.t(), RDF.Resource.coercible() | nil, default) ::
  RDF.Data.Source.t() | default
when default: term()
```

Returns a specific graph from the RDF data structure, or a default value if not found.

## Examples

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.graph(dataset, EX.Graph1, nil)
    RDF.graph({EX.S, EX.p, EX.O}, name: EX.Graph1)

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.graph(dataset, EX.NonExistent, nil)
    nil

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.Graph1}])
    iex> RDF.Data.graph(dataset, EX.NonExistent, :not_found)
    :not_found

# `graph_count`

```elixir
@spec graph_count(RDF.Data.Source.t()) :: non_neg_integer()
```

Returns the count of graphs in the RDF data structure.

Depending on the structure type, the count represents:

- `:description`: Always 1 (implicit unnamed graph)
- `:graph`: Always 1
- `:dataset`: Number of graphs (including default if present)

## Examples

    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.graph_count(desc)
    1

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}])
    iex> RDF.Data.graph_count(graph)
    1

    iex> dataset = RDF.dataset([
    ...>   {EX.S1, EX.p, EX.O1, nil},
    ...>   {EX.S2, EX.p, EX.O2, EX.Graph}
    ...> ])
    iex> RDF.Data.graph_count(dataset)
    2

    iex> RDF.Data.graph_count(RDF.dataset())
    0

# `graph_names`

```elixir
@spec graph_names(RDF.Data.Source.t()) :: [RDF.IRI.t() | nil]
```

Returns all graph names in the data structure.

Behavior by structure type:

- `:description`: Always returns `[nil]` (implicit unnamed graph)
- `:graph`: Returns `[name]` where name is the graph's name (could be `nil`)
- `:dataset`: Returns all graph names

## Examples

    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.graph_names(desc)
    [nil]

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}], name: EX.Graph1)
    iex> RDF.Data.graph_names(graph)
    [~I<http://example.com/Graph1>]

    iex> dataset = RDF.Dataset.new([{EX.S, EX.p, EX.O, EX.G1}, {EX.S2, EX.p, EX.O2, nil}])
    iex> RDF.Data.graph_names(dataset)
    [nil, ~I<http://example.com/G1>]

# `graphs`

```elixir
@spec graphs(RDF.Data.Source.t()) :: [RDF.Data.Source.t()]
```

Returns all graphs in the data structure.

Behavior by structure type:

- `:description`: returns a single graph containing the description
- `:graph`: returns a list containing the graph itself
- `:dataset`: returns all graphs (including the default graph if present)

## Examples

    iex> description = EX.S |> EX.p(EX.O)
    iex> RDF.Data.graphs(description)
    [RDF.graph(description)]

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.graphs(graph)
    [graph]

    iex> dataset = RDF.dataset([{EX.S1, EX.p, EX.O1, nil}, {EX.S2, EX.p, EX.O2, EX.graph1}])
    iex> RDF.Data.graphs(dataset)
    [RDF.graph([{EX.S1, EX.p, EX.O1}]), RDF.graph([{EX.S2, EX.p, EX.O2}], name: EX.graph1)]

# `include?`

```elixir
@spec include?(
  data :: RDF.Data.Source.t(),
  statements :: RDF.Statement.t() | [RDF.Statement.t()] | RDF.Data.Source.t()
) :: boolean()
```

Checks if data includes the given statements.

Returns `true` if all given statements are present in the data structure.

The `statements` parameter can be:

- A single triple `{s, p, o}` or quad `{s, p, o, g}`
- A list of triples or quads
- Another structure implementing `RDF.Data.Source`

For datasets, triple patterns (without graph) are matched against **all**
graphs. Use a quad `{s, p, o, graph_name}` to check a specific graph (including
`nil` for the default graph).

## Examples

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.include?(graph, {EX.S, EX.p, EX.O})
    true

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.include?(graph, [{EX.S, EX.p, EX.O}])
    true

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}])
    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.include?(graph, desc)
    true

    iex> dataset = RDF.Dataset.new([
    ...>   {EX.S1, EX.p, EX.O1, nil},
    ...>   {EX.S2, EX.p, EX.O2, EX.Graph}
    ...> ])
    iex> RDF.Data.include?(dataset, {EX.S1, EX.p, EX.O1})
    true
    iex> RDF.Data.include?(dataset, {EX.S2, EX.p, EX.O2})
    true
    iex> RDF.Data.include?(dataset, {EX.S2, EX.p, EX.O2, nil})
    false
    iex> RDF.Data.include?(dataset, {EX.S2, EX.p, EX.O2, EX.Graph})
    true

# `map`

```elixir
@spec map(RDF.Data.Source.t(), (RDF.Statement.t() -&gt;
                            RDF.Statement.t() | [RDF.Statement.t()] | nil)) ::
  RDF.Data.Source.t()
```

Maps a transformation function over all statements in the data structure.

The given `fun` function can return:
  
- a statement tuple (triple or quad) - included in the result
- a list of statements - flattened and included in the result
- `nil` - excluded from the result

## Structural promotion

The result structure type depends on the mapped statements:

- stays the same type when all mapped statements fit the original structure
- promotes to a graph structure when statements have different subjects
- promotes to a dataset structure when statements have different graph names

## Examples

    iex> RDF.Data.map(EX.S |> EX.p(EX.O), fn {s, p, _o} -> {s, p, EX.New} end)
    EX.S |> EX.p(EX.New)

    iex> RDF.Data.map(RDF.graph({EX.S, EX.p, EX.O}), fn {s, p, _o} -> {s, p, EX.New} end)
    RDF.graph({EX.S, EX.p, EX.New})

Description upgrades to graph when mapped statements have different subjects:

    iex> desc = EX.S |> EX.p(EX.O1) |> EX.p(EX.O2)
    iex> RDF.Data.map(desc, fn {_s, p, o} -> {o, p, EX.S} end)
    RDF.graph([{EX.O1, EX.p, EX.S}, {EX.O2, EX.p, EX.S}])

Graph upgrades to dataset when mapped statements have different graph names:

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O1}, {EX.S, EX.p, EX.O2}])
    iex> RDF.Data.map(graph, fn {s, p, o} -> {s, p, o, o} end)
    RDF.dataset([{EX.S, EX.p, EX.O1, EX.O1}, {EX.S, EX.p, EX.O2, EX.O2}])

# `map_reduce`

```elixir
@spec map_reduce(RDF.Data.Source.t(), acc, (RDF.Statement.t(), acc -&gt; {mapped, acc})) ::
  {RDF.Data.Source.t(), acc}
when acc: term(), mapped: RDF.Statement.t() | [RDF.Statement.t()] | nil
```

Transforms statements and accumulates a value in a single pass.

Combines the functionality of `map/2` with an accumulator, similar to
`Enum.map_reduce/3`. For each statement, the function receives the statement
and the current accumulator, and returns a tuple with the mapped result and
the new accumulator.

The mapped result can be:

- a statement (triple or quad) - replaces the original statement
- `nil` - filters out the statement
- a list of statements - expands to multiple statements

Like `map/2`, structural promotion occurs based on the mapped results.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p1, EX.O1}, {EX.S2, EX.p2, EX.O2}])
    iex> RDF.Data.map_reduce(graph, 0, fn {s, p, _o}, count ->
    ...>   {{s, p, EX.New}, count + 1}
    ...> end)
    {RDF.graph([{EX.S1, EX.p1, EX.New}, {EX.S2, EX.p2, EX.New}]), 2}

# `merge`

```elixir
@spec merge([RDF.Data.Source.t() | RDF.Statement.t()]) :: RDF.Data.Source.t()
```

Merges a list of data structures and/or statements into a single structure.

## Examples

    iex> graph = RDF.graph({EX.S1, EX.p1, EX.O1})
    iex> RDF.Data.merge([graph, {EX.S2, EX.p2, EX.O2}, EX.S3 |> EX.p3(EX.O3)])
    RDF.graph([{EX.S1, EX.p1, EX.O1}, {EX.S2, EX.p2, EX.O2}, {EX.S3, EX.p3, EX.O3}])

    iex> RDF.Data.merge([])
    RDF.graph()

# `merge`

```elixir
@spec merge(
  RDF.Data.Source.t(),
  RDF.Data.Source.t()
  | RDF.Statement.t()
  | [RDF.Data.Source.t() | RDF.Statement.t()]
) :: RDF.Data.Source.t()
```

Merges two data structures with automatic structural promotion.

Rules:

- Description + Description with same subject → Description
- Description + Description with different subjects → Graph
- Graph + Graph with same name → Graph
- Graph + Graph with different names → Dataset
- Otherwise, promotes to the most complex structure required

## Examples

    iex> desc1 = EX.S |> EX.p1(EX.O1)
    iex> desc2 = EX.S |> EX.p2(EX.O2)
    iex> RDF.Data.merge(desc1, desc2)
    EX.S |> EX.p1(EX.O1) |> EX.p2(EX.O2)

    iex> graph1 = RDF.graph([{EX.S1, EX.p, EX.O}], name: EX.G1)
    iex> graph2 = RDF.graph([{EX.S2, EX.p, EX.O}], name: EX.G2)
    iex> RDF.Data.merge(graph1, graph2)
    RDF.dataset([{EX.S1, EX.p, EX.O, EX.G1}, {EX.S2, EX.p, EX.O, EX.G2}])

# `object_resources`

```elixir
@spec object_resources(RDF.Data.Source.t()) :: [RDF.Resource.t()]
```

Returns all unique resource objects in the data structure.

Resource objects are IRIs and blank nodes, excluding literals.
For all object terms including literals, use `objects/1`.

## Examples

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O1}, {EX.S, EX.p2, "literal"}])
    iex> RDF.Data.object_resources(graph)
    [~I<http://example.com/O1>]

# `objects`

```elixir
@spec objects(RDF.Data.Source.t()) :: [RDF.Term.t()]
```

Returns all unique object terms in the data structure.

Object terms include all RDF terms: IRIs, blank nodes, and literals.

## Examples

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O1}, {EX.S, EX.p2, "literal"}])
    iex> RDF.Data.objects(graph)
    [~L"literal", ~I<http://example.com/O1>]

# `objects`

```elixir
@spec objects(RDF.Data.Source.t(), (RDF.Term.t() -&gt; boolean()) | nil) :: [
  RDF.Term.t()
]
```

Returns all unique object terms in the data structure matching the filter function.

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}, {EX.S, EX.p2, "literal"}])
    iex> RDF.Data.objects(graph, &RDF.resource?/1)
    [RDF.iri(EX.O)]

# `pop`

```elixir
@spec pop(RDF.Data.Source.t()) :: {RDF.Statement.t() | nil, RDF.Data.Source.t()}
```

Removes and returns one statement from the RDF data structure.

Returns a tuple `{statement, remaining_data}` where `statement` is a triple or quad,
and `remaining_data` is the data structure without that `statement`. For empty data
structures, returns `{nil, data}`.

The specific statement returned is implementation-dependent and should not be
relied upon for ordering.

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> {triple, remaining} = RDF.Data.pop(graph)
    iex> triple
    {~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>}
    iex> RDF.Graph.empty?(remaining)
    true

    iex> RDF.Data.pop(RDF.graph())
    {nil, RDF.graph()}

# `predicate_count`

```elixir
@spec predicate_count(RDF.Data.Source.t()) :: non_neg_integer()
```

Returns the count of unique predicates in the data structure.

## Examples

    iex> desc = EX.S |> EX.p(EX.O1) |> EX.q(EX.O2)
    iex> RDF.Data.predicate_count(desc)
    2

    iex> graph = RDF.Graph.new([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}, {EX.S3, EX.q, EX.O3}])
    iex> RDF.Data.predicate_count(graph)
    2

# `predicates`

```elixir
@spec predicates(RDF.Data.Source.t()) :: [RDF.IRI.t()]
```

Returns all unique predicates in the data structure.

## Examples

    iex> graph = RDF.Graph.new([{EX.S, EX.p1, EX.O}, {EX.S, EX.p2, EX.O}])
    iex> RDF.Data.predicates(graph)
    [~I<http://example.com/p1>, ~I<http://example.com/p2>]

# `predicates`

```elixir
@spec predicates(RDF.Data.Source.t(), (RDF.IRI.t() -&gt; boolean()) | nil) :: [
  RDF.IRI.t()
]
```

Returns all unique predicates in the data structure matching the filter function.

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p1, EX.O}, {EX.S, EX.p2, EX.O}])
    iex> RDF.Data.predicates(graph, &(&1 == EX.p1()))
    [EX.p1()]

# `quads`

```elixir
@spec quads(RDF.Data.Source.t()) :: [RDF.Quad.t()]
```

Returns all statements as quads.

Converts all statements to quad format `{subject, predicate, object, graph_name}`.
For descriptions and graphs, adds the graph name (or `nil` for default graph).

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.quads(graph)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>, nil}]

    iex> named_graph = RDF.graph([{EX.S, EX.p, EX.O}], name: EX.G)
    iex> RDF.Data.quads(named_graph)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>, ~I<http://example.com/G>}]

# `reduce`

```elixir
@spec reduce(RDF.Data.Source.t(), (RDF.Statement.t(), acc -&gt; acc)) :: acc
when acc: term()
```

Reduces the RDF data structure using the given function without an initial accumulator.

The first statement becomes the initial accumulator.

Raises `Enum.EmptyError` if the data structure is empty.

# `reduce`

```elixir
@spec reduce(RDF.Data.Source.t(), acc, (RDF.Statement.t(), acc -&gt; acc)) :: acc
when acc: term()
```

Reduces the RDF data structure using the given function.

Similar to `Enum.reduce/3`, applies `fun` to each statement in the data structure
to produce a single result. This is the user-facing function that hides the
complexity of the protocol's tagged tuple system.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.reduce(graph, 0, fn {_s, _p, _o}, acc -> acc + 1 end)
    2

# `reduce_while`

```elixir
@spec reduce_while(
  RDF.Data.Source.t(),
  acc,
  (RDF.Statement.t(), acc -&gt; {:cont, acc} | {:halt, acc})
) :: acc
when acc: term()
```

Reduces the RDF data structure using the given function with early termination support.

Similar to `Enum.reduce_while/3`, applies `fun` to each statement in the data structure
with the ability to halt iteration early. The function must return:

- `{:cont, acc}` to continue iteration with the new accumulator
- `{:halt, acc}` to stop iteration and return the accumulator

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}, {EX.S3, EX.p, EX.O3}])
    iex> RDF.Data.reduce_while(graph, 0, fn _stmt, acc -> {:cont, acc + 1} end)
    3

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.target, EX.p, EX.O2}])
    iex> RDF.Data.reduce_while(graph, nil, fn {s, _p, _o}, _acc ->
    ...>   if s == ~I<http://example.com/target> do
    ...>     {:halt, s}
    ...>   else
    ...>     {:cont, nil}
    ...>   end
    ...> end)
    ~I<http://example.com/target>

# `reject`

```elixir
@spec reject(RDF.Data.Source.t(), (RDF.Statement.t() -&gt; boolean())) ::
  RDF.Data.Source.t()
```

Rejects statements in the data structure based on a predicate function.

Returns a new data structure of the same type containing only the statements
for which the predicate function returns a falsy value.

This is the complement of `filter/2`.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p1, EX.O1}, {EX.S2, EX.p2, EX.O2}])
    iex> RDF.Data.reject(graph, fn {_s, p, _o} -> p == EX.p1 end)
    RDF.graph([{EX.S2, EX.p2, EX.O2}])

    iex> dataset = RDF.dataset([{EX.S, EX.p1, EX.O, EX.G}, {EX.S, EX.p2, EX.O, nil}])
    iex> RDF.Data.reject(dataset, fn {_s, p, _o, _g} -> p == EX.p1 end)
    RDF.dataset([{EX.S, EX.p2, EX.O}])

# `resources`

```elixir
@spec resources(
  RDF.Data.Source.t(),
  keyword()
  | (RDF.Resource.t() -&gt; boolean())
  | (RDF.Resource.t(), atom() -&gt; boolean())
) :: [RDF.Resource.t()]
```

Returns all unique resources (non-literal terms) in the data structure.

The second argument can be either a keyword list of options or a filter function.

## Options

- `:predicates` - when `true`, includes predicates in the result (default: `false`)
- `:filter` - a filter function (see below)

## Filter function

The filter function can be:

- a 1-arity function receiving only the term
- a 2-arity function receiving `(term, position)` where position is `:subject`,
  `:predicate`, or `:object`

## Examples

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}, {EX.S, EX.p2, "literal"}])
    iex> RDF.Data.resources(graph)
    [~I<http://example.com/O>, ~I<http://example.com/S>]

    iex> graph = RDF.Graph.new([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.resources(graph, predicates: true)
    [~I<http://example.com/O>, ~I<http://example.com/S>, ~I<http://example.com/p>]

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}, {RDF.bnode(), EX.p2, EX.O2}])
    iex> RDF.Data.resources(graph, &RDF.iri?/1) |> MapSet.new()
    MapSet.new([RDF.iri(EX.S), RDF.iri(EX.O), RDF.iri(EX.O2)])

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.resources(graph, fn _term, pos -> pos == :subject end)
    [RDF.iri(EX.S)]

# `statement_count`

```elixir
@spec statement_count(RDF.Data.Source.t()) :: non_neg_integer()
```

Returns the count of statements in the RDF data structure.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.statement_count(graph)
    2

    iex> RDF.Data.statement_count(RDF.dataset())
    0

# `statements`

```elixir
@spec statements(RDF.Data.Source.t()) :: [RDF.Statement.t()]
```

Returns all statements in the data structure.

Extracts all statements as a list. The format depends on the structure type:

- `:description` and `:graph`: Returns triples `{subject, predicate, object}`
- `:dataset`: Returns quads `{subject, predicate, object, graph_name}`

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.statements(graph)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>}]

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.G}])
    iex> RDF.Data.statements(dataset)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>, ~I<http://example.com/G>}]

# `subject_count`

```elixir
@spec subject_count(RDF.Data.Source.t()) :: non_neg_integer()
```

Returns the count of unique subjects in the RDF data structure.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])
    iex> RDF.Data.subject_count(graph)
    2

    iex> RDF.Data.subject_count(EX.S |> EX.p(EX.O))
    1

    iex> RDF.Data.subject_count(RDF.graph())
    0

# `subjects`

```elixir
@spec subjects(RDF.Data.Source.t()) :: [RDF.Resource.t()]
```

Returns all unique subjects in the data structure.

## Examples

    iex> graph = RDF.Graph.new([{EX.S1, EX.p, EX.O}, {EX.S2, EX.p, EX.O}])
    iex> RDF.Data.subjects(graph)
    [~I<http://example.com/S1>, ~I<http://example.com/S2>]

# `subjects`

```elixir
@spec subjects(RDF.Data.Source.t(), (RDF.Resource.t() -&gt; boolean()) | nil) :: [
  RDF.Resource.t()
]
```

Returns all unique subjects in the data structure matching the filter function.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O}, {RDF.bnode(:b), EX.p, EX.O}])
    iex> RDF.Data.subjects(graph, &RDF.iri?/1)
    [RDF.iri(EX.S1)]

# `take`

```elixir
@spec take(RDF.Data.Source.t(), integer()) :: RDF.Data.Source.t()
```

Takes the first `amount` statements from the RDF data structure.

Returns a new data structure of the same type containing at most `amount` statements.
The order of statements is implementation-dependent and should not be relied upon.

For negative amounts, returns an empty data structure.

## Examples

    iex> graph = RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}, {EX.S3, EX.p, EX.O3}])
    iex> RDF.Data.statement_count(RDF.Data.take(graph, 2))
    2

    iex> RDF.Data.take(RDF.graph(), 5)
    RDF.graph()

# `to_dataset`

```elixir
@spec to_dataset(
  RDF.Data.Source.t(),
  keyword()
) :: RDF.Dataset.t() | RDF.Data.Source.t()
```

Converts the RDF data structure to a dataset.

If the data is already a dataset and no options are provided, returns it unchanged.
For graphs, the graph is embedded preserving its name (named or default graph).

## Options

- `:native` - Forces conversion to native `RDF.Dataset` (default: `false`)

## Examples

    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.to_dataset(desc)
    RDF.dataset({EX.S, EX.p(), EX.O})

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}], name: EX.G)
    iex> RDF.Data.to_dataset(graph)
    RDF.dataset({EX.S, EX.p(), EX.O, EX.G})

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.G}])
    iex> RDF.Data.to_dataset(dataset)
    dataset

# `to_graph`

```elixir
@spec to_graph(
  RDF.Data.Source.t(),
  keyword()
) :: RDF.Graph.t() | RDF.Data.Source.t()
```

Converts the RDF data structure to a graph.

If the data is already a graph and no options are provided, returns it unchanged.
For datasets, all graphs are merged into a single graph (graph names are lost).

## Options

- `:native` - Forces conversion to native `RDF.Graph` (default: `false`)

## Examples

    iex> desc = EX.S |> EX.p(EX.O)
    iex> RDF.Data.to_graph(desc)
    RDF.graph({EX.S, EX.p(), EX.O})

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}], name: EX.G)
    iex> RDF.Data.to_graph(graph)
    graph

    iex> dataset = RDF.dataset([{EX.S1, EX.p, EX.O1, EX.G1}, {EX.S2, EX.p, EX.O2, EX.G2}])
    iex> RDF.Data.to_graph(dataset)
    RDF.graph([{EX.S1, EX.p, EX.O1}, {EX.S2, EX.p, EX.O2}])

# `triples`

```elixir
@spec triples(RDF.Data.Source.t()) :: [RDF.Triple.t()]
```

Returns all statements as triples.

Converts all statements to triple format `{subject, predicate, object}`.
For Datasets, this drops the graph component from quads.

## Examples

    iex> graph = RDF.graph([{EX.S, EX.p, EX.O}])
    iex> RDF.Data.triples(graph)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>}]

    iex> dataset = RDF.dataset([{EX.S, EX.p, EX.O, EX.G}])
    iex> RDF.Data.triples(dataset)
    [{~I<http://example.com/S>, EX.p(), ~I<http://example.com/O>}]

---

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