ibind/IState

This commit is contained in:
AF 2023-04-22 15:30:33 +00:00
parent 8a3f9ac25f
commit eb153d6396
14 changed files with 288 additions and 40 deletions

View File

@ -19,20 +19,21 @@ pub trait Context {
} }
/// Failure yielded by [`Origin`]. /// Failure yielded by [`Origin`].
#[derive(Debug)]
pub enum ResolutionError<L, P> { pub enum ResolutionError<L, P> {
Lookup(L), Lookup(L),
Parse(P), Parse(P),
} }
/// Result yielded by [`Origin`]. /// See [`ResolutionResult`].
pub type ResolutionResult<'a, Ctx, A> = Result< pub type ResolutionFailure<'a, Ctx, A> = ResolutionError<
Rc<A>, <Ctx as Context>::LookupError<'a>,
ResolutionError< <<A as Mentionable<'a, Ctx>>::Fctr as Factory<'a, Ctx>>::ParseError,
<Ctx as Context>::LookupError<'a>,
<<A as Mentionable<'a, Ctx>>::Fctr as Factory<'a, Ctx>>::ParseError,
>,
>; >;
/// Result yielded by [`Origin`].
pub type ResolutionResult<'a, Ctx, A> = Result<Rc<A>, ResolutionFailure<'a, Ctx, A>>;
/// Helper alias for [`WeakFunctor::F`] of [`Context::T`]. /// Helper alias for [`WeakFunctor::F`] of [`Context::T`].
pub type Wrapped<'a, Ctx, A> = <<Ctx as Context>::T as WeakFunctor>::F<'a, A>; pub type Wrapped<'a, Ctx, A> = <<Ctx as Context>::T as WeakFunctor>::F<'a, A>;
@ -98,8 +99,11 @@ pub trait Mentionable<'a, Ctx: 'a + Context>: 'a + Serializable {
} }
/// Short-hand for the type of values returned by [`Resolver::resolve`]. /// Short-hand for the type of values returned by [`Resolver::resolve`].
pub type HashResolution<'a, Ctx> = pub type HashResolution<'a, Ctx> = Wrapped<
Wrapped<'a, Ctx, Result<(Vec<u8>, Rc<dyn Resolver<'a, Ctx>>), <Ctx as Context>::LookupError<'a>>>; 'a,
Ctx,
Result<(Vec<u8>, Rc<dyn Resolver<'a, Ctx>>), <Ctx as Context>::LookupError<'a>>,
>;
/// Value accepted by [`Resolver::resolve`]. Includes index to make it order-sensitive. /// Value accepted by [`Resolver::resolve`]. Includes index to make it order-sensitive.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View File

@ -147,6 +147,14 @@ pub trait Applicative: Functor + ApplicativeSeq + ApplicativeLA2 + ApplicativeTu
} }
} }
/// Represents iteration state.
pub enum IState<A, B> {
/// Loop running.
Pending(A),
/// Loop finished.
Done(B),
}
/// Equivalent of Haskell's `Monad`. /// Equivalent of Haskell's `Monad`.
/// ///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html> /// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html>
@ -159,6 +167,18 @@ pub trait Monad: Applicative {
where where
Self: 'a; Self: 'a;
/// Kleisli category composition special case used to represent loops.
/// Included for optimisation and clarity.
/// Generally, [`Monad::bind`] should be enough implement it.
/// See [`classes::stackless::StacklessClass::ibind`] for a generic, though less-than ideal, blanket implementation.
/// On practice, you shouldn't be using [`Monad::bind`]/[`Applicative::pure`]/[`Functor::fmap`] here.
fn ibind<'a, A: 'a, B: 'a>(
a: A,
f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a;
/// Equivalent of Haskell's `join`. /// Equivalent of Haskell's `join`.
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A>
where where

View File

@ -84,6 +84,23 @@ impl Monad for FutureClass {
Box::pin(async { f(fa.await).await }) Box::pin(async { f(fa.await).await })
} }
fn ibind<'a, A: 'a, B: 'a>(
mut a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
Box::pin(async move {
loop {
match f(a).await {
IState::Pending(next_a) => a = next_a,
IState::Done(b) => return b,
};
}
})
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> { fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> {
Box::pin(async { ffa.await.await }) Box::pin(async { ffa.await.await })
} }

View File

@ -83,6 +83,21 @@ impl Monad for LazyClass {
Box::new(|| f(fa())()) Box::new(|| f(fa())())
} }
fn ibind<'a, A: 'a, B: 'a>(
mut a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
Box::new(move || loop {
match f(a)() {
IState::Pending(next_a) => a = next_a,
IState::Done(b) => return b,
};
})
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> { fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> {
Box::new(|| ffa()()) Box::new(|| ffa()())
} }

View File

@ -77,6 +77,21 @@ impl Monad for OptionClass {
f(fa?) f(fa?)
} }
fn ibind<'a, A: 'a, B: 'a>(
mut a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
loop {
match f(a)? {
IState::Pending(next_a) => a = next_a,
IState::Done(b) => return Self::pure(b),
};
}
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> { fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> {
ffa? ffa?
} }

View File

@ -77,6 +77,21 @@ impl<E> Monad for ResultClass<E> {
f(fa?) f(fa?)
} }
fn ibind<'a, A: 'a, B: 'a>(
mut a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
loop {
match f(a)? {
IState::Pending(next_a) => a = next_a,
IState::Done(b) => return Self::pure(b),
};
}
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> { fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> {
ffa? ffa?
} }

View File

@ -77,6 +77,21 @@ impl Monad for SoloClass {
f(fa) f(fa)
} }
fn ibind<'a, A: 'a, B: 'a>(
mut a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
loop {
match f(a) {
IState::Pending(next_a) => a = next_a,
IState::Done(b) => return b,
};
}
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> { fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> {
ffa ffa
} }

View File

