tamer: f: Add TryMap
This implements TryMap and utilizes it in `asg::graph::object::tpl`. DEV-13163main
parent
38c0161257
commit
3c9e1add20
|
@ -22,7 +22,11 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::{prelude::*, Doc, Expr, Ident};
|
use super::{prelude::*, Doc, Expr, Ident};
|
||||||
use crate::{f::Map, parse::util::SPair, span::Span};
|
use crate::{
|
||||||
|
f::{Map, TryMap},
|
||||||
|
parse::util::SPair,
|
||||||
|
span::Span,
|
||||||
|
};
|
||||||
|
|
||||||
/// Template with associated name.
|
/// Template with associated name.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -54,10 +58,16 @@ impl Map<Span> for Tpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Map<TplShape> for Tpl {
|
impl TryMap<TplShape> for Tpl {
|
||||||
fn map(self, f: impl FnOnce(TplShape) -> TplShape) -> Self::Target {
|
fn try_map<E>(
|
||||||
|
self,
|
||||||
|
f: impl FnOnce(TplShape) -> Self::FnResult<E>,
|
||||||
|
) -> Self::Result<E> {
|
||||||
match self {
|
match self {
|
||||||
Self(span, shape) => Self(span, f(shape)),
|
Self(span, x) => match f(x) {
|
||||||
|
Ok(shape) => Ok(Self(span, shape)),
|
||||||
|
Err((shape, e)) => Err((Self(span, shape), e)),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,15 +161,22 @@ pub enum TplShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TplShape {
|
impl TplShape {
|
||||||
|
/// Attempt to adapt a template shape to that of another.
|
||||||
|
///
|
||||||
|
/// If the shape of `other` is a refinement of the shape of `self`,
|
||||||
|
/// then `other` will be chosen.
|
||||||
|
/// If the shape of `other` conflicts with `self`,
|
||||||
|
/// an appropriate [`AsgError`] will describe the problem.
|
||||||
fn try_adapt_to(
|
fn try_adapt_to(
|
||||||
self,
|
self,
|
||||||
other: TplShape,
|
other: TplShape,
|
||||||
tpl_name: Option<SPair>,
|
tpl_name: Option<SPair>,
|
||||||
) -> Result<Self, AsgError> {
|
) -> Result<Self, (Self, AsgError)> {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(TplShape::Expr(first_span), TplShape::Expr(bad_span)) => {
|
(TplShape::Expr(first_span), TplShape::Expr(bad_span)) => Err((
|
||||||
Err(AsgError::TplShapeExprMulti(tpl_name, bad_span, first_span))
|
self,
|
||||||
}
|
AsgError::TplShapeExprMulti(tpl_name, bad_span, first_span),
|
||||||
|
)),
|
||||||
|
|
||||||
// Higher levels of specificity take precedence.
|
// Higher levels of specificity take precedence.
|
||||||
(shape @ TplShape::Expr(_), TplShape::Empty)
|
(shape @ TplShape::Expr(_), TplShape::Empty)
|
||||||
|
@ -177,6 +194,14 @@ impl TplShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the shape stores [`Span`] information as evidence of inference,
|
||||||
|
/// overwrite it with the provided `span`.
|
||||||
|
///
|
||||||
|
/// This is most commonly used to encapsulate a previous shape
|
||||||
|
/// inference.
|
||||||
|
/// For example,
|
||||||
|
/// a template application's span may overwrite the inferred shape of
|
||||||
|
/// its own body.
|
||||||
fn overwrite_span_if_any(self, span: Span) -> Self {
|
fn overwrite_span_if_any(self, span: Span) -> Self {
|
||||||
match self {
|
match self {
|
||||||
TplShape::Empty | TplShape::Unknown => self,
|
TplShape::Empty | TplShape::Unknown => self,
|
||||||
|
@ -214,14 +239,9 @@ object_rel! {
|
||||||
let span = to_oi.resolve(asg).span();
|
let span = to_oi.resolve(asg).span();
|
||||||
|
|
||||||
from_oi.try_map_obj(asg, |tpl| {
|
from_oi.try_map_obj(asg, |tpl| {
|
||||||
let new = tpl
|
tpl.try_map(|shape| {
|
||||||
.shape()
|
shape.try_adapt_to(TplShape::Expr(span), tpl_name)
|
||||||
.try_adapt_to(TplShape::Expr(span), tpl_name);
|
})
|
||||||
|
|
||||||
match new {
|
|
||||||
Ok(shape) => Ok(tpl.overwrite(shape)),
|
|
||||||
Err(e) => Err((tpl, e)),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(commit(asg))
|
Ok(commit(asg))
|
||||||
|
@ -249,14 +269,9 @@ object_rel! {
|
||||||
|
|
||||||
// TODO: Refactor; very similar to Expr edge above.
|
// TODO: Refactor; very similar to Expr edge above.
|
||||||
from_oi.try_map_obj(asg, |tpl| {
|
from_oi.try_map_obj(asg, |tpl| {
|
||||||
let new = tpl
|
tpl.try_map(|shape| {
|
||||||
.shape()
|
shape.try_adapt_to(apply_shape, tpl_name)
|
||||||
.try_adapt_to(apply_shape, tpl_name);
|
})
|
||||||
|
|
||||||
match new {
|
|
||||||
Ok(shape) => Ok(tpl.overwrite(shape)),
|
|
||||||
Err(e) => Err((tpl, e)),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(commit(asg))
|
Ok(commit(asg))
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
/// This trait also provides a number of convenience methods that can be
|
/// This trait also provides a number of convenience methods that can be
|
||||||
/// implemented in terms of [`Map::map`].
|
/// implemented in terms of [`Map::map`].
|
||||||
///
|
///
|
||||||
|
/// If a mapping can fail,
|
||||||
|
/// see [`TryMap`].
|
||||||
|
///
|
||||||
/// Why a primitive `map` instead of `fmap`?
|
/// Why a primitive `map` instead of `fmap`?
|
||||||
/// ========================================
|
/// ========================================
|
||||||
/// One of the methods of this trait is [`Map::fmap`],
|
/// One of the methods of this trait is [`Map::fmap`],
|
||||||
|
@ -124,6 +127,59 @@ impl<T, U> Map<T, U> for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type providing a `try_map` function from inner type `T` to `U`.
|
||||||
|
///
|
||||||
|
/// This is a fallible version of [`Map`];
|
||||||
|
/// see that trait for more information.
|
||||||
|
pub trait TryMap<T, U = T>: Sized {
|
||||||
|
/// Type of object resulting from [`TryMap::try_map`] operation.
|
||||||
|
///
|
||||||
|
/// The term "target" originates from category theory,
|
||||||
|
/// representing the codomain of the functor.
|
||||||
|
type Target = Self;
|
||||||
|
|
||||||
|
/// Result of the mapping function.
|
||||||
|
type FnResult<E> = Result<T, (T, E)>;
|
||||||
|
|
||||||
|
/// Result of the entire map operation.
|
||||||
|
type Result<E> = Result<Self, (Self, E)>;
|
||||||
|
|
||||||
|
/// A structure-preserving map between types `T` and `U`.
|
||||||
|
///
|
||||||
|
/// This unwraps any number of `T` from `Self` and applies the
|
||||||
|
/// function `f` to transform it into `U`,
|
||||||
|
/// wrapping the result back up into [`Self`].
|
||||||
|
///
|
||||||
|
/// Since this method takes ownership over `self` rather than a mutable
|
||||||
|
/// reference,
|
||||||
|
/// [`Self::FnResult`] is expected to return some version of `T`
|
||||||
|
/// alongside the error `E`;
|
||||||
|
/// this is usually the original `self`,
|
||||||
|
/// but does not have to be.
|
||||||
|
/// Similarly,
|
||||||
|
/// [`Self::Result`] will also return [`Self`] in the event of an
|
||||||
|
/// error.
|
||||||
|
///
|
||||||
|
/// This is the only method that needs to be implemented on this trait;
|
||||||
|
/// all others are implemented in terms of it.
|
||||||
|
fn try_map<E>(
|
||||||
|
self,
|
||||||
|
f: impl FnOnce(T) -> Self::FnResult<E>,
|
||||||
|
) -> Self::Result<E>;
|
||||||
|
|
||||||
|
/// Curried form of [`TryMap::try_map`],
|
||||||
|
/// with arguments reversed.
|
||||||
|
///
|
||||||
|
/// `try_fmap` returns a unary closure that accepts an object of type
|
||||||
|
/// [`Self`].
|
||||||
|
/// This is more amenable to function composition and a point-free style.
|
||||||
|
fn try_fmap<E>(
|
||||||
|
f: impl FnOnce(T) -> Self::FnResult<E>,
|
||||||
|
) -> impl FnOnce(Self) -> Self::Result<E> {
|
||||||
|
move |x| x.try_map(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A nullary [`Fn`] delaying some computation.
|
/// A nullary [`Fn`] delaying some computation.
|
||||||
///
|
///
|
||||||
/// For the history and usage of this term in computing,
|
/// For the history and usage of this term in computing,
|
||||||
|
|
Loading…
Reference in New Issue