prowocessing: add documentation of trait experiment

This commit is contained in:
Schrottkatze 2024-02-21 14:24:57 +01:00
parent dddbcccf72
commit 734a734f09
No known key found for this signature in database
9 changed files with 93 additions and 37 deletions

View file

@ -1,3 +1,2 @@
pub mod enum_based; pub mod enum_based;
pub mod trait_based; pub mod trait_based;

View file

@ -1,3 +1,9 @@
//! An experiment for a hyper-modular trait-based architecture.
//!
//! Patterns defining this (or well, which I reference a lot while writing this):
//! - [Command pattern using trait objects](https://rust-unofficial.github.io/patterns/patterns/behavioural/command.html)
//! - [Builder pattern](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html)
pub mod data; pub mod data;
pub mod element; pub mod element;
pub mod ops; pub mod ops;

View file

@ -1,2 +1,7 @@
//! Definitions of the data transfer and storage types.
/// Types for element and pipeline IO
pub mod io; pub mod io;
/// Raw data types contained in `io`
pub mod raw; pub mod raw;

View file

@ -1,29 +1,38 @@
use super::raw::{Data, OwnedData}; use super::raw::{Data, OwnedData};
/// Newtype struct with borrowed types for pipeline/element inputs, so that doesn't force a move or clone
pub struct Inputs<'a>(Vec<Data<'a>>); pub struct Inputs<'a>(Vec<Data<'a>>);
impl<'a> Inputs<'a> { impl<'a> Inputs<'a> {
/// get inner value(s)
pub(crate) fn inner(&self) -> Vec<Data<'a>> { pub(crate) fn inner(&self) -> Vec<Data<'a>> {
self.0.clone() self.0.clone()
} }
} }
impl<'a> From<Vec<Data<'a>>> for Inputs<'a> { impl<'a> From<Vec<Data<'a>>> for Inputs<'a> {
fn from(value: Vec<Data<'a>>) -> Self { fn from(value: Vec<Data<'a>>) -> Self {
Self(value) Self(value)
} }
} }
impl<'a, T: Into<Data<'a>>> From<T> for Inputs<'a> { impl<'a, T: Into<Data<'a>>> From<T> for Inputs<'a> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self(vec![value.into()]) Self(vec![value.into()])
} }
} }
impl<'a> From<&'a Outputs> for Inputs<'a> { impl<'a> From<&'a Outputs> for Inputs<'a> {
fn from(value: &'a Outputs) -> Self { fn from(value: &'a Outputs) -> Self {
Self(value.0.iter().map(std::convert::Into::into).collect()) Self(value.0.iter().map(std::convert::Into::into).collect())
} }
} }
/// Newtype struct around `OwnedData` for pipeline/element outputs
pub struct Outputs(Vec<OwnedData>); pub struct Outputs(Vec<OwnedData>);
impl Outputs { impl Outputs {
/// consume self and return inner value(s)
pub fn into_inner(self) -> Vec<OwnedData> { pub fn into_inner(self) -> Vec<OwnedData> {
self.0 self.0
} }

View file

@ -1,3 +1,25 @@
//! Dynamic data storage and transfer types
/// Owned data type, for use mostly in outputs and storage
#[derive(Clone, Debug)]
pub enum OwnedData {
String(String),
Int(i32),
}
impl From<String> for OwnedData {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<i32> for OwnedData {
fn from(value: i32) -> Self {
Self::Int(value)
}
}
/// Unowned data type, for inputs into runner functions
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Data<'a> { pub enum Data<'a> {
String(&'a str), String(&'a str),
@ -5,6 +27,7 @@ pub enum Data<'a> {
} }
impl Data<'_> { impl Data<'_> {
/// converts itself to `OwnedData`
pub fn to_owned_data(&self) -> OwnedData { pub fn to_owned_data(&self) -> OwnedData {
match self { match self {
Data::String(s) => (*s).to_owned().into(), Data::String(s) => (*s).to_owned().into(),
@ -12,16 +35,19 @@ impl Data<'_> {
} }
} }
} }
impl<'a> From<&'a str> for Data<'a> { impl<'a> From<&'a str> for Data<'a> {
fn from(value: &'a str) -> Self { fn from(value: &'a str) -> Self {
Self::String(value) Self::String(value)
} }
} }
impl From<i32> for Data<'_> { impl From<i32> for Data<'_> {
fn from(value: i32) -> Self { fn from(value: i32) -> Self {
Self::Int(value) Self::Int(value)
} }
} }
impl<'a> From<&'a OwnedData> for Data<'a> { impl<'a> From<&'a OwnedData> for Data<'a> {
fn from(value: &'a OwnedData) -> Self { fn from(value: &'a OwnedData) -> Self {
match value { match value {
@ -30,19 +56,3 @@ impl<'a> From<&'a OwnedData> for Data<'a> {
} }
} }
} }
#[derive(Clone, Debug)]
pub enum OwnedData {
String(String),
Int(i32),
}
impl From<String> for OwnedData {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<i32> for OwnedData {
fn from(value: i32) -> Self {
Self::Int(value)
}
}

View file

@ -1,17 +1,23 @@
//! The trait and type representations
use crate::experimental::trait_based::data::io::Inputs; use crate::experimental::trait_based::data::io::Inputs;
use super::data::io::Outputs; use super::data::io::Outputs;
pub(crate) trait PipelineElement { pub(crate) trait PipelineElement {
/// return a static runner function pointer to avoid dynamic dispatch during pipeline execution - Types MUST match the signature
fn runner(&self) -> fn(&Inputs) -> Outputs; fn runner(&self) -> fn(&Inputs) -> Outputs;
fn signature(&self) -> ElementIo; /// return the signature of the element
fn signature(&self) -> ElementSignature;
} }
pub(crate) struct ElementIo { /// Type signature for an element used for static checking
pub(crate) struct ElementSignature {
pub inputs: Vec<DataType>, pub inputs: Vec<DataType>,
pub outputs: Vec<DataType>, pub outputs: Vec<DataType>,
} }
/// Data type enum
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DataType { pub enum DataType {
String, String,

View file

@ -1,3 +1,4 @@
//! Operations on numeric data
use core::panic; use core::panic;
use crate::experimental::trait_based::{ use crate::experimental::trait_based::{
@ -5,9 +6,10 @@ use crate::experimental::trait_based::{
io::{Inputs, Outputs}, io::{Inputs, Outputs},
raw::Data, raw::Data,
}, },
element::{DataType, ElementIo, PipelineElement}, element::{DataType, ElementSignature, PipelineElement},
}; };
/// Addition
pub struct Add(pub i32); pub struct Add(pub i32);
impl PipelineElement for Add { impl PipelineElement for Add {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -20,14 +22,15 @@ impl PipelineElement for Add {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::Int, DataType::Int], inputs: vec![DataType::Int, DataType::Int],
outputs: vec![DataType::Int], outputs: vec![DataType::Int],
} }
} }
} }
/// Subtraction
pub struct Subtract(pub i32); pub struct Subtract(pub i32);
impl PipelineElement for Subtract { impl PipelineElement for Subtract {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -40,14 +43,15 @@ impl PipelineElement for Subtract {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::Int, DataType::Int], inputs: vec![DataType::Int, DataType::Int],
outputs: vec![DataType::Int], outputs: vec![DataType::Int],
} }
} }
} }
/// Turn input to string
pub struct Stringify; pub struct Stringify;
impl PipelineElement for Stringify { impl PipelineElement for Stringify {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -60,8 +64,8 @@ impl PipelineElement for Stringify {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::Int], inputs: vec![DataType::Int],
outputs: vec![DataType::String], outputs: vec![DataType::String],
} }