@ -117,7 +117,7 @@ impl<'a, A: 'a> From<A> for Stackless<'a, A> {
} }
} }
struct StacklessClass; pub struct StacklessClass;
impl WeakFunctor for StacklessClass { impl WeakFunctor for StacklessClass {
type F<'a, A: 'a> = Stackless<'a, A>; type F<'a, A: 'a> = Stackless<'a, A>;
@ -221,6 +221,22 @@ impl Monad for StacklessClass {
{ {
fa.bind(f) fa.bind(f)
} }
fn ibind<'a, A: 'a, B: 'a>(
a: A,
mut f: impl 'a + FnMut(A) -> Self::F<'a, IState<A, B>>,
) -> Self::F<'a, B>
where
Self: 'a,
{
Self::pure(a).bind(move |a| {
f(a).bind(|state| match state {
IState::Pending(a) => Self::ibind(a, f),
IState::Done(b) => Self::pure(b),
})
})
}
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A>
where where
Self::F<'a, A>: 'a, Self::F<'a, A>: 'a,

View File

@ -344,11 +344,11 @@ impl Display for Address {
} }
pub trait ExtSerializable: Serializable { pub trait ExtSerializable: Serializable {
fn to_vec(&self) -> Vec<u8>; fn bytes(&self) -> Vec<u8>;
} }
impl<S: Serializable> ExtSerializable for S { impl<S: Serializable> ExtSerializable for S {
fn to_vec(&self) -> Vec<u8> { fn bytes(&self) -> Vec<u8> {
let mut vec = Vec::new(); let mut vec = Vec::new();
self.serialize(&mut vec); self.serialize(&mut vec);
vec vec

View File

@ -3,12 +3,12 @@ use std::fmt::Display;
use crate::std::atomic::*; use crate::std::atomic::*;
#[derive(Clone)] #[derive(Clone)]
struct Plain { pub struct Plain {
pub data: Vec<u8>, data: Vec<u8>,
} }
#[derive(Debug)] #[derive(Debug)]
enum PlainParseError {} pub enum PlainParseError {}
impl Display for PlainParseError { impl Display for PlainParseError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -28,9 +28,7 @@ impl Atomic for Plain {
type ParseError = PlainParseError; type ParseError = PlainParseError;
fn deserialize(deserializer: &mut dyn Deserializer) -> Result<Self, Self::ParseError> { fn deserialize(deserializer: &mut dyn Deserializer) -> Result<Self, Self::ParseError> {
Ok(Plain { Ok(Plain::from_slice(deserializer.read_all()))
data: deserializer.read_all().into(),
})
} }
fn unexpected_tail(tail: &[u8]) -> Self::ParseError { fn unexpected_tail(tail: &[u8]) -> Self::ParseError {
@ -41,15 +39,21 @@ impl Atomic for Plain {
} }
} }
impl Plain {
pub fn from_slice(slice: &[u8]) -> Self {
Plain { data: slice.into() }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::std::*;
use super::*; use super::*;
use crate::std::*;
#[test] #[test]
fn test_plain() -> Result<(), PlainParseError> { fn test_plain() -> Result<(), PlainParseError> {
let plain = Plain::parse_slice(b"slice")?; let plain = Plain::parse_slice(b"slice")?;
assert_eq!(plain.to_vec().as_slice(), b"slice"); assert_eq!(plain.bytes().as_slice(), b"slice");
Ok(()) Ok(())
} }
} }

View File

@ -5,9 +5,13 @@ struct CastResolver<'a, Ctx: 'a + Context> {
points: Vec<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>>, points: Vec<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>>,
} }
#[derive(Debug)]
pub enum CastError<'a> { pub enum CastError<'a> {
Typeless(TypelessError<'a>), Typeless(TypelessError<'a>),
AddressIndexOutOfBounds(usize), AddressIndexOutOfBounds {
index: usize,
length: usize,
},
AddressPointMismatch { AddressPointMismatch {
expected: Hash, expected: Hash,
received: Hash, received: Hash,
@ -15,6 +19,30 @@ pub enum CastError<'a> {
}, },
} }
impl<'a> Display for CastError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CastError::Typeless(typeless_error) => {
f.write_fmt(format_args!("typeless cast error: {}", typeless_error))
}
CastError::AddressIndexOutOfBounds { index, length } => f.write_fmt(format_args!(
"cast index out of bound: {}>={}",
index, length
)),
CastError::AddressPointMismatch {
expected,
received,
index,
} => f.write_fmt(format_args!(
"address mismatch at index {}: {}>={}",
index,
hex::encode(expected),
hex::encode(received),
)),
}
}
}
impl<'a, Ctx: 'a + Context> Resolver<'a, Ctx> for CastResolver<'a, Ctx> impl<'a, Ctx: 'a + Context> Resolver<'a, Ctx> for CastResolver<'a, Ctx>
where where
Ctx::LookupError<'a>: From<CastError<'a>>, Ctx::LookupError<'a>: From<CastError<'a>>,
@ -23,7 +51,11 @@ where
let point = match self.points.get(address.index) { let point = match self.points.get(address.index) {
Some(point) => point, Some(point) => point,
None => { None => {
return Ctx::T::pure(Err(CastError::AddressIndexOutOfBounds(address.index).into())); return Ctx::T::pure(Err(CastError::AddressIndexOutOfBounds {
index: address.index,
length: self.points.len(),
}
.into()));
} }
}; };
if point.point != address.point { if point.point != address.point {
@ -40,7 +72,7 @@ where
let resolver: Rc<dyn Resolver<'a, Ctx>> = Rc::new(CastResolver { let resolver: Rc<dyn Resolver<'a, Ctx>> = Rc::new(CastResolver {
points: mentionable.points(), points: mentionable.points(),
}); });
Ok((mentionable.to_vec(), resolver)) Ok((mentionable.bytes(), resolver))
} }
Err(error) => Err(match error { Err(error) => Err(match error {
ResolutionError::Lookup(lookup_error) => lookup_error, ResolutionError::Lookup(lookup_error) => lookup_error,
@ -60,7 +92,7 @@ where
{ {
pub fn cast<A: Mentionable<'a, Ctx>>(&self, factory: A::Fctr) -> CastResult<'a, Ctx, A> { pub fn cast<A: Mentionable<'a, Ctx>>(&self, factory: A::Fctr) -> CastResult<'a, Ctx, A> {
factory.parse_slice( factory.parse_slice(
&self.to_vec(), &self.bytes(),
Rc::new(CastResolver { Rc::new(CastResolver {
points: self.points(), points: self.points(),
}), }),

View File

@ -4,7 +4,7 @@ use crate::std::*;
pub struct StackNode<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> { pub struct StackNode<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> {
pub rest: Stack<'a, Ctx, A>, pub rest: Stack<'a, Ctx, A>,
pub element: A, pub element: Rc<A>,
} }
type Stack<'a, Ctx, A> = Nullable<'a, Ctx, StackNode<'a, Ctx, A>>; type Stack<'a, Ctx, A> = Nullable<'a, Ctx, StackNode<'a, Ctx, A>>;
@ -90,12 +90,14 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx>
return Err(StackParseError::Point(ppe)); return Err(StackParseError::Point(ppe));
} }
}; };
let element = match self.element_factory.deserialize(deserializer, resolver) { let element = Rc::new(
Ok(element) => element, match self.element_factory.deserialize(deserializer, resolver) {
Err(epe) => { Ok(element) => element,
return Err(StackParseError::Element(epe)); Err(epe) => {
} return Err(StackParseError::Element(epe));
}; }
},
);
Ok(StackNode { rest, element }) Ok(StackNode { rest, element })
} }
@ -104,10 +106,17 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx>
} }
} }
pub type StackFaiure<'a, Ctx, A> = ResolutionFailure<'a, Ctx, StackNode<'a, Ctx, A>>;
pub type StackVecResult<'a, Ctx, A> = Result<Vec<Rc<A>>, StackFaiure<'a, Ctx, A>>;
pub type StackVecWrapped<'a, Ctx, A> = Wrapped<'a, Ctx, StackVecResult<'a, Ctx, A>>;
pub trait ExtStack<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>>: Mentionable<'a, Ctx> { pub trait ExtStack<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>>: Mentionable<'a, Ctx> {
fn empty(factory: A::Fctr) -> Self; fn empty(factory: A::Fctr) -> Self;
fn f(factory: A::Fctr) -> Self::Fctr; fn f(factory: A::Fctr) -> Self::Fctr;
fn add(self, element: A) -> Self; fn add(self, element: A) -> Self;
fn vec(self) -> StackVecWrapped<'a, Ctx, A>;
} }
impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> ExtStack<'a, Ctx, A> for Stack<'a, Ctx, A> { impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> ExtStack<'a, Ctx, A> for Stack<'a, Ctx, A> {
@ -121,9 +130,77 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> ExtStack<'a, Ctx, A> for St
Nullable::NotNull( Nullable::NotNull(
StackNode { StackNode {
rest: self, rest: self,
element, element: Rc::new(element),
} }
.into(), .into(),
) )
} }
fn vec(self) -> StackVecWrapped<'a, Ctx, A> {
Ctx::T::ibind((vec![], self), |(mut vec, stack)| match stack {
Nullable::Null(_) => Ctx::T::pure(IState::Done(Ok(vec))),
Nullable::NotNull(point) => Ctx::T::fmap(
|resolved| {
let node = match resolved {
Ok(node) => node,
Err(error) => {
return IState::Done(Err(error));
}
};
vec.push(node.element.clone());
IState::Pending((vec, node.rest.clone()))
},
point.resolve(),
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::std::atomic::plain::*;
use crate::std::atomic::*;
use crate::testing::*;
type T = Stack<'static, TestContext, Plain>;
fn unstack(stack: &T) -> Result<Vec<Vec<u8>>, StackFaiure<'static, TestContext, Plain>> {
Ok(stack
.clone()
.vec()?
.iter()
.map(|plain| plain.bytes())
.collect())
}
fn make_stack() -> T {
let stack: T = Stack::empty(Plain::f());
let stack: T = stack.add(Plain::from_slice(b"A0"));
let stack: T = stack.add(Plain::from_slice(b"B1"));
let stack: T = stack.add(Plain::from_slice(b"C2"));
stack
}
fn validate_stack(stack: &T) {
let mut vec: Vec<Vec<u8>> = unstack(stack).unwrap();
vec.reverse();
assert_eq!(vec, vec![b"A0".to_vec(), b"B1".to_vec(), b"C2".to_vec()]);
}
#[test]
fn test_stack() -> Result<(), StackFaiure<'static, TestContext, Plain>> {
let stack: T = make_stack();
validate_stack(&stack);
let stack: T = TypelessMentionable::from_typed(stack.into())
.cast(Stack::f(Plain::f()))
.expect("cast failed");
validate_stack(&stack);
Ok(())
}
} }

View File

@ -65,13 +65,13 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx>
deserializer: &mut dyn Deserializer, deserializer: &mut dyn Deserializer,
resolver: std::rc::Rc<dyn Resolver<'a, Ctx>>, resolver: std::rc::Rc<dyn Resolver<'a, Ctx>>,
) -> ParseResult<'a, Ctx, Self> { ) -> ParseResult<'a, Ctx, Self> {
let point = deserializer.read_n_const::<HASH_SIZE>()?; let mut addresses = Addresses::start();
let factory = self.factory.clone(); let factory = self.factory.clone();
Ok(if point == HASH_ZEROS { let address = addresses.next(deserializer)?;
Ok(if address.point == HASH_ZEROS {
Nullable::Null(factory) Nullable::Null(factory)
} else { } else {
let mut addresses = Addresses::start(); Nullable::NotNull(Point::from_point(address, factory, resolver))
Nullable::NotNull(addresses.next_point(deserializer, resolver, factory)?)
}) })
} }
@ -86,10 +86,8 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Nullable<'a, Ctx, Nullable<
Nullable::Null(nullable_factory) => { Nullable::Null(nullable_factory) => {
let NullableFactory { factory } = nullable_factory; let NullableFactory { factory } = nullable_factory;
Ctx::T::pure(Ok(Rc::new(Nullable::Null(factory.clone())))) Ctx::T::pure(Ok(Rc::new(Nullable::Null(factory.clone()))))
}, }
Nullable::NotNull(point) => { Nullable::NotNull(point) => point.resolve(),
point.resolve()
},
} }
} }
} }
@ -99,3 +97,12 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> NullableFactory<'a, Ctx, A>
Self { factory } Self { factory }
} }
} }
impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Clone for Nullable<'a, Ctx, A> {
fn clone(&self) -> Self {
match self {
Self::Null(factory) => Self::Null(factory.clone()),
Self::NotNull(point) => Self::NotNull(point.clone()),
}
}
}

