tamer: f: Add TryMap

This implements TryMap and utilizes it in `asg::graph::object::tpl`.

DEV-13163
main
Mike Gerwitz 2023-07-27 01:44:12 -04:00
parent 38c0161257
commit 3c9e1add20
2 changed files with 95 additions and 24 deletions

View File

@ -22,7 +22,11 @@
use std::fmt::Display;
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.
#[derive(Debug, PartialEq, Eq)]
@ -54,10 +58,16 @@ impl Map<Span> for Tpl {
}
}
impl Map<TplShape> for Tpl {
fn map(self, f: impl FnOnce(TplShape) -> TplShape) -> Self::Target {
impl TryMap<TplShape> for Tpl {
fn try_map<E>(
self,
f: impl FnOnce(TplShape) -> Self::FnResult<E>,
) -> Self::Result<E> {
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 {
/// 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(
self,
other: TplShape,
tpl_name: Option<SPair>,
) -> Result<Self, AsgError> {
) -> Result<Self, (Self, AsgError)> {
match (self, other) {
(TplShape::Expr(first_span), TplShape::Expr(bad_span)) => {
Err(AsgError::TplShapeExprMulti(tpl_name, bad_span, first_span))
}
(TplShape::Expr(first_span), TplShape::Expr(bad_span)) => Err((
self,
AsgError::TplShapeExprMulti(tpl_name, bad_span, first_span),
)),
// Higher levels of specificity take precedence.
(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 {
match self {
TplShape::Empty | TplShape::Unknown => self,
@ -214,14 +239,9 @@ object_rel! {
let span = to_oi.resolve(asg).span();
from_oi.try_map_obj(asg, |tpl| {
let new = tpl
.shape()
.try_adapt_to(TplShape::Expr(span), tpl_name);
match new {
Ok(shape) => Ok(tpl.overwrite(shape)),
Err(e) => Err((tpl, e)),
}
tpl.try_map(|shape| {
shape.try_adapt_to(TplShape::Expr(span), tpl_name)
})
})?;
Ok(commit(asg))
@ -249,14 +269,9 @@ object_rel! {
// TODO: Refactor; very similar to Expr edge above.
from_oi.try_map_obj(asg, |tpl| {
let new = tpl
.shape()
.try_adapt_to(apply_shape, tpl_name);
match new {
Ok(shape) => Ok(tpl.overwrite(shape)),
Err(e) => Err((tpl, e)),
}
tpl.try_map(|shape| {
shape.try_adapt_to(apply_shape, tpl_name)
})
})?;
Ok(commit(asg))

View File

@ -51,6 +51,9 @@
/// This trait also provides a number of convenience methods that can be
/// implemented in terms of [`Map::map`].
///
/// If a mapping can fail,
/// see [`TryMap`].
///
/// Why a primitive `map` instead of `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.
///
/// For the history and usage of this term in computing,