2
\$\begingroup\$

This is the first program I have written in Rust and I would like to know what some Rust experts think of it. I must be doing some stuff wrong, such as breaking code style, etc. The code differentiates simple expressions.

Two points of interest: there are verbose Box structures everywhere, and it makes the code less readable. Is there an alternative for this?

Second, I have a let ee = e.clone(); just so that I can print the value of e after calling diff. Coming from C++, Haskell, etc, this is quite strange. I understand that ownership is lost when the function is called, but I am hoping for a nicer way to do the print. One option I see is to let diff return the input as well, but this is strange.

use self::Expression::{Prod, Sum, Pow, Var, Cons};

#[derive(Debug, PartialEq, Clone)]
enum Expression {
    Cons(i32),
    Var(char),
    Prod(Box<Expression>, Box<Expression>),
    Sum(Box<Expression>, Box<Expression>),
    Pow(Box<Expression>, i32)
}

fn diff(e : Box<Expression>) -> Box<Expression> {
    match *e {
        Prod(ref a, ref b) => Box::new(Sum(Box::new(Prod(diff(a.clone()), b.clone())), 
            Box::new(Prod(a.clone(), diff(b.clone()))))),
        Sum(ref a, ref b) => Box::new(Sum(diff(a.clone()), diff(b.clone()))),
        // only valid for Var^n
        Pow(ref a, ref b) => Box::new(Prod(Box::new(Cons(*b)), Box::new(Pow(a.clone(), *b - 1)))),
        Var(_) => Box::new(Cons(1)),
        Cons(_) => Box::new(Cons(0)),
    }
}

fn main() {
    let e = Box::new(Prod(Box::new(Cons(5)), Box::new(Pow(Box::new(Var('x')), 2)))); // 5*x^2
    let ee = e.clone(); // this is ugly..
    let d = diff(e); // d/dx 5*x^2 = 0*x^2 + 5*2*x^1

    println!("Differentiating {:?} yields {:?}", ee, d);
}
\$\endgroup\$
  • \$\begingroup\$ To make life easier for reviewers, please add sufficient context to your question. The more you tell us about what your code does and what the purpose of doing that is, the easier it will be for reviewers to help you. See also this meta question \$\endgroup\$ – Simon Forsberg Mar 2 '15 at 15:06
3
\$\begingroup\$

I strongly recommend using Expression as your basic type, not Box<Expression> as you are. The boxing should be purely an implementation detail of the recursive type, it should not escape beyond that. For that reason, diff will return Expression rather than Box<Expression>; as far as the argument it takes is concerned, there are a couple of approaches that may be used:

  1. You may choose to take an Expression by value; in this case, stop using ref a et al. in the match patterns and just use a, thus improving efficiency ever so slightly in some of those cases, and reducing the number of clones needed by one (a in Pow).

  2. You may choose to take it by reference, &Expression. In this case, you won’t need to clone the entire expression e in main. This will require a mite less cloning than the first choice for your specific case.

There is little practical difference between the two; it is mostly just a matter of shuffling things around. I would probably go with the second, taking &Expression.

For the Pow(ref a, ref b) pattern, taking ref b rather than b, given b is i32, is rather pointless.

That is the basic stuff out of the way, and leaves us with this code (note a couple of minor stylistic things as well, such as e: instead of e :, and having a trailing comma after the last variant in the enum definition, and using visual indent on line 15):

use self::Expression::{Prod, Sum, Pow, Var, Cons};

#[derive(Debug, PartialEq, Clone)]
enum Expression {
    Cons(i32),
    Var(char),
    Prod(Box<Expression>, Box<Expression>),
    Sum(Box<Expression>, Box<Expression>),
    Pow(Box<Expression>, i32),
}

fn diff(e: &Expression) -> Expression {
    match *e {
        Prod(ref a, ref b) => Sum(Box::new(Prod(Box::new(diff(a)), b.clone())), 
                                  Box::new(Prod(a.clone(), Box::new(diff(b))))),
        Sum(ref a, ref b) => Sum(Box::new(diff(a)), Box::new(diff(b))),
        // only valid for Var^n
        Pow(ref a, b) => Prod(Box::new(Cons(b)), Box::new(Pow(a.clone(), b - 1))),
        Var(_) => Cons(1),
        Cons(_) => Cons(0),
    }
}

fn main() {
    let e = Prod(Box::new(Cons(5)), Box::new(Pow(Box::new(Var('x')), 2))); // 5*x^2
    let d = diff(&e); // d/dx 5*x^2 = 0*x^2 + 5*2*x^1

    println!("Differentiating {:?} yields {:?}", e, d);
}

Other alterations I would be inclined to make are making diff a method rather than a free function (fn diff(&self) -> Expression). Your naming is also inconsistent: the enum has a full-word name, Expression and not Expr, while the variants have short names, Cons and not Constant and so forth. I would recommend making them consistent.

I also have a personal fondness for struct variants, e.g. Power { base: Box<Expression>, exponent: i32 } instead of Power(Box<Expresion>, i32) (in patterns, Power(ref a, b) would become Power { ref base, exponent } with the values being bound to the better names base and exponent instead of a and b, another change you should consider anyway), but this is subjective.

Now for the big usability wins: implementing operators. Why should Expression not implement std::ops::{Add, Mul} to create Product and Sum? And while there is no power operator, why not add a similar method for it? These things can make it much simpler to look at and read. I’m only going to impl Add<Expression, Output = Expression> for Expression (i.e. Expression + Expression -> Expression) but it would be possible to also implement it for combinations of boxedness on Self and RHS, which could allow a.clone() instead of (**a).clone() in the Product differentiation below, but I do not believe it to be a particularly good idea; it is not any more efficient and clutters things ever so slightly; I think it is best if Box is kept purely an implementation detail.

We might as well also make a simple implementation of std::fmt::Display for bonus marks. std::fmt::Debug output gets a bit tiring to look at occasionally.

Well, here’s what I end up with:

use std::fmt;
use std::ops;
use self::Expression::{Product, Sum, Power, Variable, Constant};

#[derive(Debug, PartialEq, Clone)]
enum Expression {
    Constant(i32),
    Variable(char),
    Product(Box<Expression>, Box<Expression>),
    Sum(Box<Expression>, Box<Expression>),
    Power(Box<Expression>, i32),
}

impl ops::Mul for Expression {
    type Output = Expression;

    fn mul(self, rhs: Expression) -> Expression {
        Expression::Product(Box::new(self), Box::new(rhs))
    }
}

impl ops::Add for Expression {
    type Output = Expression;

    fn add(self, rhs: Expression) -> Expression {
        Expression::Sum(Box::new(self), Box::new(rhs))
    }
}

impl Expression {
    // Yeah, I’m calling this method pow because for a *method* it’s the standard name.
    fn pow(self, power: i32) -> Expression {
        Expression::Power(Box::new(self), power)
    }

    fn diff(&self) -> Expression {
        match *self {
            Product(ref a, ref b) => a.diff() * (**b).clone() + (**a).clone() * b.diff(),
            Sum(ref a, ref b) => a.diff() + b.diff(),
            // only valid for Variable^n
            Power(ref a, b) => Constant(b) * a.clone().pow(b - 1),
            Variable(_) => Constant(1),
            Constant(_) => Constant(0),
        }
    }
}

impl fmt::Display for Expression {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Product(ref a, ref b) => write!(f, "({} * {})", a, b),
            Sum(ref a, ref b) => write!(f, "({} + {})", a, b),
            Power(ref a, b) => write!(f, "({} ^ {})", a, b),
            Variable(v) => fmt::Display::fmt(&v, f),
            Constant(c) => fmt::Display::fmt(&c, f),
        }
    }
}

fn main() {
    let e = Constant(5) * Variable('x').pow(2);
    let d = e.diff(); // d/dx 5*x^2 = 0*x^2 + 5*2*x^1

    println!("Differentiating {} yields {}", e, d);
}

One final comment—if your power differentiation rule is only applicable to Variable^n (of course, mathematically that’s requiring that you are differentiating with regards to that variable) then it would be good to enforce that and fail rather than produce a bad result. One might even make diff take a char argument as the variable that you are differentiating with regards to, if one wished to make it more correct. All of this sort of thing might lead to you returning a Result from diff so that you can return an error if you wind up with something that you cannot differentiate.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.