4 ms·
They're not comparable. The C version contains no error handling, and off-loads most of its work to another function.
by rkangel 8y ago
They're not comparable. The C version contains no error handling, and off-loads most of its work to another function.
- kazinator 8y agoWhat work does it offload to another function that the Rust function doesn't also? We can make a table: C function Rust analog args_add data.insert do_eval eval_forms Rather, the Rust function actually off-loads the evaluation work to another function, eval_forms. I should be comparing that one to do_eval_args: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr> { arg_forms .iter() .map(|x| eval(x, env)) .collect::<Result<Vec<RispExp>, RispErr>>() } That's a lot smaller, but still very noisy. My eyes bleed!
- burntsushi 8y agoTry this instead: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr>> { arg_forms.iter().map(|x| eval(x, env)).collect() } Or, if you don't like return value polymorphism, then: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr>> { let mut exps = vec![]; for x in arg_forms { exps.push(eval(x, env)?); } Ok(exps) } It looks no more noisy than your C code, and I can't actually tell whether your C code is doing error handling. Is it? If not, try adding it. Now which is noisier?
- kazinator 8y agoIt's at least twice as noisy. It has more operators, more different punctuators, < > brackets in addition to ( ) and { }. Not sure what error handling you have in mind, but it's robust. We can interactively call it from gdb with some garbage values: (gdb) r Starting program: /home/kaz/txr/txr-dbg This is the TXR Lisp interactive listener of TXR 215. Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet. 1> (raise 5) Program received signal SIGTRAP, Trace/breakpoint trap. 0x00132416 in __kernel_vsyscall () (gdb) p do_eval_args(9, 9, 9, 9) Too few arguments in function call. (gdb) p do_eval_args(9, 9, 9, 9, 9) ** car: 2 is not a cons ** during evaluation at expr-1:1 of form (raise 5) ** run with --backtrace to enable backtraces 2> _ The function relies on args having enough room for all the values; both callers ensure that. The lookup function can't be wrong, either.
- burntsushi 8y agoWhat a strange comment. When compared to my first Rust snippet in my previous comment, your C code has more parens, the same number of brackets, more semi-colons, more asterisks and more inscrutable function pointer syntax. Of course, I don't actually find C's syntax all that noisy, but I don't find Rust's syntax particularly noisy either. Probably because I know both languages. But at least be fair and try to look at both with virgin eyes instead of only one of them. The fact that it does error handling is not at all clear from its type signature, in contrast to Rust's function signature. So that's going to contribute "more noise" from your perspective, but on the flip side, it also conveys more information. Based on your demonstration, it looks like your function just aborts the program on an error, but the Rust function is a bit more versatile. It gives the caller a choice of how to deal with an error. Otherwise, I could just write this instead: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Vec<RispExp> { arg_forms.iter().map(|x| eval(x, env)).collect() }
- kazinator 8y agoSince we're working on Lisp internals, we can accept a few parentheses. The code has two asterisks, both in parameter declarations, indicating pointers. It has exactly one binary operator in the body, the assignment = denoting the one local side effect (stepping the iteration variable of the simple for (;;) loop). All else is simple function calls. Except for the function pointer declaration, and perhaps not knowing car and cdr, an Awk or JS programmer might grok this. The program doesn't abort; the exception was caught in the REPL. We were thrown right out of the GNU Debugger where we caused the problem, and back into the Lisp REPL. We can demonstrate that in other ways, like this: (gdb) r Starting program: /home/kaz/txr/txr-dbg This is the TXR Lisp interactive listener of TXR 215. Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet. 1> (catch (raise sig-trap) (error (x) (put-line `caught error: @x`))) Program received signal SIGTRAP, Trace/breakpoint trap. 0x00132416 in __kernel_vsyscall () (gdb) p car(9) caught error: car: 2 is not a cons t 2> (+ 2 2) 4 3> _ gdb gets confused here, though: 3> (exit 0) [Inferior 1 (process 8529) exited normally] The program being debugged exited while in a function called from GDB. Evaluation of the expression containing the function (car) will be abandoned. Quite understandably, it doesn't understand the exception handling and didn't notice that we jumped out; it still thinks we are executing the car function. Oh well!
- estebank 8y agoFYI, you don't need to use the turbofish to hint `rustc` what time you need to `collect` here because it takes it from the return type: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr> { arg_forms .iter() .map(|x| eval(x, env)) .collect() } You could also make the stylistic change (but not necessarily good change) of wrapping eval to make it less wordy: fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr> { let eval = |x| eval(x, env); arg_forms .iter() .map(eval) .collect() } At that point, it all fits in one line fn eval_forms(arg_forms: &[RispExp], env: &mut RispEnv) -> Result<Vec<RispExp>, RispErr> { let eval = |x| eval(x, env); arg_forms.iter().map(eval).collect() } And you could make it so that you don't need to call `iter` inside of the fn on `arg_forms` and potentially accept many things besides slices: fn eval_forms( arg_forms: impl Iterator<Item=RispExp>, env: &mut RispEnv, ) -> Result<Vec<RispExp>, RispErr> { let eval = |x| eval(x, env); arg_forms.map(eval).collect() } :)