Skip to content

Instantly share code, notes, and snippets.

@MattesWhite
Forked from pchampin/sophia_experiment.rs
Last active December 22, 2019 11:35
Show Gist options
  • Save MattesWhite/f5daf2d38d36c4e325e00cbb41ec59e8 to your computer and use it in GitHub Desktop.
Save MattesWhite/f5daf2d38d36c4e325e00cbb41ec59e8 to your computer and use it in GitHub Desktop.
//! This is an experiment on how to get rid of lifetime parameters in
//! Sopghia's Graph and Dataset traits. It demonstrates the general idea
//! on a simplified version of Triple and Graph.
//!
//! Graph iterators no longer return Graph::Triple's,
//! they return a safe abstraction around it: GuardedTRiple<'a, Graph::Triple>.
//!
//! Graph::Triple itself can be one of several options:
//! * T (where T implements Triple)
//! * *const T (where T implements Triple)
//! * [*const T; 3] (where T is Term<X>)
//!
//! Intuitively:
//! * the first option must be used when triples are created during iteration,
//! and their ownership is transferred to the caller;
//! * the second option must be used when triples are owned by the Graph,
//! and the caller borrows them for the lifetime of the iterator;
//! * the third option must be used when the Graph owns its terms,
//! but does not own triples per se.
//!
//! The triples() method (and all its sister methods)
//! must use one of the methods provided by the Graph interface
//! to produce GuardedTriple:
//! * guard_triple for the first option
//! * guard_triple_ref for the second option
//! * guard_term_refs for the second option
//! These methods guarantee that the inner pointers will never be used
//! unsafely.
//!
//! NB: technically, the three option above are the three provided implementation
//! of the UnsafeTriple trait, which should not be used directly.
//! In the future, more options could be provided by adding more implementations,
//! but users of the Sophia crate are not expected to do that.
// **** Dummy Triple trait
pub trait Triple {
type Term: AsRef<str>;
fn s(&self) -> &Self::Term;
fn p(&self) -> &Self::Term {
self.s() // just for faster prototyping
}
fn o(&self) -> &Self::Term {
self.s() // just for faster prototyping
}
}
/// **** Dummy Triple implementation
impl Triple for String {
type Term = String;
fn s(&self) -> &Self::Term {
&self
}
// TODO implement p() and o()
}
/// **** Dummy Triple implementation
impl<T: AsRef<str>> Triple for [T; 3] {
type Term = T;
fn s(&self) -> &Self::Term {
&self[0]
}
// TODO implement p() and o()
}
/// Wrapper around triples of a `Graph`.
///
/// This type's main purpose is to avoid a lifetime-parameter on the
/// `Graph` trait.
///
#[derive(Debug)]
pub enum GuardedTriple<'a, T: Triple> {
OwnedTriple(T),
BorrowedTriple(&'a T),
BorrowedTerms {
s: &'a T::Term,
p: &'a T::Term,
o: &'a T::Term,
},
}
impl<'a, T: Triple> Triple for GuardedTriple<'a, T> {
type Term = T::Term;
fn s(&self) -> &Self::Term {
use self::GuardedTriple::*;
match self {
OwnedTriple(t) => t.s(),
BorrowedTriple(t) => t.s(),
BorrowedTerms { s, ..} => s,
}
}
// TODO implement p() and o()
}
impl<'src, T: Triple> GuardedTriple<'src, T> {
/// Construct a `GuardedTriple` by **taking** a `Triple`.
pub fn from_triple(triple: T) -> Self {
Self::OwnedTriple(triple)
}
/// Construct a `GuardedTriple` by **borrowing** a `Triple`.
pub fn from_triple_ref(triple: &'src T) -> Self {
Self::BorrowedTriple(triple)
}
}
impl<'src, T: AsRef<str>> GuardedTriple<'src, [T; 3]> {
/// Construct a `GuardedTriple` from **borrowed `Term`s**.
pub fn from_spo_refs(s: &'src T, p: &'src T, o: &'src T) -> Self {
Self::BorrowedTerms {
s, p, o,
}
}
}
// Dummy Graph trait, with the minimally required structure
/// Type alias for all iterators returned by `Graph` methods.
type GIterator<'a, G> = Box<dyn Iterator<Item=GuardedTriple<'a, <G as Graph>::Triple>> + 'a>;
pub trait Graph {
/// This type is the one used *indternally* by `GIterator`s.
/// What users will get is a safe wrapper around this type.
/// This should be one of the 3 options documented at the top of this file.
type Triple: Triple;
fn triples<'s>(&'s self) -> GIterator<'s, Self>;
}
// Testing the different ways to implement Graph iterators
struct Test1<T: Triple> (Vec<T>);
impl<T: Triple + Clone> Graph for Test1<T> {
type Triple = T;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GuardedTriple::from_triple(t.clone())))
}
}
struct Test2<T: Triple> (Vec<T>);
impl<T: Triple + Clone> Graph for Test2<T> {
type Triple = T;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GuardedTriple::from_triple_ref(t)))
}
}
struct Test3<T: Triple> (Vec<T>);
impl<T> Graph for Test3<T> where
T: Triple + Clone,
T::Term: Sized,
{
type Triple = [T::Term;3];
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GuardedTriple::from_spo_refs(t.s(), t.p(), t.o())))
}
}
fn main() {
let v = vec!["foo".to_string(), "bar".to_string()];
println!("\nTest1");
for t in Test1(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
println!("\nTest2");
for t in Test2(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
println!("\nTest3");
for t in Test3(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
}
//! This is an experiment on how to get rid of lifetime parameters in
//! Sopghia's Graph and Dataset traits. It demonstrates the general idea
//! on a simplified version of Triple and Graph.
//!
//! Graph iterators no longer return Graph::Triple's,
//! they return a safe abstraction around it: GuardedTRiple<'a, Graph::Triple>.
//!
//! Graph::Triple itself can be one of several options:
//! * T (where T implements Triple)
//! * *const T (where T implements Triple)
//! * [*const T; 3] (where T is Term<X>)
//!
//! Intuitively:
//! * the first option must be used when triples are created during iteration,
//! and their ownership is transferred to the caller;
//! * the second option must be used when triples are owned by the Graph,
//! and the caller borrows them for the lifetime of the iterator;
//! * the third option must be used when the Graph owns its terms,
//! but does not own triples per se.
//!
//! The triples() method (and all its sister methods)
//! must use one of the methods provided by the Graph interface
//! to produce GuardedTriple:
//! * guard_triple for the first option
//! * guard_triple_ref for the second option
//! * guard_term_refs for the second option
//! These methods guarantee that the inner pointers will never be used
//! unsafely.
//!
//! NB: technically, the three option above are the three provided implementation
//! of the UnsafeTriple trait, which should not be used directly.
//! In the future, more options could be provided by adding more implementations,
//! but users of the Sophia crate are not expected to do that.
use std::marker::PhantomData;
use std::fmt::Debug;
use std::ptr::NonNull;
// **** Dummy Triple trait
pub trait Triple: Debug {
type Term: AsRef<str> + Debug;
fn s(&self) -> &Self::Term;
fn p(&self) -> &Self::Term {
self.s() // just for faster prototyping
}
fn o(&self) -> &Self::Term {
self.s() // just for faster prototyping
}
}
/// **** Dummy Triple implementation
impl Triple for String {
type Term = String;
fn s(&self) -> &Self::Term {
&self
}
// TODO implement p() and o()
}
/// **** Dummy Triple implementation
impl<Term> Triple for [Term; 3]
where
Term: AsRef<str> + Debug,
{
type Term = Term;
fn s(&self) -> &Self::Term {
&self[0]
}
// TODO implement p() and o()
}
/// Marker trait that states the origin of a triple handed out.
///
/// All implementors are type-level-constucts. As such they can not be
/// instanciated by the user. Everything nesessary is handled in a zero-cost
/// way internally.
pub trait TripleOrigin: Triple {}
/// States that handed out triples are created on-demand.
#[derive(Debug)]
struct CreatedTriple<T: Triple>(T);
impl<T: Triple> Triple for CreatedTriple<T> {
type Term = T::Term;
fn s(&self) -> &Self::Term {
self.0.s()
}
}
impl<T: Triple> TripleOrigin for CreatedTriple<T> {}
/// States that handed out triples are stored inside and handed out by
/// reference.
#[derive(Debug)]
struct StoredTriple<T: Triple>(NonNull<T>);
impl<T: Triple> Triple for StoredTriple<T> {
type Term = T::Term;
fn s(&self) -> &Self::Term {
unsafe { self.0.as_ref().s() }
}
}
impl<T: Triple> TripleOrigin for StoredTriple<T> {}
/// States that handed out triples constructed from references to terms that
/// are stored inside.
#[derive(Debug)]
struct StoredTerms<Term: AsRef<str> + Debug> {
s: NonNull<Term>,
p: NonNull<Term>,
o: NonNull<Term>,
}
impl<Term: AsRef<str> + Debug> Triple for StoredTerms<Term> {
type Term = Term;
fn s(&self) -> &Self::Term {
unsafe { self.s.as_ref() }
}
}
impl<Term: AsRef<str> + Debug> TripleOrigin for StoredTerms<Term> {}
/// Triples handed out by a `Graph`.
///
/// The lifetime of `GraphTriple` ensures that the `Graph` is recognized as
/// borrowed immutable by the compiler ensuring that the `Graph` is not mutated
/// while handing out triples.
///
/// # Overhead
///
/// This type incures zero memory-overhead and is just a type-level work around
/// until GATs arrive.
#[derive(Debug)]
pub struct GraphTriple<'a, T: TripleOrigin> {
_phantom: PhantomData<&'a T>,
triple: T,
}
impl<'a, T> GraphTriple<'a, CreatedTriple<T>>
where
T: Triple
{
/// Wraps a newly created triple.
///
/// The lifetime is defined by the calling context.
fn from_triple(triple: T) -> Self {
Self {
_phantom: PhantomData,
triple: CreatedTriple(triple),
}
}
}
impl<'a, T> GraphTriple<'a, StoredTriple<T>>
where
T: Triple
{
/// Wraps a referenced triple.
fn from_triple_ref(triple: &'a T) -> Self {
Self {
_phantom: PhantomData,
triple: StoredTriple(NonNull::from(triple)),
}
}
}
impl<'a, Term> GraphTriple<'a, StoredTerms<Term>>
where
Term: AsRef<str> + Debug,
{
/// Creates a new triple from references to terms.
fn from_term_refs(s: &'a Term, p: &'a Term, o: &'a Term) -> Self {
Self {
_phantom: PhantomData,
triple: StoredTerms {
s: NonNull::from(s),
p: NonNull::from(p),
o: NonNull::from(o),
},
}
}
}
impl<'a, T: TripleOrigin> Triple for GraphTriple<'a, T> {
type Term = T::Term;
fn s(&self) -> &Self::Term {
self.triple.s()
}
// TODO implement p() and o()
}
// Dummy Graph trait, with the minimally required structure
/// Type alias for all iterators returned by `Graph` methods.
type GIterator<'a, G> = Box<dyn Iterator<Item=GraphTriple<'a, <G as Graph>::IterContent>> + 'a>;
pub trait Graph {
/// Tells the compiler where the triples handed out by the `Graph`'s
/// methods originate from.
///
/// This also defines how the handed-out `GraphTriples` are build:
/// * `CreatedTriple` use `GraphTriple::from_triple()`
/// * `StoredTriple` use `GraphTriple::from_triple_ref()`
/// * `StoredTerms` use `GraphTriple::from_term_refs()`
///
/// _Note:_ Both `TripleOrigin` and `GraphTriple` are zero-cost
/// abstractions. Consequently, this type-level-construct will not harm
/// performance.
type IterContent: TripleOrigin;
fn triples<'s>(&'s self) -> GIterator<'s, Self>;
}
// Testing the different ways to implement Graph iterators
struct Test1<T: Triple> (Vec<T>);
impl<T: Triple + Clone> Graph for Test1<T> {
type IterContent = CreatedTriple<T>;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GraphTriple::from_triple(t.clone())))
}
}
struct Test2<T: Triple> (Vec<T>);
impl<T: Triple + Clone> Graph for Test2<T> {
type IterContent = StoredTriple<T>;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GraphTriple::from_triple_ref(t)))
}
}
struct Test3<T: Triple> (Vec<T>);
impl<T> Graph for Test3<T> where
T: Triple + Clone,
{
type IterContent = StoredTerms<T::Term>;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| GraphTriple::from_term_refs(t.s(), t.p(), t.o())))
}
}
fn main() {
let v = vec!["foo".to_string(), "bar".to_string()];
println!("\nTest1");
for t in Test1(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
println!("\nTest2");
for t in Test2(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
println!("\nTest3");
for t in Test3(v.clone()).triples() {
dbg!(&t);
println!("v: {}", t.s());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment