rdf-ex/lib/rdf/graph.ex

701 lines
20 KiB
Elixir
Raw Normal View History

2016-10-15 16:26:56 +00:00
defmodule RDF.Graph do
@moduledoc """
2017-06-16 20:50:56 +00:00
A set of RDF triples with an optional name.
2016-10-15 16:26:56 +00:00
2017-06-16 20:50:56 +00:00
`RDF.Graph` implements:
2018-03-19 00:50:05 +00:00
- Elixir's `Access` behaviour
- Elixir's `Enumerable` protocol
- Elixir's `Inspect` protocol
2017-06-16 20:50:56 +00:00
- the `RDF.Data` protocol
2016-10-15 16:26:56 +00:00
"""
2017-06-16 20:50:56 +00:00
2016-10-15 16:26:56 +00:00
defstruct name: nil, descriptions: %{}
2016-11-27 12:49:42 +00:00
@behaviour Access
alias RDF.Description
import RDF.Statement
2016-10-15 16:26:56 +00:00
@type t :: module
@doc """
Creates an empty unnamed `RDF.Graph`.
2016-10-15 16:26:56 +00:00
"""
def new, do: %RDF.Graph{}
2016-10-15 16:26:56 +00:00
@doc """
Creates an `RDF.Graph`.
If a keyword list is given an empty graph is created.
Otherwise an unnamed graph initialized with the given data is created.
See `new/2` for available arguments and the different ways to provide data.
## Examples
RDF.Graph.new({EX.S, EX.p, EX.O})
2017-02-18 20:35:27 +00:00
RDF.Graph.new(name: EX.GraphName)
"""
def new(data_or_options)
def new(data_or_options)
when is_list(data_or_options) and length(data_or_options) != 0 do
if Keyword.keyword?(data_or_options) do
new([], data_or_options)
else
new(data_or_options, [])
end
end
def new(data), do: new(data, [])
@doc """
Creates an `RDF.Graph` initialized with data.
The initial RDF triples can be provided
- as a single statement tuple
- an `RDF.Description`
- an `RDF.Graph`
- or a list with any combination of the former
Available options:
- `name`: the name of the graph to be created
## Examples
RDF.Graph.new({EX.S, EX.p, EX.O})
RDF.Graph.new({EX.S, EX.p, EX.O}, name: EX.GraphName)
RDF.Graph.new({EX.S, EX.p, [EX.O1, EX.O2]})
RDF.Graph.new([{EX.S1, EX.p1, EX.O1}, {EX.S2, EX.p2, EX.O2}])
RDF.Graph.new(RDF.Description.new(EX.S, EX.P, EX.O))
RDF.Graph.new([graph, description, triple])
"""
def new(data, options)
def new(%RDF.Graph{} = graph, options) do
%RDF.Graph{graph | name: options |> Keyword.get(:name) |> coerce_graph_name()}
end
def new(data, options) do
%RDF.Graph{}
|> new(options)
|> add(data)
end
@doc """
Creates an `RDF.Graph` with initial triples.
See `new/2` for available arguments.
"""
def new(subject, predicate, objects, options \\ []),
do: new([], options) |> add(subject, predicate, objects)
@doc """
Adds triples to a `RDF.Graph`.
"""
def add(%RDF.Graph{} = graph, subject, predicate, objects),
do: add(graph, {subject, predicate, objects})
@doc """
Adds triples to a `RDF.Graph`.
2017-06-02 14:24:15 +00:00
Note: When the statements to be added are given as another `RDF.Graph`,
the graph name must not match graph name of the graph to which the statements
are added. As opposed to that `RDF.Data.merge/2` will produce a `RDF.Dataset`
containing both graphs.
"""
def add(graph, triples)
def add(%RDF.Graph{} = graph, {subject, _, _} = statement),
do: do_add(graph, coerce_subject(subject), statement)
def add(graph, {subject, predicate, object, _}),
do: add(graph, {subject, predicate, object})
def add(graph, triples) when is_list(triples) do
Enum.reduce triples, graph, fn (triple, graph) ->
add(graph, triple)
2016-10-15 16:26:56 +00:00
end
end
def add(%RDF.Graph{} = graph, %Description{subject: subject} = description),
do: do_add(graph, subject, description)
def add(graph, %RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
add(graph, description)
end
end
defp do_add(%RDF.Graph{name: name, descriptions: descriptions},
subject, statements) do
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject, Description.new(statements),
fn description ->
Description.add(description, statements)
end)
}
end
2016-11-26 22:45:41 +00:00
@doc """
Adds statements to a `RDF.Graph` and overwrites all existing statements with the same subjects and predicates.
2017-06-16 22:27:05 +00:00
## Examples
2016-11-26 22:45:41 +00:00
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |>
...> RDF.Graph.put([{EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
2016-11-26 22:45:41 +00:00
"""
def put(graph, statements)
def put(%RDF.Graph{} = graph, {subject, _, _} = statement),
do: do_put(graph, coerce_subject(subject), statement)
2016-11-26 22:45:41 +00:00
def put(graph, {subject, predicate, object, _}),
do: put(graph, {subject, predicate, object})
def put(%RDF.Graph{} = graph, %Description{subject: subject} = description),
do: do_put(graph, subject, description)
def put(graph, %RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
put(graph, description)
end
end
def put(%RDF.Graph{} = graph, statements) when is_map(statements) do
2016-11-26 22:45:41 +00:00
Enum.reduce statements, graph, fn ({subject, predications}, graph) ->
put(graph, subject, predications)
end
end
def put(%RDF.Graph{} = graph, statements) when is_list(statements) do
2016-11-26 22:45:41 +00:00
put(graph, Enum.group_by(statements, &(elem(&1, 0)), fn {_, p, o} -> {p, o} end))
end
@doc """
Add statements to a `RDF.Graph`, overwriting all statements with the same subject and predicate.
"""
def put(graph, subject, predications)
2016-11-26 22:45:41 +00:00
def put(%RDF.Graph{name: name, descriptions: descriptions}, subject, predications)
when is_list(predications) do
with subject = coerce_subject(subject) do
# TODO: Can we reduce this case also to do_put somehow? Only the initializer of Map.update differs ...
2017-03-31 23:13:18 +00:00
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject, Description.new(subject, predications),
fn current ->
Description.put(current, predications)
end)
2017-03-31 23:13:18 +00:00
}
2016-11-26 22:45:41 +00:00
end
end
def put(graph, subject, {_predicate, _objects} = predications),
2016-11-27 12:49:42 +00:00
do: put(graph, subject, [predications])
defp do_put(%RDF.Graph{name: name, descriptions: descriptions},
subject, statements) do
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject, Description.new(statements),
fn current ->
Description.put(current, statements)
end)
}
end
@doc """
Add statements to a `RDF.Graph`, overwriting all statements with the same subject and predicate.
2017-06-16 22:27:05 +00:00
## Examples
iex> RDF.Graph.new(EX.S, EX.P, EX.O1) |> RDF.Graph.put(EX.S, EX.P, EX.O2)
RDF.Graph.new(EX.S, EX.P, EX.O2)
iex> RDF.Graph.new(EX.S, EX.P1, EX.O1) |> RDF.Graph.put(EX.S, EX.P2, EX.O2)
RDF.Graph.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
"""
def put(%RDF.Graph{} = graph, subject, predicate, objects),
do: put(graph, {subject, predicate, objects})
2016-11-27 12:49:42 +00:00
2017-06-02 14:24:15 +00:00
@doc """
Deletes statements from a `RDF.Graph`.
"""
def delete(graph, subject, predicate, object),
do: delete(graph, {subject, predicate, object})
@doc """
Deletes statements from a `RDF.Graph`.
Note: When the statements to be deleted are given as another `RDF.Graph`,
the graph name must not match graph name of the graph from which the statements
are deleted. If you want to delete only graphs with matching names, you can
use `RDF.Data.delete/2`.
2017-06-02 14:24:15 +00:00
"""
def delete(graph, triples)
def delete(%RDF.Graph{} = graph, {subject, _, _} = triple),
do: do_delete(graph, coerce_subject(subject), triple)
def delete(graph, {subject, predicate, object, _}),
do: delete(graph, {subject, predicate, object})
2017-06-02 14:24:15 +00:00
def delete(%RDF.Graph{} = graph, triples) when is_list(triples) do
Enum.reduce triples, graph, fn (triple, graph) ->
delete(graph, triple)
end
end
def delete(%RDF.Graph{} = graph, %Description{subject: subject} = description),
do: do_delete(graph, subject, description)
2017-06-02 14:24:15 +00:00
def delete(%RDF.Graph{} = graph, %RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
delete(graph, description)
end
end
defp do_delete(%RDF.Graph{name: name, descriptions: descriptions} = graph,
subject, statements) do
with description when not is_nil(description) <- descriptions[subject],
2017-06-02 14:24:15 +00:00
new_description = Description.delete(description, statements)
do
%RDF.Graph{name: name,
descriptions:
if Enum.empty?(new_description) do
Map.delete(descriptions, subject)
else
Map.put(descriptions, subject, new_description)
end
}
else
nil -> graph
end
end
2017-06-02 16:22:58 +00:00
@doc """
Deletes all statements with the given subjects.
"""
def delete_subjects(graph, subjects)
def delete_subjects(%RDF.Graph{} = graph, subjects) when is_list(subjects) do
Enum.reduce subjects, graph, fn (subject, graph) ->
delete_subjects(graph, subject)
end
end
def delete_subjects(%RDF.Graph{name: name, descriptions: descriptions}, subject) do
with subject = coerce_subject(subject) do
2017-06-02 16:22:58 +00:00
%RDF.Graph{name: name, descriptions: Map.delete(descriptions, subject)}
end
end
2016-11-27 12:49:42 +00:00
@doc """
Fetches the description of the given subject.
When the subject can not be found `:error` is returned.
2017-06-16 22:27:05 +00:00
## Examples
2016-11-27 12:49:42 +00:00
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |>
...> RDF.Graph.fetch(EX.S1)
{:ok, RDF.Description.new({EX.S1, EX.P1, EX.O1})}
iex> RDF.Graph.fetch(RDF.Graph.new, EX.foo)
:error
2016-11-27 12:49:42 +00:00
"""
2018-09-17 00:08:16 +00:00
@impl Access
2016-11-27 12:49:42 +00:00
def fetch(%RDF.Graph{descriptions: descriptions}, subject) do
Access.fetch(descriptions, coerce_subject(subject))
2016-11-27 12:49:42 +00:00
end
@doc """
Gets the description of the given subject.
When the subject can not be found the optionally given default value or `nil` is returned.
2017-06-16 22:27:05 +00:00
## Examples
2016-11-27 12:49:42 +00:00
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |>
...> RDF.Graph.get(EX.S1)
RDF.Description.new({EX.S1, EX.P1, EX.O1})
iex> RDF.Graph.get(RDF.Graph.new, EX.Foo)
nil
iex> RDF.Graph.get(RDF.Graph.new, EX.Foo, :bar)
:bar
2016-11-27 12:49:42 +00:00
"""
def get(%RDF.Graph{} = graph, subject, default \\ nil) do
2016-11-27 12:49:42 +00:00
case fetch(graph, subject) do
{:ok, value} -> value
:error -> default
end
end
@doc """
The `RDF.Description` of the given subject.
"""
def description(%RDF.Graph{descriptions: descriptions}, subject),
do: Map.get(descriptions, coerce_subject(subject))
2017-07-07 17:03:01 +00:00
@doc """
All `RDF.Description`s within a `RDF.Graph`.
"""
def descriptions(%RDF.Graph{descriptions: descriptions}),
do: Map.values(descriptions)
2016-11-27 12:49:42 +00:00
@doc """
Gets and updates the description of the given subject, in a single pass.
Invokes the passed function on the `RDF.Description` of the given subject;
this function should return either `{description_to_return, new_description}` or `:pop`.
If the passed function returns `{description_to_return, new_description}`, the
return value of `get_and_update` is `{description_to_return, new_graph}` where
`new_graph` is the input `Graph` updated with `new_description` for
the given subject.
If the passed function returns `:pop` the description for the given subject is
removed and a `{removed_description, new_graph}` tuple gets returned.
2017-06-16 22:27:05 +00:00
## Examples
2016-11-27 12:49:42 +00:00
iex> RDF.Graph.new({EX.S, EX.P, EX.O}) |>
...> RDF.Graph.get_and_update(EX.S, fn current_description ->
...> {current_description, {EX.P, EX.NEW}}
...> end)
{RDF.Description.new(EX.S, EX.P, EX.O), RDF.Graph.new(EX.S, EX.P, EX.NEW)}
2016-11-27 12:49:42 +00:00
"""
2018-09-17 00:08:16 +00:00
@impl Access
def get_and_update(%RDF.Graph{} = graph, subject, fun) do
with subject = coerce_subject(subject) do
case fun.(get(graph, subject)) do
2016-11-27 12:49:42 +00:00
{old_description, new_description} ->
{old_description, put(graph, subject, new_description)}
2017-02-18 20:35:27 +00:00
:pop ->
pop(graph, subject)
2017-02-18 20:35:27 +00:00
other ->
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
2016-11-27 12:49:42 +00:00
end
end
end
@doc """
Pops an arbitrary triple from a `RDF.Graph`.
"""
def pop(graph)
def pop(%RDF.Graph{descriptions: descriptions} = graph)
when descriptions == %{}, do: {nil, graph}
def pop(%RDF.Graph{name: name, descriptions: descriptions}) do
# TODO: Find a faster way ...
[{subject, description}] = Enum.take(descriptions, 1)
{triple, popped_description} = Description.pop(description)
popped = if Enum.empty?(popped_description),
do: descriptions |> Map.delete(subject),
else: descriptions |> Map.put(subject, popped_description)
{triple, %RDF.Graph{name: name, descriptions: popped}}
end
2016-11-27 12:49:42 +00:00
@doc """
Pops the description of the given subject.
When the subject can not be found the optionally given default value or `nil` is returned.
2017-06-16 22:27:05 +00:00
## Examples
2016-11-27 12:49:42 +00:00
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |>
...> RDF.Graph.pop(EX.S1)
{RDF.Description.new({EX.S1, EX.P1, EX.O1}), RDF.Graph.new({EX.S2, EX.P2, EX.O2})}
iex> RDF.Graph.pop(RDF.Graph.new({EX.S, EX.P, EX.O}), EX.Missing)
{nil, RDF.Graph.new({EX.S, EX.P, EX.O})}
2016-11-27 12:49:42 +00:00
"""
2018-09-17 00:08:16 +00:00
@impl Access
def pop(%RDF.Graph{name: name, descriptions: descriptions} = graph, subject) do
case Access.pop(descriptions, coerce_subject(subject)) do
2016-11-27 12:49:42 +00:00
{nil, _} ->
{nil, graph}
{description, new_descriptions} ->
{description, %RDF.Graph{name: name, descriptions: new_descriptions}}
end
end
2016-11-26 22:45:41 +00:00
2017-02-18 20:35:27 +00:00
@doc """
The number of subjects within a `RDF.Graph`.
2017-06-16 22:27:05 +00:00
## Examples
2017-02-18 20:35:27 +00:00
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S1, EX.p2, EX.O3}]) |>
...> RDF.Graph.subject_count
2
2017-02-18 20:35:27 +00:00
"""
def subject_count(%RDF.Graph{descriptions: descriptions}),
do: Enum.count(descriptions)
@doc """
The number of statements within a `RDF.Graph`.
2017-06-16 22:27:05 +00:00
## Examples
2016-10-15 16:26:56 +00:00
2017-02-18 20:35:27 +00:00
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S1, EX.p2, EX.O3}]) |>
...> RDF.Graph.triple_count
3
2017-02-18 20:35:27 +00:00
"""
2016-10-15 16:26:56 +00:00
def triple_count(%RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, 0, fn ({_subject, description}, count) ->
count + Description.count(description)
end
end
@doc """
2017-02-18 20:35:27 +00:00
The set of all subjects used in the statements within a `RDF.Graph`.
2017-06-16 22:27:05 +00:00
## Examples
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S1, EX.p2, EX.O3}]) |>
...> RDF.Graph.subjects
MapSet.new([RDF.iri(EX.S1), RDF.iri(EX.S2)])
"""
def subjects(%RDF.Graph{descriptions: descriptions}),
do: descriptions |> Map.keys |> MapSet.new
@doc """
2017-02-18 20:35:27 +00:00
The set of all properties used in the predicates of the statements within a `RDF.Graph`.
2017-06-16 22:27:05 +00:00
## Examples
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S1, EX.p2, EX.O3}]) |>
...> RDF.Graph.predicates
MapSet.new([EX.p1, EX.p2])
"""
def predicates(%RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) ->
description
|> Description.predicates
|> MapSet.union(acc)
end
end
@doc """
The set of all resources used in the objects within a `RDF.Graph`.
Note: This function does collect only IRIs and BlankNodes, not Literals.
2017-06-16 22:27:05 +00:00
## Examples
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S3, EX.p1, EX.O2},
...> {EX.S4, EX.p2, RDF.bnode(:bnode)},
...> {EX.S5, EX.p3, "foo"}
...> ]) |> RDF.Graph.objects
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)])
"""
def objects(%RDF.Graph{descriptions: descriptions}) do
Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) ->
description
|> Description.objects
|> MapSet.union(acc)
end
end
@doc """
The set of all resources used within a `RDF.Graph`.
2017-06-16 22:27:05 +00:00
## Examples
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p1, EX.O2},
...> {EX.S2, EX.p2, RDF.bnode(:bnode)},
...> {EX.S3, EX.p1, "foo"}
...> ]) |> RDF.Graph.resources
MapSet.new([RDF.iri(EX.S1), RDF.iri(EX.S2), RDF.iri(EX.S3),
RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2])
"""
def resources(graph = %RDF.Graph{descriptions: descriptions}) do
Enum.reduce(descriptions, MapSet.new, fn ({_, description}, acc) ->
description
|> Description.resources
|> MapSet.union(acc)
end) |> MapSet.union(subjects(graph))
end
2017-02-18 20:35:27 +00:00
@doc """
The list of all statements within a `RDF.Graph`.
2017-02-18 20:35:27 +00:00
2017-06-16 22:27:05 +00:00
## Examples
2017-02-18 20:35:27 +00:00
iex> RDF.Graph.new([
...> {EX.S1, EX.p1, EX.O1},
...> {EX.S2, EX.p2, EX.O2},
...> {EX.S1, EX.p2, EX.O3}
...> ]) |> RDF.Graph.triples
[{RDF.iri(EX.S1), RDF.iri(EX.p1), RDF.iri(EX.O1)},
{RDF.iri(EX.S1), RDF.iri(EX.p2), RDF.iri(EX.O3)},
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
2017-02-18 20:35:27 +00:00
"""
def triples(graph = %RDF.Graph{}), do: Enum.to_list(graph)
defdelegate statements(graph), to: RDF.Graph, as: :triples
@doc """
Checks if the given statement exists within a `RDF.Graph`.
"""
2016-10-15 16:26:56 +00:00
def include?(%RDF.Graph{descriptions: descriptions},
triple = {subject, _, _}) do
with subject = coerce_subject(subject),
%Description{} <- description = descriptions[subject] do
2016-10-15 16:26:56 +00:00
Description.include?(description, triple)
else
_ -> false
end
end
@doc """
Checks if a `RDF.Graph` contains statements about the given resource.
## Examples
iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S1)
true
iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S2)
false
"""
def describes?(%RDF.Graph{descriptions: descriptions}, subject) do
with subject = coerce_subject(subject) do
Map.has_key?(descriptions, subject)
end
end
2016-10-15 16:26:56 +00:00
@doc """
Returns a nested map of the native Elixir values of a `RDF.Graph`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate` or `:object`,
while `rdf_term` is the RDF term to be mapped.
## Examples
iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)}
...> ]
...> |> RDF.Graph.new()
...> |> RDF.Graph.values()
%{
"http://example.com/S1" => %{"http://example.com/p" => ["Foo"]},
"http://example.com/S2" => %{"http://example.com/p" => [42]}
}
iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)}
...> ]
...> |> RDF.Graph.new()
...> |> RDF.Graph.values(fn
...> {:predicate, predicate} ->
...> predicate
...> |> to_string()
...> |> String.split("/")
...> |> List.last()
...> |> String.to_atom()
...> {_, term} ->
...> RDF.Term.value(term)
...> end)
%{
"http://example.com/S1" => %{p: ["Foo"]},
"http://example.com/S2" => %{p: [42]}
}
"""
def values(graph, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Graph{descriptions: descriptions}, mapping) do
Map.new descriptions, fn {subject, description} ->
{mapping.({:subject, subject}), Description.values(description, mapping)}
end
end
defimpl Enumerable do
def member?(graph, triple), do: {:ok, RDF.Graph.include?(graph, triple)}
def count(graph), do: {:ok, RDF.Graph.triple_count(graph)}
def slice(_graph), do: {:error, __MODULE__}
2016-10-15 16:26:56 +00:00
def reduce(%RDF.Graph{descriptions: descriptions}, {:cont, acc}, _fun)
when map_size(descriptions) == 0, do: {:done, acc}
2016-10-15 16:26:56 +00:00
def reduce(%RDF.Graph{} = graph, {:cont, acc}, fun) do
{triple, rest} = RDF.Graph.pop(graph)
reduce(rest, fun.(triple, acc), fun)
end
2016-10-15 16:26:56 +00:00
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(%RDF.Graph{} = graph, {:suspend, acc}, fun) do
{:suspended, acc, &reduce(graph, &1, fun)}
end
2016-10-15 16:26:56 +00:00
end
defimpl Collectable do
def into(original) do
collector_fun = fn
graph, {:cont, list} when is_list(list)
-> RDF.Graph.add(graph, List.to_tuple(list))
graph, {:cont, elem} -> RDF.Graph.add(graph, elem)
graph, :done -> graph
_graph, :halt -> :ok
end
{original, collector_fun}
end
end
2016-10-15 16:26:56 +00:00
end