View file

@ -1,11 +1,13 @@
//! Operation on String/text data
use crate::experimental::trait_based::{ use crate::experimental::trait_based::{
data::{ data::{
io::{Inputs, Outputs}, io::{Inputs, Outputs},
raw::Data, raw::Data,
}, },
element::{DataType, ElementIo, PipelineElement}, element::{DataType, ElementSignature, PipelineElement},
}; };
/// Concatenate the inputs
pub struct Concatenate(pub String); pub struct Concatenate(pub String);
impl PipelineElement for Concatenate { impl PipelineElement for Concatenate {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -18,14 +20,15 @@ impl PipelineElement for Concatenate {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::String, DataType::String], inputs: vec![DataType::String, DataType::String],
outputs: vec![DataType::String], outputs: vec![DataType::String],
} }
} }
} }
/// Turn input text to uppercase
pub struct Upper; pub struct Upper;
impl PipelineElement for Upper { impl PipelineElement for Upper {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -38,14 +41,15 @@ impl PipelineElement for Upper {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::String], inputs: vec![DataType::String],
outputs: vec![DataType::String], outputs: vec![DataType::String],
} }
} }
} }
/// Turn input text to lowercase
pub struct Lower; pub struct Lower;
impl PipelineElement for Lower { impl PipelineElement for Lower {
fn runner(&self) -> fn(&Inputs) -> Outputs { fn runner(&self) -> fn(&Inputs) -> Outputs {
@ -58,8 +62,8 @@ impl PipelineElement for Lower {
} }
} }
fn signature(&self) -> ElementIo { fn signature(&self) -> ElementSignature {
ElementIo { ElementSignature {
inputs: vec![DataType::String], inputs: vec![DataType::String],
outputs: vec![DataType::String], outputs: vec![DataType::String],
} }

View file

@ -2,21 +2,25 @@ use super::data::io::{Inputs, Outputs};
use super::element::PipelineElement; use super::element::PipelineElement;
use super::ops::prelude::*; use super::ops::prelude::*;
// TODO: /// Builder for the pipelines that are actually run
// - Bind additional inputs if instruction has more then one and is passd without any additional ///
// - allow binding to pointers to other pipelines? /// TODO:
// - allow referencing earlier data /// - Bind additional inputs if instruction has more then one and is passd without any additional
/// - allow binding to pointers to other pipelines?
/// - allow referencing earlier data
pub struct PipelineBuilder { pub struct PipelineBuilder {
elements: Vec<Box<dyn PipelineElement>>, elements: Vec<Box<dyn PipelineElement>>,
} }
impl PipelineBuilder { impl PipelineBuilder {
/// Create new, empty builder
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
elements: Vec::new(), elements: Vec::new(),
} }
} }
/// Insert element into pipeline
fn insert<T: PipelineElement + 'static>(mut self, el: T) -> Self { fn insert<T: PipelineElement + 'static>(mut self, el: T) -> Self {
if let Some(previous_item) = self.elements.last() { if let Some(previous_item) = self.elements.last() {
assert_eq!( assert_eq!(
@ -28,21 +32,25 @@ impl PipelineBuilder {
self self
} }
/// insert string concatenattion element
#[must_use] #[must_use]
pub fn concatenate(self, sec: String) -> Self { pub fn concatenate(self, sec: String) -> Self {
self.insert(Concatenate(sec)) self.insert(Concatenate(sec))
} }
/// insert string uppercase element
#[must_use] #[must_use]
pub fn upper(self) -> Self { pub fn upper(self) -> Self {
self.insert(Upper) self.insert(Upper)
} }
/// insert string lowercase element
#[must_use] #[must_use]
pub fn lower(self) -> Self { pub fn lower(self) -> Self {
self.insert(Lower) self.insert(Lower)
} }
/// insert numeric addition element
#[must_use] #[must_use]
#[allow( #[allow(
clippy::should_implement_trait, clippy::should_implement_trait,
@ -52,16 +60,19 @@ impl PipelineBuilder {
self.insert(Add(sec)) self.insert(Add(sec))
} }
/// insert numeric subtraction element
#[must_use] #[must_use]
pub fn subtract(self, sec: i32) -> Self { pub fn subtract(self, sec: i32) -> Self {
self.insert(Subtract(sec)) self.insert(Subtract(sec))
} }
/// insert stringify element
#[must_use] #[must_use]
pub fn stringify(self) -> Self { pub fn stringify(self) -> Self {
self.insert(Stringify) self.insert(Stringify)
} }
/// Build the pipeline. Doesn't check again - `insert` should verify correctness.
pub fn build(&self) -> Pipeline { pub fn build(&self) -> Pipeline {
let mut r = Vec::new(); let mut r = Vec::new();
@ -77,11 +88,13 @@ impl Default for PipelineBuilder {
} }
} }
/// Runnable pipeline - at the core of this library
pub struct Pipeline { pub struct Pipeline {
runners: Vec<fn(&Inputs) -> Outputs>, runners: Vec<fn(&Inputs) -> Outputs>,
} }
impl Pipeline { impl Pipeline {
/// run the pipeline
pub fn run(&self, inputs: Inputs) -> Outputs { pub fn run(&self, inputs: Inputs) -> Outputs {
let mut out: Outputs = inputs.into(); let mut out: Outputs = inputs.into();