View File

@ -3,6 +3,7 @@ use std::fmt::Display;
use crate::core::*; use crate::core::*;
use crate::func::*; use crate::func::*;
use crate::std::cast::CastError;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
pub struct TestContext; pub struct TestContext;
@ -10,6 +11,7 @@ pub struct TestContext;
#[derive(Debug)] #[derive(Debug)]
pub enum TestLookupError<'a> { pub enum TestLookupError<'a> {
Typeless(TypelessError<'a>), Typeless(TypelessError<'a>),
Cast(CastError<'a>),
EmptyResolverAccess(Address), EmptyResolverAccess(Address),
} }
@ -19,12 +21,21 @@ impl<'a> From<TypelessError<'a>> for TestLookupError<'a> {
} }
} }
impl<'a> From<CastError<'a>> for TestLookupError<'a> {
fn from(value: CastError<'a>) -> Self {
Self::Cast(value)
}
}
impl<'a> Display for TestLookupError<'a> { impl<'a> Display for TestLookupError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
TestLookupError::Typeless(typeless_error) => { TestLookupError::Typeless(typeless_error) => {
f.write_fmt(format_args!("typeless lookup failure: {}", typeless_error)) f.write_fmt(format_args!("typeless lookup failure: {}", typeless_error))
} }
TestLookupError::Cast(cast_error) => {
f.write_fmt(format_args!("cast failure: {}", cast_error))
}
TestLookupError::EmptyResolverAccess(address) => f.write_fmt(format_args!( TestLookupError::EmptyResolverAccess(address) => f.write_fmt(format_args!(
"accessed an empty resolved at address {}.", "accessed an empty resolved at address {}.",
address address