-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gensym #126
Comments
And here's a
So if we need 3 temporary variables in a macro, we can use:
|
Okay, a few options:
I think 1 would be pretty simple in most languages (except languages like Haskell where you basically don't have globals). However I do like having "gensym-counter" as a user accessible symbol in the environment, and you certainly wouldn't want a new type to be introduced to wrap a stateful value just to avoid atoms until stepA. For 2, step8 generally isn't that large, especially since it's fairly similar in some ways to step7. So step8 would still be inline with the size/difficulty of the other steps. So this isn't a terrible option (a lot of tedious updates though). In the end though, I think I prefer 3. It's simple, it exposes the "gensym-counter" symbol using the standard mutable reference (atoms) and I think it also provides a good teaching opportunity (i.e. "here is a better way of writing the or macro now that we have gensym"). So for 3, here is the less efficient version of or that calls (first xs) twice:
What do you think? Maybe we should start a examples/macros.mal file for interesting macros (like with-gensyms)? |
I'm good with option 3. Just to clarify:
Once we have gensym, we can have the If you approve, I can take try to craft a PR with those changes (probably not today). |
So I wrote up some notes (included below), but in thinking through it some more, I think I'm actually reconsidering the options :-( The use of gensym is specifically related to macros. So thinking more about it, I think I do want to keep it with step8. Option 1 remains my least favorite (due to requiring a new type implementation), but I think something more like option 2 is now my preference. Since atom types are implemented in types/core modules (which are shared between steps), most implementations should be able to support atoms at an earlier step without much fuss. In fact, atom type support could could be moved even earlier than step8. I'm actually leaning towards putting them as step6 optionals. There are a couple of reasons for this. step6 is small step in terms of test size and implementation difficulty. But more importantly, step6 plus the atom type would in many ways be complete (albeit simple) Lisp. In fact, with a few additional core functions listed in later steps, step6 plus atom support could be self-hosted (the mal implementation would look somewhat different and more complicated because it wouldn't have the luxury of the "or"/"cond" macros or metadata). As you can see, I'm still sort of mulling this over and open to any thoughts on it. Also, whatever change we make, it's not set in stone of course (I've made many restructurings in the past and I'm sure will continue to do so into the future), but with 44 implementations, it does get more tedious to change things that affect all the implementations. If you buy into my reasons for step 2, here is what I think would be needed:
Sorry for going back and forth on this. Thoughts? Original response regarding step 3: Yep, I think that basically covers it. Additional thoughts:
|
I tried the atom tests on Ruby step 6 impl. It works except:
I think your idea (moving atoms to step 6) is OK. I wonder if we lose anything in terms of "look, we have a language with almost-only immutable data types" if we introduce atoms earlier. |
Sorry for the slow reply. Yep, the tests labelled ";; Testing hash-map evaluation and atoms (i.e. an env)" will need to go no earlier than step9 since that's where we add hash-map functions. I'm not too concerned about having atoms earlier in the process. It will actually give an earlier opportunity to emphasize the importance of immutability in the guide and describe atoms as the escape hatch when you absolutely need mutable state (and also to note that only the reference to data is mutable, not the data itself). You want to tackle a PR for that? I would do it but I'm moving and also have several school projects that I have to finish in the next 1.5 weeks. |
I'll take it, though it'll take me a few days as well. BTW, regarding immutability (just making sure I understand the ideas correctly): we do have |
@dubek yes, that's true that there are two different forms of mutable state supported by mal: environments (modified by def!), and atoms (modified by reset! and swap!). One place where mal diverges a bit from Clojure (and other Lisps for that matter) is that I wanted all types of mutating functions to be clearly identified with a "!" suffix. In Clojure for example, def* forms mutate the current namespace but the mutating nature is not explicitly identified with a "!". I suspect one reason that def/defines are not marked as mutating is that normal code doesn't often redefine things in a namespace after the first definition (you usually want to avoid that IMO). In Clojure atoms, refs and agents are all reference types that enable mutation but each one has different properties especially as related to concurrency. Technically vars are too, but you should really be using one of the other references types if you want mutable references. All that to say, even if there was a way to mutate outer environments in mal, I would still want to use an atom to wrap gensym-counter rather than mutating the environment. That reminds me, if I ever get around to implementing namespaces in mal, I will probably do it by extending the environment concept rather than creating a new type of thing in the language (as with Clojure namespaces and vars). The implementation of namespaces and vars is actually one of my biggest complaints with Clojure. As an example of how namespaces could extend environments without much fuss, consider a symbol "strings/replace". The "strings/" part could basically just be an alias to the environment that contains a "replace" function. Something like Clojure's (ns strings) at the beginning of a file would set up the alias. Anyways, just another random stream of consciousness. |
Now that #130 is done and merged, we have atoms implemented in step 6. This means we can introduce So my suggestion would be in step A, add something like this to the process guide (+ tests that show the problem with the older gensymThe Previously you used
For extra information read Peter Seibel's thorough discussion about |
Sorry, meant to reply earlier and it slipped my mind. I was thinking just doing the correct implementation of these in step8. However, I like the way you presented it and I think pointing out the bug and fixing it provides a good learning opportunity for stepA so let's go with your suggestion. Want to cook up a PR? |
|
(Following discussion in #103)
Here's a pure mal
gensym
implementation, and an implementation of theor
macro (calledor2
below) withgensym
:Output:
It relies on
atom
, so cannot be used in step 8 where we test theor
andand
macros (atom
is added in step A).Do you prefer a native impl in step 8, with all the quote special operators?
The text was updated successfully, but these errors were encountered: