10 ms·
Show HN: Risp – Lisp in Rust
- EamonnMR 7y agoI did almost the exact same thing (follow Norvig's Lisp tutorial, but in Rust) at one point, though I didn't get as far: https://github.com/eamonnmr/atmoslisp https://github.com/eamonnmr/atmoslisp
- stopachka 7y agoAwesome EamonnMR! Hope it helps kick it back up :}
- billsmithaustin 7y agoIn Lisp notation that would be (In List Rust) or maybe (In Rust List).
- stopachka 7y agoHeck yeah Bill :) -- you know what, will update the title on medium!
- jakeinspace 7y ago(reduce Rustify Lisp)
- dataking 7y ago> Finally, I have to say, I loved using Rust. It’s the least mental overhead I’ve had to maintain with a systems language, and it was a blast to use. This mirrors my experience learning Rust while working on the C2Rust translator (HN discussion https://news.ycombinator.com/item?id=17381946 https://news.ycombinator.com/item?id=17381946). Great read!
- stopachka 7y agoAwesome, thanks for the kind words dataking! :)
- Sandra435345 7y agosdfsdf
- deleted 7y ago[deleted]
- kaycebasques 7y agoI would’ve went with “Lust”...
- stopachka 7y agoeyes widen...nods -- ah, amazing name!
- zadler 7y agoI find humor in the choice of “risp” given the obvious alternative.
- sureaboutthis 7y agoI am positive someone would complain and ruin the fun for the vast majority of us normal people.
- ben509 7y agoIt would definitely hinder adoption in business.
- Lowkeyloki 7y agoIt's long been a dream of mine to make some software package that would be immensely useful and offer it for free with the catch being that the name is something childish and embarrassing and offensive. And then maybe offer a version of it with a nice, normal, appropriate name, but charge money for that version.
- msvan 7y agoIf you like Rust and Lisp this is also a cool project: https://github.com/carp-lang/Carp https://github.com/carp-lang/Carp.
- stopachka 7y agooo, very interesting! will take a deeper look, thanks! :)
- paulddraper 7y agoGlad to see the rule being proved, albeit intentionally :) > Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
- firethief 7y ago> `List(Vec<RispExp>)` The big question when implementing a runtime for a Lisp-like language in Rust is how the interpreter will interact with the GC. It looks like this project has avoided most of the complexity by not implementing actual lists, and allowing multiple references to the same value to exist only in restricted circumstances, hewing close to Rust's ownership model rather than a typical Lisp's. I wonder, is the plan to stay on this side of the space, as a lightweight language with potentially smooth FFI interoperation with other low-level/Rust code? Or to rewrite almost everything to support the high-level semantics of list-processing languages, with their emphasis on shared structure?
- stopachka 7y agoGreat question firethief! I think both approaches -- a) sticking close to rust and supporting beautiful interop, or b) creating our own GC and using interesting data structures (I've wanted to implement persistent vector tries for a while) -- could be very cool. For now, this was more of a toy project. If you have an interest in pushing either of these directions, would love to talk!
- Sandra435345 7y agoSuperb Online job opprtunity.Earn Upto $11000 to $15000 per month.Easy Work From Home Job to Making more Dollars Than you Expect. Its True. Just Visit This Site ------------> https://goo.gl/7CLERv https://goo.gl/7CLERv
- yellowapple 7y ago> I wonder, is the plan to stay on this side of the space, as a lightweight language with potentially smooth FFI interoperation with other low-level/Rust code? I personally hope this is the case; I feel like Lisps in that space are scarce. Strange interactions between the GC and "alien" functions are one of the things that make FFI painful even when using a Lisp with otherwise-thorough FFI support (like Chicken Scheme or most Common Lisps).
- firethief 7y ago
- TehCorwiz 7y agoI love the article, I actually tinkered with something like this a few moths ago. I tried to navigate back to HN using the back button, however it was inoperable. I found that passing every section heading on this article adds a line in your browsing history. Please don't mess with my browser history while I'm just scrolling. If I click on something that's fine, but not while I'm scrolling. I already have a history of where I am in your article, it's called the SCROLLBAR!
- stopachka 7y agore: article -- thank you for the kind words TehCorwiz :). re: scrollbar -- oi -- definitely did not intend that. Will look into this (I am hosted medium, so don't have much ability to move things around, but will see what I can do)
- TehCorwiz 7y agoThanks for the response. Yeah completely understand.
- uhryks 7y agoThis is a fairly recent addition to medium I think, but I also hate it a lot. This kind of behavior can make sense on news sites that load another article when you get to the bottom of the page, but adding history entries for each header you haven't interacted with is beyond words.
- hauleth 7y agoThere is also quite older, but still maintained other implementation https://github.com/murarth/ketos https://github.com/murarth/ketos
- codr7 7y agoEmbedding Lisp as a scripting language is a nice way to balance performance vs. expressive power from my experience. I recently started working on something similar [0] in Go. [0] https://github.com/codr7/g-fu https://github.com/codr7/g-fu
- stopachka 7y agoCheers codr7! You're project looks great -- you even got channels! Can't wait to go deeper.
- codr7 7y agoI sort of got them for free :) g-fu adds task isolation though, more along the lines of Erlang than Go. Remixing Lisp is one of the most interesting things you can do in software as far as I'm concerned.
- thenewwazoo 7y agoA bit of feedback to the author: I cringe really, really hard when I see things like: #[derive(Debug)] enum RispErr { Reason(String), } One of the beauties of Rust's type system is that you can combine meaningful error types with the From trait to build zero-effort error handling. When you turn every error into a String, you lose information that future programmers (including yourself!) will be glad to have. I know it can be a little bit painful to get used to, but once you do, meaningful error handling will be at your fingertips. To be helpful to readers of this comment and not just critical of the author, I recommend you don't overthink your error types when you're first starting to write your program. Think of the contents of the enum like a "scratchpad" where you list every failure mode you've encountered so far. While you're still developing, you can greedily match on every variant in your match statements, and it's fine. As you do this, you'll begin to notice themes that will inform the eventual refactor. Best of all, once you do refactor, you won't have to re-understand your code to tease out the various kinds of failures - you'll already have a list of them!
- stopachka 7y agoThanks for the feedback thenewwazoo! This sounds quite cool -- if you have the time, would you mind giving me a quick example? Want to make sure I have a full understanding of what you mean.
- thenewwazoo 7y agoBecause I'm on my soapbox, I'll also recommend implementing ToString[1] right off the bat, too. That way you can print out useful error messages immediately. For example, if your errors are: enum RispError { /// Syntax error returning line number and char SyntaxErr(u32, u32), /// Parens not balanced; contains number of parens needed UnbalancedParens(usize), } You can impl ToString straight away: impl ToString for RispErr { fn to_string(&self) -> String { match self { RispErr::SyntaxErr(l,c) => format!("syntax error at line {}, col {}", l, c), RispErr::UnbalancedParens(n) => format!("use your % key more! add {} more paren", n), } } } Then, when handling the error, you can match do_thing() { Ok(v) => { ... }, Err(e) => { // TODO handle error println!(e) } } where do_thing's signature looks like fn do_thing(...) -> Result<T, RispErr>; It takes the same amount of code, but all your error messages are in the same place, you haven't lost information about them, and refactoring to handle them becomes super easy. (n.b. I dashed this comment off without actually compiling the above, so please forgive any dumb errors =] ) [1] https://doc.rust-lang.org/std/string/trait.ToString.html https://doc.rust-lang.org/std/string/trait.ToString.html
- asdfman123 7y agoLisp in Rust? Have we reached peak HN?
- the_duke 7y agoNah, that will happen once someone implements a Javascript compiler written in a Scheme dialect with a VM which is written in Rust, with the entire stack compiling to WASM so it can run in the browser.
- eridius 7y agoPassing both a &[String] and an index into the function is duplicative, because a slice already has an index in it (the start point). It would be cleaner to just use `tokens.split_first()` to yank the first token off the list, which gives you both that first token plus the remainder, then you can pass the remainder into subsequent calls, and return the remainder at the end when you're done. Or, heck, you could even take a `&mut &[String]` instead, allowing you to mutate the slice directly and not have to return anything.
- stopachka 7y agoThis is a great suggestion eridius! I'll update it to use split_first. Note: updated the essay and left a thanks to you :)
- slowrabbit 7y agoShould've gone with Lust it's way better than Risp.
- kibwen 7y agoI thought this was just going to be another forgettable "X implemented in Y", but this is actually a Rust-flavored version of Peter Norvig's classic "(How to Write a (Lisp) Interpreter (in Python))", which is an absolute treasure. Glad I didn't just skip over this!
- stopachka 7y agoThank you for the kind words kibwen :)
- kovrik 7y ago`tokenize` function looks weird. Why use replace and then split? How would you extend this to stirngs (which might contain "()" chars)? It might be OK in a toy example, but it would have been better if the author wrote an actual Reader (not familiar with Rust, but I guess it should have some readers that allow peeking 1 char ahead OR something like Java's PushbackReader). Then `parse_list_of_floats`, if I am reading it right, this function is fail-safe, not fail-fast, right? It won't panic upon the very first reading error? Why eval makes environment lookups? Shouldn't environment itself know how to perform a lookup?
- stevekemp 7y agoIt's basically a clone of the tokenizer from this python implementation https://norvig.com/lispy.html https://norvig.com/lispy.html which was referenced in the introduction. The same code has been seen in a bunch of simple/similar lisp interpreters in different languages. To be honest I'm not sure if that was the first documented tokenizer using this simple approach, but it is definitely a common pattern for such things - and as you say it is naive at best, and buggy at worst. That said it is easy to get it working, and later fix it properly.
- kazinator 7y ago> It’s the least mental overhead ... Arguments to values in Risp: fn env_for_lambda<'a>( params: Rc<RispExp>, arg_forms: &[RispExp], outer_env: &'a mut RispEnv, ) -> Result<RispEnv<'a>, RispErr> { let ks = parse_list_of_symbol_strings(params)?; if ks.len() != arg_forms.len() { return Err( RispErr::Reason( format!("expected {} arguments, got {}", ks.len(), arg_forms.len()) ) ); } let vs = eval_forms(arg_forms, outer_env)?; let mut data: HashMap<String, RispExp> = HashMap::new(); for (k, v) in ks.iter().zip(vs.iter()) { data.insert(k.clone(), v.clone()); } Ok( RispEnv { data, outer: Some(outer_env), } ) } Arguments to values in TXR Lisp (interpreter), written in C: static void do_eval_args(val form, val env, val ctx, val (*lookup)(val env, val sym), struct args *args) { for (; form; form = cdr(form)) args_add(args, do_eval(car(form), env, ctx, lookup)); }
- rkangel 7y agoThey're not comparable. The C version contains no error handling, and off-loads most of its work to another function.
- kazinator 7y 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 7y 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?
- ridiculous_fish 7y agoRispEsp is a tree using value semantics. How will RispExp evolve to support e.g. circularly linked lists? How will that interact with Rust's ownership requirements?
- stshine 7y agoRust is the best lisp ever invented. Why bother another lisp inside it?
- kristianov 7y agoWhy is this named Risp? Lust is much better...
- encse 7y agoOne interesting way to extend this is to implement ‘call with current continuation’ or call/cc from Scheme. It’s a great challenge. I’ve done it in the past with rewriting the interpreter to continuation passing style. This also make it easy to implement breakpoints and a whole debugger for the interpreter. I still have it running here: https://csokavar.hu/projects/mini-scheme/ https://csokavar.hu/projects/mini-scheme/
- stopachka 7y agoGreat work encse -- love that you have a playground online for your language!
- sara7262 7y agoThey found world's biggest fish Even you can't believe how big fish is this That's unbelievable http://bit.ly/2FHl4pM http://bit.ly/2FHl4pM Even some goats are better than humans See that she did and they made her as mayor of city http://bit.ly/2UjTcS5 http://bit.ly/2UjTcS5 See the video a dog shopping and lots of other acts like humans http://bit.ly/2DCpo9I http://bit.ly/2DCpo9I Even you can't believe there is a hen in world's biggest animals See the video how they looks and what they do https://cutt.ly/1rOU7Q https://cutt.ly/1rOU7Q Fish walking with legs like humans See the video on link below http://bit.ly/2XRGyYD http://bit.ly/2XRGyYD
- hilaly 7y agoHahahaha Lust !
- etoipi1 7y agoI wonder why not call it Lisp + Rust
- _nalply 7y agoPerhaps because it's ... boring?