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`].
#[derive(Debug)]
pub enum ResolutionError<L, P> {
Lookup(L),
Parse(P),
}
/// Result yielded by [`Origin`].
pub type ResolutionResult<'a, Ctx, A> = Result<
Rc<A>,
ResolutionError<
<Ctx as Context>::LookupError<'a>,
<<A as Mentionable<'a, Ctx>>::Fctr as Factory<'a, Ctx>>::ParseError,
>,
/// See [`ResolutionResult`].
pub type ResolutionFailure<'a, Ctx, A> = ResolutionError<
<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`].
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`].
pub type HashResolution<'a, Ctx> =
Wrapped<'a, Ctx, Result<(Vec<u8>, Rc<dyn Resolver<'a, Ctx>>), <Ctx as Context>::LookupError<'a>>>;
pub type HashResolution<'a, Ctx> = Wrapped<
'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.
#[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`.
///
/// <https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Monad.html>
@ -159,6 +167,18 @@ pub trait Monad: Applicative {
where
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`.
fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A>
where

View File

@ -84,6 +84,23 @@ impl Monad for FutureClass {
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> {
Box::pin(async { ffa.await.await })
}

View File

@ -83,6 +83,21 @@ impl Monad for LazyClass {
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> {
Box::new(|| ffa()())
}

View File

@ -77,6 +77,21 @@ impl Monad for OptionClass {
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> {
ffa?
}

View File

@ -77,6 +77,21 @@ impl<E> Monad for ResultClass<E> {
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> {
ffa?
}

View File

@ -77,6 +77,21 @@ impl Monad for SoloClass {
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> {
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 {
type F<'a, A: 'a> = Stackless<'a, A>;
@ -221,6 +221,22 @@ impl Monad for StacklessClass {
{
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>
where
Self::F<'a, A>: 'a,

View File

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

View File

@ -3,12 +3,12 @@ use std::fmt::Display;
use crate::std::atomic::*;
#[derive(Clone)]
struct Plain {
pub data: Vec<u8>,
pub struct Plain {
data: Vec<u8>,
}
#[derive(Debug)]
enum PlainParseError {}
pub enum PlainParseError {}
impl Display for PlainParseError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -28,9 +28,7 @@ impl Atomic for Plain {
type ParseError = PlainParseError;
fn deserialize(deserializer: &mut dyn Deserializer) -> Result<Self, Self::ParseError> {
Ok(Plain {
data: deserializer.read_all().into(),
})
Ok(Plain::from_slice(deserializer.read_all()))
}
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)]
mod tests {
use crate::std::*;
use super::*;
use crate::std::*;
#[test]
fn test_plain() -> Result<(), PlainParseError> {
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(())
}
}

View File

@ -5,9 +5,13 @@ struct CastResolver<'a, Ctx: 'a + Context> {
points: Vec<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>>,
}
#[derive(Debug)]
pub enum CastError<'a> {
Typeless(TypelessError<'a>),
AddressIndexOutOfBounds(usize),
AddressIndexOutOfBounds {
index: usize,
length: usize,
},
AddressPointMismatch {
expected: 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>
where
Ctx::LookupError<'a>: From<CastError<'a>>,
@ -23,7 +51,11 @@ where
let point = match self.points.get(address.index) {
Some(point) => point,
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 {
@ -40,7 +72,7 @@ where
let resolver: Rc<dyn Resolver<'a, Ctx>> = Rc::new(CastResolver {
points: mentionable.points(),
});
Ok((mentionable.to_vec(), resolver))
Ok((mentionable.bytes(), resolver))
}
Err(error) => Err(match 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> {
factory.parse_slice(
&self.to_vec(),
&self.bytes(),
Rc::new(CastResolver {
points: self.points(),
}),

View File

@ -4,7 +4,7 @@ use crate::std::*;
pub struct StackNode<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> {
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>>;
@ -90,12 +90,14 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx>
return Err(StackParseError::Point(ppe));
}
};
let element = match self.element_factory.deserialize(deserializer, resolver) {
Ok(element) => element,
Err(epe) => {
return Err(StackParseError::Element(epe));
}
};
let element = Rc::new(
match self.element_factory.deserialize(deserializer, resolver) {
Ok(element) => element,
Err(epe) => {
return Err(StackParseError::Element(epe));
}
},
);
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> {
fn empty(factory: A::Fctr) -> Self;
fn f(factory: A::Fctr) -> Self::Fctr;
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> {
@ -121,9 +130,77 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> ExtStack<'a, Ctx, A> for St
Nullable::NotNull(
StackNode {
rest: self,
element,
element: Rc::new(element),
}
.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,
resolver: std::rc::Rc<dyn Resolver<'a, Ctx>>,
) -> ParseResult<'a, Ctx, Self> {
let point = deserializer.read_n_const::<HASH_SIZE>()?;
let mut addresses = Addresses::start();
let factory = self.factory.clone();
Ok(if point == HASH_ZEROS {
let address = addresses.next(deserializer)?;
Ok(if address.point == HASH_ZEROS {
Nullable::Null(factory)
} else {
let mut addresses = Addresses::start();
Nullable::NotNull(addresses.next_point(deserializer, resolver, factory)?)
Nullable::NotNull(Point::from_point(address, factory, resolver))
})
}
@ -86,10 +86,8 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Nullable<'a, Ctx, Nullable<
Nullable::Null(nullable_factory) => {
let NullableFactory { factory } = nullable_factory;
Ctx::T::pure(Ok(Rc::new(Nullable::Null(factory.clone()))))
},
Nullable::NotNull(point) => {
point.resolve()
},
}
Nullable::NotNull(point) => point.resolve(),
}
}
}
@ -99,3 +97,12 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> NullableFactory<'a, Ctx, A>
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::func::*;
use crate::std::cast::CastError;
use sha2::{Digest, Sha256};
pub struct TestContext;
@ -10,6 +11,7 @@ pub struct TestContext;
#[derive(Debug)]
pub enum TestLookupError<'a> {
Typeless(TypelessError<'a>),
Cast(CastError<'a>),
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> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TestLookupError::Typeless(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!(
"accessed an empty resolved at address {}.",
address