Boost logo

Boost :

Subject: Re: [boost] Yap's formal review is starting now!
From: Brook Milligan (brook_at_[hidden])
Date: 2018-02-12 17:28:21


> On Feb 8, 2018, at 2:15 PM, Zach Laine via Boost <boost_at_[hidden]> wrote:
>
> So, I have been very resistant to adding another new evaluation mode.
> Instead, I think something like this should be usable in most cases:
>
> // Let's assume the existence of a my_make_term() function that takes a Yap
> terminal
> // and a Context &, and returns a new Yap terminal.
> template <typename Context>
> struct transform_terminals_with_context
> {
> // Base case. Note that I'm ignoring placeholders entirely for this
> // example (they're easy to special-case if necessary).
> template <typename T>
> auto operator() (yap::terminal_tag, T && t)
> { return my_make_term(std::forward<T>(t), context_); }
>
> // Recursive case: Match any binary expression.
> template <typename Tag, typename LExpr, typename RExpr>
> auto operator() (Tag, LExpr const & left, RExpr const & right)
> {
> return yap::make_expression<yap::detail::kind_for<Tag>>(
> boost::yap::transform(left, *this),
> boost::yap::transform(right, *this));
> }
>
> // Recursive case: Match any unary expression.
> template <typename Tag, typename Expr>
> auto operator() (Tag, Expr const & expr)
> {
> return yap::make_expression<yap::detail::kind_for<Tag>>(
> boost::yap::transform(expr, *this));
> }
>
> // Ternary and call are added as necessary.
>
> Context & context_;
> };

So just to be sure, I made up what I think is a complete example to illustrate your main Yap idiom. The features I wanted to capture are:

- the evaluate(transform(expr,xform)) idiom

- move-only terminals

- terminals with no particular expression semantics that are transformed into values that have expression semantics

- a customization point in the transform to support arbitrary user-defined types being added to the transform

- ignore context-dependent transforms for now; given this pattern those are easy to support since a transform object is available everywhere a terminal is transformed into a value

In what follows, I am thinking of the "user" namespace as user-defined code and the "N" namespace as library code.

Have I missed anything of note?

Please also note that yap::detail::kind_for<Tag> in your code above should not be in the "detail" namespace as that is something that library writers building things upon Yap will be using and therefore it is not an implementation detail. I also think you didn't quite get the yap::make_expression<>() call correct in the above code.

Cheers,
Brook

#include <boost/yap/algorithm.hpp>

namespace user
{

  template < typename T = void >
  class UDT {};

  template < typename T, typename Transform >
  auto transform_terminal (UDT<T>, Transform) { return 1; }

} // namespace user

namespace N
{

  template <boost::yap::expr_kind Kind, typename Tuple>
  struct minimal_expr
  {
    static const boost::yap::expr_kind kind = Kind;
    Tuple elements;
    BOOST_YAP_USER_BINARY_OPERATOR_MEMBER(plus,::N::minimal_expr)
  };

  template < typename T = double >
  class number
  {
  public:
    number () = default;
    explicit number (T value) : value_(value) {}
    number (number const&) = delete;
    number (number&&) = default;
    auto value () const { return value_; }
  private:
    T value_ = 0;
  };

  template < typename Tag >
  class Kind;

  template <>
  struct Kind< boost::yap::plus_tag >
  {
    static constexpr boost::yap::expr_kind value = boost::yap::expr_kind::plus;
  };

  struct xform
  {
    // recursion: any binary operator
    template < typename Tag, typename Left, typename Right >
    decltype(auto) operator () (Tag, Left&& left, Right&& right) const
    {
      return boost::yap::make_expression<minimal_expr,Kind<Tag>::value>
        (boost::yap::transform(std::forward<Left>(left),*this),
         boost::yap::transform(std::forward<Right>(right),*this));
    }

    // base case: any terminal (requires transform_terminal() overload)
    template < typename Terminal >
    decltype(auto) operator () (boost::yap::terminal_tag, Terminal const& terminal) const
    { return transform_terminal(terminal,*this); }

    // base case: number<T>: this can be in the transform (as here) or
    // a transform_terminal overload (as for user::UDT)
    template < typename T >
    auto operator () (boost::yap::terminal_tag, number<T> const& n) const
    { return boost::yap::make_terminal<minimal_expr>(n.value()); }

    // ... more overloads for other terminals, unary operators, etc.
  };

  template < typename T >
  auto lit (T&& t)
  { return boost::yap::make_terminal<minimal_expr>(std::forward<T>(t)); }

} // namespace N

int main ()
{
  using boost::yap::evaluate;
  using boost::yap::transform;
  using N::lit;

  auto plus_expr = lit(N::number<>{41}) + user::UDT<>{};
  auto plus_result = evaluate(transform(plus_expr,N::xform{}));
  assert(plus_result == 42);

  return 0;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk