ruby-rdf/rdf

View on GitHub
lib/rdf/model/list.rb

Summary

Maintainability
F
3 days
Test Coverage
# coding: utf-8
module RDF
  ##
  # An RDF list.
  #
  # @example Constructing a new list
  #   RDF::List[1, 2, 3]
  #
  # @since 0.2.3
  class RDF::List
    include RDF::Enumerable
    include RDF::Value
    include Comparable

    ##
    # Constructs a new list from the given `values`.
    #
    # The list will be identified by a new autogenerated blank node, and
    # backed by an initially empty in-memory graph.
    #
    # @example
    #     RDF::List[]
    #     RDF::List[*(1..10)]
    #     RDF::List[1, 2, 3]
    #     RDF::List["foo", "bar"]
    #     RDF::List["a", 1, "b", 2, "c", 3]
    #
    # @param  [Array<RDF::Term>] values
    # @return [RDF::List]
    def self.[](*values)
      self.new(subject: nil, graph: nil, values: values)
    end

    ##
    # Initializes a newly-constructed list.
    #
    # Instantiates a new list based at `subject`, which **should** be an RDF::Node. List may be initialized using passed `values`.
    #
    # If a `values` initializer is set with an empty list, `subject`
    # will be used as the first element in the list. Otherwise,
    # if the list is not empty, `subject` identifies the first element
    # of the list to which `values` are prepended yielding a new `subject`.
    # Otherwise, if there are no initial `values`, and `subject` does
    # not identify an existing list in `graph`, the list remains
    # identified by `subject`, but will be invalid.
    #
    # @example add constructed list to existing graph
    #     l = RDF::List(values: (1, 2, 3))
    #     g = RDF::Graph.new << l
    #     g.count # => l.count
    #
    # @example use a transaction for block initialization
    #     l = RDF::List(graph: graph, wrap_transaction: true) do |list|
    #       list << RDF::Literal(1)
    #       # list.graph.rollback will rollback all list changes within this block.
    #     end
    #     list.count #=> 1
    #
    # @param  [RDF::Resource]         subject (RDF.nil)
    #   Subject should be an {RDF::Node}, not a {RDF::URI}. A list with an IRI head will not validate, but is commonly used to detect if a list is valid.
    # @param  [RDF::Graph]        graph (RDF::Graph.new)
    # @param  [Array<RDF::Term>]  values
    #   Any values which are not terms are coerced to `RDF::Literal`.
    # @param [Boolean] wrap_transaction (false)
    #   Wraps the callback in a transaction, and replaces the graph with that transaction for the duraction of the callback. This has the effect of allowing any list changes to be made atomically, or rolled back.
    # @yield  [list]
    # @yieldparam [RDF::List] list
    def initialize(subject: nil, graph: nil, values: nil, wrap_transaction: false, &block)
      @subject = subject || RDF.nil
      @graph   = graph   || RDF::Graph.new
      is_empty = @graph.query({subject: subject, predicate: RDF.first}).empty?

      if subject && is_empty
        # An empty list with explicit subject and value initializers
        @subject = RDF.nil
        first, *values = Array(values)
        if first || values.length > 0
          # Intantiate the list from values, and insert the first value using subject.
          values.reverse_each {|value| self.unshift(value)}
          @graph.insert RDF::Statement(subject, RDF.first, first || RDF.nil)
          @graph.insert RDF::Statement(subject, RDF.rest, @subject)
        end
        @subject = subject
      else
        # Otherwise, prepend any values, which resets @subject
        Array(values).reverse_each {|value| self.unshift(value)}
      end

      if block_given?
        if wrap_transaction
          old_graph = @graph
          begin
            Transaction.begin(@graph, graph_name: @graph.graph_name, mutable: @graph.mutable?) do |trans|
              @graph = trans
              case block.arity
                when 1 then block.call(self)
                else instance_eval(&block)
              end
              trans.execute if trans.mutated?
            end
          ensure
            @graph = old_graph
          end
        else
          case block.arity
            when 1 then block.call(self)
            else instance_eval(&block)
          end
        end
      end
    end

    UNSET = Object.new.freeze # @private

    # The canonical empty list.
    NIL = RDF::List.new(subject: RDF.nil).freeze

    ##
    # Is this a {RDF::List}?
    #
    # @return [Boolean]
    def list?
      true
    end

    ##
    # Validate the list ensuring that
    # * each node is referenced exactly once (except for the head, which may have no reference)
    # * rdf:rest values are all BNodes are nil
    # * each subject has exactly one value for `rdf:first` and
    #   `rdf:rest`.
    # * The value of `rdf:rest` must be either a BNode or `rdf:nil`.
    # * only the list head may have any other properties
    # @return [Boolean]
    def valid?
      li = subject
      list_nodes = []
      while li != RDF.nil do
        return false if list_nodes.include?(li)
        list_nodes << li
        rest = nil
        firsts = rests = 0
        @graph.query({subject: li}) do |st|
          return false unless st.subject.node?
          case st.predicate
          when RDF.first
            firsts += 1
          when RDF.rest
            rest = st.object
            return false unless rest.node? || rest == RDF.nil
            rests += 1
          when RDF.type
          else
            # It may have no other properties
            return false unless li == subject
          end
        end
        return false unless firsts == 1 && rests == 1
        li = rest
      end

      # All elements other than the head must be referenced exactly once
      return list_nodes.all? do |li|
        refs = @graph.query({object: li}).count
        case refs
        when 0 then li == subject
        when 1 then true
        else        false
        end
      end
    end

    # @!attribute [r] subject
    # @return [RDF::Resource] the subject term of this list.
    attr_reader :subject

    # @!attribute [r] graph
    # @return [RDF::Graph] the underlying graph storing the statements that constitute this list
    attr_reader :graph

    ##
    # @see RDF::Value#==
    def ==(other)
      return false if other.is_a?(RDF::Value) && !other.list?
      super
    end

    ##
    # Returns the set intersection of this list and `other`.
    #
    # The resulting list contains the elements common to both lists, with no
    # duplicates.
    #
    # @example
    #   RDF::List[1, 2] & RDF::List[1, 2]       #=> RDF::List[1, 2]
    #   RDF::List[1, 2] & RDF::List[2, 3]       #=> RDF::List[2]
    #   RDF::List[1, 2] & RDF::List[3, 4]       #=> RDF::List[]
    #
    # @param  [RDF::List] other
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-26
    def &(other)
      self.class.new(values: (to_a & other.to_a))
    end

    ##
    # Returns the set union of this list and `other`.
    #
    # The resulting list contains the elements from both lists, with no
    # duplicates.
    #
    # @example
    #   RDF::List[1, 2] | RDF::List[1, 2]       #=> RDF::List[1, 2]
    #   RDF::List[1, 2] | RDF::List[2, 3]       #=> RDF::List[1, 2, 3]
    #   RDF::List[1, 2] | RDF::List[3, 4]       #=> RDF::List[1, 2, 3, 4]
    #
    # @param  [RDF::List] other
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-7C
    def |(other)
      self.class.new(values: (to_a | other.to_a))
    end

    ##
    # Returns the concatenation of this list and `other`.
    #
    # @example
    #   RDF::List[1, 2] + RDF::List[3, 4]       #=> RDF::List[1, 2, 3, 4]
    #
    # @param  [RDF::List] other
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-2B
    def +(other)
      self.class.new(values: (to_a + other.to_a))
    end

    ##
    # Returns the difference between this list and `other`, removing any
    # elements that appear in both lists.
    #
    # @example
    #   RDF::List[1, 2, 2, 3] - RDF::List[2]    #=> RDF::List[1, 3]
    #
    # @param  [RDF::List] other
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-2D
    def -(other)
      self.class.new(values: (to_a - other.to_a))
    end

    ##
    # Returns either a repeated list or a string concatenation of the
    # elements in this list.
    #
    # @overload *(times)
    #   Returns a new list built of `times` repetitions of this list.
    #
    #   @example
    #     RDF::List[1, 2, 3] * 2                #=> RDF::List[1, 2, 3, 1, 2, 3]
    #
    #   @param  [Integer] times
    #   @return [RDF::List]
    #
    # @overload *(sep)
    #   Returns the string concatenation of the elements in this list
    #   separated by `sep`. Equivalent to `self.join(sep)`.
    #
    #   @example
    #     RDF::List[1, 2, 3] * ","              #=> "1,2,3"
    #
    #   @param  [String, #to_s] sep
    #   @return [RDF::List]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-2A
    def *(int_or_str)
      case int_or_str
        when Integer then self.class.new(values: (to_a * int_or_str))
        else join(int_or_str.to_s)
      end
    end

    ##
    # Element Assignment — Sets the element at `index`, or replaces a subarray from the `start` index for `length` elements, or replaces a subarray specified by the `range` of indices.
    #
    # If indices are greater than the current capacity of the array, the array grows automatically. Elements are inserted into the array at `start` if length is zero.
    #
    # Negative indices will count backward from the end of the array. For `start` and `range` cases the starting index is just before an element.
    #
    # An `IndexError` is raised if a negative index points past the beginning of the array.
    #
    # (see #unshift).
    #
    # @example
    #     a = RDF::List.new
    #     a[4] = "4";                 #=> [rdf:nil, rdf:nil, rdf:nil, rdf:nil, "4"]
    #     a[0, 3] = [ 'a', 'b', 'c' ] #=> ["a", "b", "c", rdf:nil, "4"]
    #     a[1..2] = [ 1, 2 ]          #=> ["a", 1, 2, rdf:nil, "4"]
    #     a[0, 2] = "?"               #=> ["?", 2, rdf:nil, "4"]
    #     a[0..2] = "A"               #=> ["A", "4"]
    #     a[-1]   = "Z"               #=> ["A", "Z"]
    #     a[1..-1] = nil              #=> ["A", rdf:nil]
    #     a[1..-1] = []               #=> ["A"]
    #     a[0, 0] = [ 1, 2 ]          #=> [1, 2, "A"]
    #     a[3, 0] = "B"               #=> [1, 2, "A", "B"]
    #
    # @overload []=(index, term)
    #   Replaces the element at `index` with `term`.
    #   @param [Integer] index
    #   @param [RDF::Term] term
    #     A non-RDF::Term is coerced to a Literal.
    #   @return [RDF::Term]
    #   @raise [IndexError]
    #
    # @overload []=(start, length, value)
    #   Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}.
    #   @param [Integer] start
    #   @param [Integer] length
    #   @param [RDF::Term, Array<RDF::Term>, RDF::List] value
    #     A non-RDF::Term is coerced to a Literal.
    #   @return [RDF::Term, RDF::List]
    #   @raise [IndexError]
    #
    # @overload []=(range, value)
    #   Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}.
    #   @param [Range] range
    #   @param [RDF::Term, Array<RDF::Term>, RDF::List] value
    #     A non-RDF::Term is coerced to a Literal.
    #   @return [RDF::Term, RDF::List]
    #   @raise [IndexError]
    # @since 1.1.15
    def []=(*args)
      start, length = 0, 0

      ary = self.to_a

      value = case args.last
      when Array then args.last
      when RDF::List then args.last.to_a
      else [args.last]
      end

      ret = case args.length
      when 3
        start, length = args[0], args[1]
        ary[start, length] = value
      when 2
        case args.first
        when Integer
          raise ArgumentError, "Index form of []= takes a single term" if args.last.is_a?(Array)
          ary[args.first] = args.last.is_a?(RDF::List) ? args.last.subject : args.last
        when Range
          ary[args.first] = value
        else
          raise ArgumentError, "Index form of must use an integer or range"
        end
      else
        raise ArgumentError, "List []= takes one or two index values"
      end

      # Clear the list and create a new list using the existing subject
      subject = @subject unless ary.empty? || @subject == RDF.nil
      self.clear
      new_list = RDF::List.new(subject: subject, graph: @graph, values: ary)
      @subject = new_list.subject
      ret # Returns inserted values
    end

    ##
    # Appends an element to the head of this list. Existing references are not updated, as the list subject changes as a side-effect.
    #
    # @example
    #   RDF::List[].unshift(1).unshift(2).unshift(3) #=> RDF::List[3, 2, 1]
    #
    # @param  [RDF::Term, Array<RDF::Term>, RDF::List] value
    #   A non-RDF::Term is coerced to a Literal
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-unshift
    #
    def unshift(value)
      value = normalize_value(value)

      new_subject, old_subject = RDF::Node.new, subject

      graph.insert([new_subject, RDF.first, value.is_a?(RDF::List) ? value.subject : value])
      graph.insert([new_subject, RDF.rest, old_subject])

      @subject = new_subject

      return self
    end

    ##
    # Removes and returns the element at the head of this list.
    #
    # @example
    #   RDF::List[1,2,3].shift              #=> 1
    #
    # @return [RDF::Term]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-shift
    def shift
      return nil if empty?

      value = first
      old_subject, new_subject = subject, rest_subject
      graph.delete([old_subject, RDF.type, RDF.List])
      graph.delete([old_subject, RDF.first, value])
      graph.delete([old_subject, RDF.rest, new_subject])

      @subject = new_subject
      return value
    end

    ##
    # Empties this list
    #
    # @example
    #   RDF::List[1, 2, 2, 3].clear    #=> RDF::List[]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-clear
    def clear
      until empty?
        shift
      end
      return self
    end

    ##
    # Appends an element to the tail of this list.
    #
    # @example
    #   RDF::List[] << 1 << 2 << 3              #=> RDF::List[1, 2, 3]
    #
    # @param  [RDF::Term] value
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-3C-3C
    def <<(value)
      value = normalize_value(value)

      if empty?
        @subject = new_subject = RDF::Node.new
      else
        old_subject, new_subject = last_subject, RDF::Node.new
        graph.delete([old_subject, RDF.rest, RDF.nil])
        graph.insert([old_subject, RDF.rest, new_subject])
      end

      graph.insert([new_subject, RDF.first, value.is_a?(RDF::List) ? value.subject : value])
      graph.insert([new_subject, RDF.rest, RDF.nil])

      self
    end

    ##
    # Compares this list to `other` using eql? on each component.
    #
    # @example
    #   RDF::List[1, 2, 3].eql? RDF::List[1, 2, 3]  #=> true
    #   RDF::List[1, 2, 3].eql? [1, 2, 3]           #=> true
    #
    # @param  [RDF::List] other
    # @return [Integer]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-3C-3D-3E
    def eql?(other)
      to_a.eql? Array(other)
    end

    ##
    # Compares this list to `other` for sorting purposes.
    #
    # @example
    #   RDF::List[1] <=> RDF::List[1]           #=> 0
    #   RDF::List[1] <=> RDF::List[2]           #=> -1
    #   RDF::List[2] <=> RDF::List[1]           #=> 1
    #
    # @param  [RDF::List] other
    # @return [Integer]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-3C-3D-3E
    def <=>(other)
      to_a <=> Array(other)
    end

    ##
    # Returns `true` if this list is empty.
    #
    # @example
    #   RDF::List[].empty?                      #=> true
    #   RDF::List[1, 2, 3].empty?               #=> false
    #
    # @return [Boolean]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-empty-3F
    def empty?
      graph.query({subject: subject, predicate: RDF.first}).empty?
    end

    ##
    # Returns the length of this list.
    #
    # @example
    #   RDF::List[].length                      #=> 0
    #   RDF::List[1, 2, 3].length               #=> 3
    #
    # @return [Integer]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-length
    def length
      each.count
    end

    alias_method :size, :length

    ##
    # Returns the index of the first element equal to `value`, or `nil` if
    # no match was found.
    #
    # @example
    #   RDF::List['a', 'b', 'c'].index('a')     #=> 0
    #   RDF::List['a', 'b', 'c'].index('d')     #=> nil
    #
    # @param  [RDF::Term] value
    # @return [Integer]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-index
    def index(value)
      each.with_index do |v, i|
        return i if v == value
      end
      return nil
    end

    ##
    # Returns a slice of a list.
    #
    # @example
    #     RDF::List[1, 2, 3].slice(0)    #=> RDF::Literal(1),
    #     RDF::List[1, 2, 3].slice(0, 2) #=> RDF::List[1, 2],
    #     RDF::List[1, 2, 3].slice(0..2) #=> RDF::List[1, 2, 3]
    #
    # @return [RDF::Term]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-slice
    def slice(*args)
      case argc = args.size
        when 2 then slice_with_start_and_length(*args)
        when 1 then (arg = args.first).is_a?(Range) ? slice_with_range(arg) : at(arg)
        when 0 then raise ArgumentError, "wrong number of arguments (0 for 1)"
        else raise ArgumentError, "wrong number of arguments (#{argc} for 2)"
      end
    end
    alias :[] :slice

    ##
    # @private
    def slice_with_start_and_length(start, length)
      self.class.new(values: to_a.slice(start, length))
    end

    ##
    # @private
    def slice_with_range(range)
      self.class.new(values: to_a.slice(range))
    end

    protected :slice_with_start_and_length
    protected :slice_with_range

    ##
    # Returns element at `index` with default.
    #
    # @example
    #   RDF::List[1, 2, 3].fetch(0)             #=> RDF::Literal(1)
    #   RDF::List[1, 2, 3].fetch(4)             #=> IndexError
    #   RDF::List[1, 2, 3].fetch(4, nil)        #=> nil
    #   RDF::List[1, 2, 3].fetch(4) { |n| n*n } #=> 16
    #
    # @return [RDF::Term, nil]
    # @see    http://ruby-doc.org/core-1.9/classes/Array.html#M000420
    def fetch(index, default = UNSET)
      val = at(index)
      return val unless val.nil?

      case
        when block_given?         then yield index
        when !default.eql?(UNSET) then default
        else raise IndexError, "index #{index} not in the list #{self.inspect}"
      end
    end

    ##
    # Returns the element at `index`.
    #
    # @example
    #   RDF::List[1, 2, 3].at(0)                #=> 1
    #   RDF::List[1, 2, 3].at(4)                #=> nil
    #
    # @return [RDF::Term, nil]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-at
    def at(index)
      each.with_index { |v, i| return v if i == index }
      return nil
    end

    alias_method :nth, :at

    ##
    # Returns the first element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].first               #=> RDF::Literal(1)
    #
    # @return [RDF::Term]
    def first
      graph.first_object(subject: first_subject, predicate: RDF.first)
    end

    ##
    # Returns the second element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].second              #=> RDF::Literal(2)
    #
    # @return [RDF::Term]
    def second
      at(1)
    end

    ##
    # Returns the third element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].third               #=> RDF::Literal(4)
    #
    # @return [RDF::Term]
    def third
      at(2)
    end

    ##
    # Returns the fourth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].fourth              #=> RDF::Literal(4)
    #
    # @return [RDF::Term]
    def fourth
      at(3)
    end

    ##
    # Returns the fifth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].fifth               #=> RDF::Literal(5)
    #
    # @return [RDF::Term]
    def fifth
      at(4)
    end

    ##
    # Returns the sixth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].sixth               #=> RDF::Literal(6)
    #
    # @return [RDF::Term]
    def sixth
      at(5)
    end

    ##
    # Returns the seventh element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].seventh             #=> RDF::Literal(7)
    #
    # @return [RDF::Term]
    def seventh
      at(6)
    end

    ##
    # Returns the eighth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].eighth              #=> RDF::Literal(8)
    #
    # @return [RDF::Term]
    def eighth
      at(7)
    end

    ##
    # Returns the ninth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].ninth               #=> RDF::Literal(9)
    #
    # @return [RDF::Term]
    def ninth
      at(8)
    end

    ##
    # Returns the tenth element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].tenth               #=> RDF::Literal(10)
    #
    # @return [RDF::Term]
    def tenth
      at(9)
    end

    ##
    # Returns the last element in this list.
    #
    # @example
    #   RDF::List[*(1..10)].last                 #=> RDF::Literal(10)
    #
    # @return [RDF::Term]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-last
    def last
      graph.first_object(subject: last_subject, predicate: RDF.first)
    end

    ##
    # Returns a list containing all but the first element of this list.
    #
    # @example
    #   RDF::List[1, 2, 3].rest                 #=> RDF::List[2, 3]
    #
    # @return [RDF::List]
    def rest
      (subject = rest_subject).eql?(RDF.nil) ? nil : self.class.new(subject: subject, graph: graph)
    end

    ##
    # Returns a list containing the last element of this list.
    #
    # @example
    #   RDF::List[1, 2, 3].tail                 #=> RDF::List[3]
    #
    # @return [RDF::List]
    def tail
      (subject = last_subject).eql?(RDF.nil) ? nil : self.class.new(subject: subject, graph: graph)
    end

    ##
    # Returns the first subject term constituting this list.
    #
    # This is equivalent to `subject`.
    #
    # @example
    #   RDF::List[1, 2, 3].first_subject        #=> RDF::Node(...)
    #
    # @return [RDF::Resource]
    def first_subject
      subject
    end

    ##
    # @example
    #   RDF::List[1, 2, 3].rest_subject         #=> RDF::Node(...)
    #
    # @return [RDF::Resource]
    def rest_subject
      graph.first_object(subject: subject, predicate: RDF.rest)
    end

    ##
    # Returns the last subject term constituting this list.
    #
    # @example
    #   RDF::List[1, 2, 3].last_subject         #=> RDF::Node(...)
    #
    # @return [RDF::Resource]
    def last_subject
      each_subject.to_a.last # TODO: optimize this
    end

    ##
    # Yields each subject term constituting this list.
    #
    # @example
    #   RDF::List[1, 2, 3].each_subject do |subject|
    #     puts subject.inspect
    #   end
    #
    # @return [Enumerator]
    # @see    RDF::Enumerable#each
    def each_subject
      return enum_subject unless block_given?

      subject = self.subject
      yield subject

      loop do
        rest = graph.first_object(subject: subject, predicate: RDF.rest)
        break if rest.nil? || rest.eql?(RDF.nil)
        yield subject = rest
      end
    end

    ##
    # Yields each element in this list.
    #
    # @example
    #   RDF::List[1, 2, 3].each do |value|
    #     puts value.inspect
    #   end
    #
    # @return [Enumerator]
    # @see    http://ruby-doc.org/core-1.9/classes/Enumerable.html
    def each
      return to_enum unless block_given?

      each_subject do |subject|
        if value = graph.first_object(subject: subject, predicate: RDF.first)
          yield value # FIXME
        end
      end
    end

    ##
    # Yields each statement constituting this list.
    #
    # @example
    #   RDF::List[1, 2, 3].each_statement do |statement|
    #     puts statement.inspect
    #   end
    #
    # @return [Enumerator]
    # @see    RDF::Enumerable#each_statement
    def each_statement(&block)
      return enum_statement unless block_given?

      each_subject do |subject|
        graph.query({subject: subject}, &block)
      end
    end
    alias_method :to_rdf, :each_statement

    ##
    # Returns a string created by converting each element of this list into
    # a string, separated by `sep`.
    #
    # @example
    #   RDF::List[1, 2, 3].join                 #=> "123"
    #   RDF::List[1, 2, 3].join(", ")           #=> "1, 2, 3"
    #
    # @param  [String] sep
    # @return [String]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-join
    def join(sep = $,)
      map(&:to_s).join(sep)
    end

    ##
    # Returns the elements in this list in reversed order.
    #
    # @example
    #   RDF::List[1, 2, 3].reverse              #=> RDF::List[3, 2, 1]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-reverse
    def reverse
      self.class.new(values: to_a.reverse)
    end

    ##
    # Returns the elements in this list in sorted order.
    #
    # @example
    #   RDF::List[2, 3, 1].sort                 #=> RDF::List[1, 2, 3]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-sort
    def sort(&block)
      self.class.new(values: super)
    end

    ##
    # Returns the elements in this list in sorted order.
    #
    # @example
    #   RDF::List[2, 3, 1].sort_by(&:to_i)      #=> RDF::List[1, 2, 3]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-sort_by
    def sort_by(&block)
      self.class.new(values: super)
    end

    ##
    # Returns a new list with the duplicates in this list removed.
    #
    # @example
    #   RDF::List[1, 2, 2, 3].uniq              #=> RDF::List[1, 2, 3]
    #
    # @return [RDF::List]
    # @see    http://ruby-doc.org/core-2.2.2/Array.html#method-i-uniq
    def uniq
      self.class.new(values: to_a.uniq)
    end

    ##
    # Returns the elements in this list as an array.
    #
    # @example
    #   RDF::List[].to_a                        #=> []
    #   RDF::List[1, 2, 3].to_a                 #=> [RDF::Literal(1), RDF::Literal(2), RDF::Literal(3)]
    #
    # @return [Array]
    def to_a
      each.to_a
    end

    ##
    # Returns the elements in this list as a set.
    #
    # @example
    #   RDF::List[1, 2, 3].to_set               #=> Set[RDF::Literal(1), RDF::Literal(2), RDF::Literal(3)]
    #
    # @return [Set]
    def to_set
      require 'set' unless defined?(::Set)
      each.to_set
    end

    ##
    # Returns the subject of the list.
    #
    # @example
    #   RDF::List[].to_term                     #=> "RDF[:nil]"
    #   RDF::List[1, 2, 3].to_term              #=> "RDF::Node"
    #
    # @return [RDF::Resource]
    def to_term
      subject
    end

    ##
    # Returns a string representation of this list.
    #
    # @example
    #   RDF::List[].to_s                        #=> "RDF::List[]"
    #   RDF::List[1, 2, 3].to_s                 #=> "RDF::List[1, 2, 3]"
    #
    # @return [String]
    def to_s
      'RDF::List[' + join(', ') + ']'
    end

    ##
    # Returns a developer-friendly representation of this list.
    #
    # @example
    #   RDF::List[].inspect                     #=> "#<RDF::List(_:g2163790380)>"
    #
    # @return [String]
    def inspect
      if self.equal?(NIL)
        'RDF::List::NIL'
      else
        sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, join(', '))
      end
    end

    private

    ##
    # Normalizes `Array` to `RDF::List` and `nil` to `RDF.nil`.
    #
    # @param value [Object]
    # @return [RDF::Value, Object] normalized value
    def normalize_value(value)
      case value
        when nil         then RDF.nil
        when RDF::Value  then value
        when Array       then self.class.new(subject: nil, graph: graph, values: value)
        else value
      end
    end
  end
end