sophia_api/
triple.rs

1//! I define how [RDF triples](https://www.w3.org/TR/rdf11-concepts/#section-triples)
2//! are represented in Sophia.
3//!
4//! I provide the main trait [`Triple`].
5//!
6//! An RDF triple expresses a single fact.
7//! Its formed of three terms called *subject*, *predicate* and *object*.
8//!
9//! You can think of a triple as a sentence of the form
10//! "subject verb complement"
11//! (although the *predicate* is often better expressed as a relationship than a verb).
12//! Examples :
13//!
14//! * John is a person.
15//! * John was born in Paris.
16//! * John knows Jane.
17//! * John's family name is "Doe".
18//!
19use crate::quad::Spog;
20use crate::term::{matcher::TermMatcher, GraphName, Term};
21
22/// Type alias for terms borrowed from a triple.
23pub type TBorrowTerm<'a, T> = <<T as Triple>::Term as Term>::BorrowTerm<'a>;
24
25/// This trait represents an abstract RDF triple,
26/// and provide convenient methods for working with triples.
27pub trait Triple {
28    /// The type of [`Term`] contained by this triple
29    type Term: Term;
30
31    /// The subject of this triple.
32    fn s(&self) -> TBorrowTerm<Self>;
33
34    /// The predicate of this triple.
35    fn p(&self) -> TBorrowTerm<Self>;
36
37    /// The object of this triple.
38    fn o(&self) -> TBorrowTerm<Self>;
39
40    /// The three components of this triple, as a triple of borrowed terms.
41    ///
42    /// See also [`Triple::to_spo`].
43    #[inline]
44    fn spo(&self) -> [TBorrowTerm<Self>; 3] {
45        [self.s(), self.p(), self.o()]
46    }
47
48    /// Consume this triple, returning its subject.
49    fn to_s(self) -> Self::Term
50    where
51        Self: Sized,
52    {
53        let [s, _, _] = self.to_spo();
54        s
55    }
56
57    /// Consume this triple, returning its predicate.
58    fn to_p(self) -> Self::Term
59    where
60        Self: Sized,
61    {
62        let [_, p, _] = self.to_spo();
63        p
64    }
65
66    /// Consume this triple, returning its object.
67    fn to_o(self) -> Self::Term
68    where
69        Self: Sized,
70    {
71        let [_, _, o] = self.to_spo();
72        o
73    }
74
75    /// Consume this triple, returning all its components.
76    ///
77    /// See also [`Triple::spo`].
78    fn to_spo(self) -> [Self::Term; 3]
79    where
80        Self: Sized;
81
82    /// Checks that the constituents terms of this triple match the respective matchers.
83    fn matched_by<S, P, O>(&self, sm: S, pm: P, om: O) -> bool
84    where
85        S: TermMatcher,
86        P: TermMatcher,
87        O: TermMatcher,
88    {
89        sm.matches(&self.s()) && pm.matches(&self.p()) && om.matches(&self.o())
90    }
91
92    /// Check whether `other` is term-wise equal (using [`Term::eq`]) to `self`.
93    ///
94    /// See also [`eq_spo`](Triple::eq_spo), [`matched_by`](Triple::matched_by).
95    #[inline]
96    fn eq<T: Triple>(&self, other: T) -> bool {
97        self.eq_spo(other.s(), other.p(), other.o())
98    }
99
100    /// Check whether the triple (`s`, `p`, `o`) is term-wise equal (using [`Term::eq`]) to `self`.
101    ///
102    /// See also [`eq`](Triple::eq), [`matched_by`](Triple::matched_by).
103    fn eq_spo<S: Term, P: Term, O: Term>(&self, s: S, p: P, o: O) -> bool {
104        self.s().eq(s) && self.p().eq(p) && self.o().eq(o)
105    }
106
107    /// Convert this triple to a [`Quad`](crate::quad::Quad) in the default graph.
108    ///
109    /// NB: if you do not wish to consume this triple,
110    /// you can combine this method with [`spo`](Triple::spo) as below:
111    /// ```
112    /// # use sophia_api::quad::Quad;
113    /// # use sophia_api::triple::Triple;
114    /// # fn test<T: Triple>(t: &T) -> impl Quad + '_ {
115    ///     t.spo().into_quad()   
116    /// # }
117    /// ```
118    ///
119    /// See also [`Triple::into_quad_from`].
120    fn into_quad(self) -> Spog<Self::Term>
121    where
122        Self: Sized,
123    {
124        (self.to_spo(), None)
125    }
126
127    /// Convert this triple to a [`Quad`](crate::quad::Quad) in the given named graph.
128    ///
129    /// See also [`Triple::into_quad`].
130    fn into_quad_from(self, graph_name: GraphName<Self::Term>) -> Spog<Self::Term>
131    where
132        Self: Sized,
133    {
134        (self.to_spo(), graph_name)
135    }
136}
137
138impl<T: Term> Triple for [T; 3] {
139    type Term = T;
140
141    fn s(&self) -> TBorrowTerm<Self> {
142        self[0].borrow_term()
143    }
144    fn p(&self) -> TBorrowTerm<Self> {
145        self[1].borrow_term()
146    }
147    fn o(&self) -> TBorrowTerm<Self> {
148        self[2].borrow_term()
149    }
150    fn to_spo(self) -> [Self::Term; 3] {
151        self
152    }
153}
154
155#[cfg(test)]
156mod check_implementability {
157    use super::*;
158    use crate::term::*;
159    use mownstr::MownStr;
160
161    #[derive(Clone, Copy, Debug)]
162    struct MyBnode(usize);
163
164    impl Term for MyBnode {
165        type BorrowTerm<'x> = Self;
166
167        fn kind(&self) -> TermKind {
168            TermKind::BlankNode
169        }
170        fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
171            Some(BnodeId::new_unchecked(MownStr::from(format!(
172                "b{}",
173                self.0
174            ))))
175        }
176        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
177            *self
178        }
179    }
180
181    #[derive(Clone, Copy, Debug)]
182    struct MyTriple([usize; 3]);
183
184    impl Triple for MyTriple {
185        type Term = MyBnode;
186
187        fn s(&self) -> TBorrowTerm<Self> {
188            MyBnode(self.0[0])
189        }
190        fn p(&self) -> TBorrowTerm<Self> {
191            MyBnode(self.0[1])
192        }
193        fn o(&self) -> TBorrowTerm<Self> {
194            MyBnode(self.0[2])
195        }
196        fn to_s(self) -> Self::Term {
197            self.s()
198        }
199        fn to_p(self) -> Self::Term {
200            self.p()
201        }
202        fn to_o(self) -> Self::Term {
203            self.o()
204        }
205        fn to_spo(self) -> [Self::Term; 3] {
206            [self.s(), self.p(), self.o()]
207        }
208    }
209
210    #[allow(dead_code)] // only checks that this compiles
211    fn check_triple_impl(t: [SimpleTerm; 3]) {
212        fn foo<T: Triple>(t: T) {
213            println!("{:?}", t.s().kind());
214        }
215        let rt = t.spo();
216        foo(rt);
217        {
218            let rt2 = t.spo();
219            foo(rt2);
220        }
221        foo(rt);
222        foo(rt.spo());
223        foo(t);
224
225        let mt = MyTriple([1, 2, 3]);
226        let rmt = mt.spo();
227        foo(rmt);
228        {
229            let rmt2 = mt.spo();
230            foo(rmt2);
231        }
232        foo(rmt);
233        foo(rmt.spo());
234        foo(mt);
235    }
236}
237
238#[cfg(test)]
239mod test_triple {
240    use super::*;
241    use crate::term::SimpleTerm;
242    use sophia_iri::IriRef;
243
244    const S: IriRef<&str> = IriRef::new_unchecked_const("tag:s");
245    const P: IriRef<&str> = IriRef::new_unchecked_const("tag:o");
246    const O: IriRef<&str> = IriRef::new_unchecked_const("tag:p");
247
248    #[test]
249    fn triple_matched_by() {
250        use crate::term::matcher::Any;
251        let t = [S, P, O];
252
253        assert!(t.matched_by(Any, Any, Any));
254        assert!(t.matched_by([S], [P], [O]));
255        assert!(t.matched_by([O, S], [S, P], [P, O]));
256        let istag = |t: SimpleTerm| t.iri().map(|iri| iri.starts_with("tag:")).unwrap_or(false);
257        assert!(t.matched_by(istag, istag, istag));
258
259        let none: Option<IriRef<&str>> = None;
260        assert!(!t.matched_by(none, Any, Any));
261        assert!(!t.matched_by(Any, none, Any));
262        assert!(!t.matched_by(Any, Any, none));
263        assert!(!t.matched_by([P, O], Any, Any));
264        assert!(!t.matched_by(Any, [S, O], Any));
265        assert!(!t.matched_by(Any, Any, [S, P]));
266        let notag = |t: SimpleTerm| t.iri().map(|iri| !iri.starts_with("tag:")).unwrap_or(true);
267        assert!(!t.matched_by(notag, Any, Any));
268        assert!(!t.matched_by(Any, notag, Any));
269        assert!(!t.matched_by(Any, Any, notag));
270
271        let ts = vec![
272            [S, P, S],
273            [S, P, P],
274            [S, P, O],
275            [P, P, S],
276            [P, P, P],
277            [P, P, O],
278            [O, P, S],
279            [O, P, P],
280            [O, P, O],
281        ];
282        let c = ts
283            .iter()
284            .filter(|t| t.matched_by([S, O], Any, [S, O]))
285            .count();
286        assert_eq!(c, 4);
287    }
288}