FemtoEmacs: Tiny Emacs clone with configuration in FemtoLisp
github.com
github.com
>> julia --lisp
; _
; |_ _ _ |_ _ | . _ _
; | (-||||_(_)|__|_)|_)
;-------------------|----------------------------------------------------------
> (+ 1 2)
3
> Total Physical Source Lines of Code (SLOC) = 499,088
Development Effort Estimate, Person-Years (Person-Months) = 136.18 (1,634.17)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months) = 3.47 (41.59)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule) = 39.29
Total Estimated Cost to Develop = $ 18,396,178
(average salary = $56,286/year, overhead = 2.40).
SLOCCount, Copyright (C) 2001-2004 David A. WheelerFor some. For others a clone that does 20% of what Emacs does and has a few nice plugins could be enough.
Comparatively, I've found that x-compiling vanilla emacs `--without-x` is often more easier and faster.
X-compiling Mg is more or less "gcc *.c" which is very straight-forward.
I don't use many advanced emacs features; this only covers basic editing (moving around, cutting and yanking, and macros) but it's the smallest subset I need to be productive.
I should look into this. I wonder how it compares.
[0]: http://www.jedsoft.org/jed/ [1]: http://www.jedsoft.org/slang/
Why not run Emacs on local machine with plink-ssh to remote?
TRAMP enables this pretty well …
[1] As a clojure library - https://github.com/brandonbloom/backtick/blob/master/src/bac... (although you have to rewrite lines 51-56 to use explicit gensyms in a let instead of using the internal syntax-quote so that it bootstraps cleanly).
[2] Using the alternate Clojure reader - https://github.com/clojure/tools.reader/blob/master/src/main...
[3] Using Hylang - https://github.com/hylang/hy/blob/master/hy/core/macros.hy#L...
Most people think that the only axis macro systems rest on is hygenic vs non-hygenic:
Hygenic |Non-hygenic
syntax-rules|defmacro and gensym
This actually isn't true. Macro systems rest on two axies: Hygenic vs Non-Hygenic, and Lowlevel vs Highlevel: Hygenic Lowlevel |Non-hygenic Lowlevel
er-macro-transformer|defmacro and gensym
ir-macro-transformer|
sc-macro-transformer|
--------------------+----------------------
Hygenic highlevel |Non-hygenic highlevel
syntax-rules |???
Highlevel macros use declarative pattern-matching. Lowlevel macros are imperative. Hygenic macros guarantee that there will be no name-clashes in a rename. Depending on your language, gensym may or may not do this, but usually does. However, hygenic macros also guarantee that all renamed expressions take on the value they have at in the environment within which the expansion occurs. Gensym does not, and never will, do this. This is acceptable in a Lisp-2 with packages (barely), but in a Lisp-1 of any sort, there is an extreme risk of function shadowing, and other nasty bugs (see http://community.schemewiki.org/?hygiene-versus-gensym for details).Hopefully, this explains why I insist on hygenic macros, and why I think PG really screwed up by not adding hygenic lowlevel macros to arc.
syntax-quote as implemented by clojure makes three guarantees:
(a) symbols in the body that are postfixed by `#` will be uniformly replaced by a unique gensym in the expansion
(b) unquoted things are left alone
(c) any other symbol that is not the result of unquote or auto-gensyming, is fully namespace-qualified
In so far as is possible with a mutable function namespace (and clojure has this, you can alter the contents of vars and add/remove vars from namespaces), the syntax-quote in Clojure provides the environment within the expansion occurs as well as guaranteeing no name clashes (unless you explicitly opt into them, e.g. unquoting a quoted symbol).
If you wanted to do something similar to what the article mentions for common lisp, you end up at:
(intern (ns-name 'clojure.core)
'symbol-you-want-to-replace
(fn [x] alt-definition-here))
I guess someone technically could do that, but if they do, I would rather my language err towards giving me the power to make bad decisions than hamstring my ability to do useful things.In summary, as long as you have lexical scoping (which FemtoLisp claims to) or namespaces of any sort, you can solve all but the most contrived problems of something like syntax-rules without the additional complexity and restrictions. Again, this can just be added to the language by the user, if they really want it.
This comes back to a larger question of, if everything about a language is interesting except its macro system, wouldn't you be well-served to just bolt on the macro system that you desire? (Not saying that FemtoLisp is for you.) Seems that a hygienic vs non-hygienic macro system is fairly trivial if you care enough about the other features.
(fn a_func [x]
(print x))
(defmacro a_macro [y]
`(a_func ~y))
(let ((a_func (fn [x] (print "oh noes!"))))
(a_macro "this is a string"))
;;;if I'm right, should print "oh noes!"
If this does work properly, and no variant of it works, than congrats clojure, you have hygene, or close enough.>I guess someone technically could do that, but if they do, I would rather my language err towards giving me the power to make bad decisions than hamstring my ability to do useful things.
Which is why I prefer low-level hygenic macros, like er/ir/sc-macro-transformers: I CAN break hygene whenever I want to, but I have hygene when I want it, and lack of hygene when I don't. Despite what Doug Hoyte may say, I don't have my hands behind my back.
>In summary, as long as you have lexical scoping (which FemtoLisp claims to) or namespaces of any sort, you can solve all but the most contrived problems of something like syntax-rules without the additional complexity and restrictions.
What additional complexity? With ir-macro-transformer (not mentioned in the article), my macros are as clean as syntax-quote, and it's not that hard to learn.
Also, what additional restrictions? I don't see any.
>This comes back to a larger question of, if everything about a language is interesting except its macro system, wouldn't you be well-served to just bolt on the macro system that you desire? (Not saying that FemtoLisp is for you.) Seems that a hygenic vs non-hygenic macro system is fairly trivial if you care enough about the other features.
Well, femtolisp IS pretty cool, and if you hack together enough code, you can make a system that's almost hygenic, but I'm not sure if you can actually add hygene without the compiler's co-operation. Although gensym may be close enough as far as co-operation goes...
I would need to look into this further.
(defmacro a_macro [y] `(a_func ~y))
At this point, let won't allow you to rebind a_func (it changes the lexical scope, and the syntax-quote is going to refer specifically to ns/a_func). binding will also not allow it in modern versions of clojure, unless you declare the a_func var as ^:dynamic (e.g. ``` (defn ^:dynamic a_func [x] (print x)) ``` (as an aside, the compiler will also warn you for making something dynamic that doesn't have earmuffs, which is clojure convention)).
(ns user (:use clojure.test))
(def a 5)
(defmacro inc-by-a [x] `(+ a ~x))
(is (= (inc-by-a 0) 5))
(is (= (let [a 0] (inc-by-a 0)) 5))
;; the is-macro implementation of thrown? doesn't catch
;; IllegalStateException...
(defmacro actually-throws?
[exception & body]
`(try (do (do ~@body) false)
(catch ~exception t# true)
(catch Throwable t# false)))
(is (actually-throws? java.lang.IllegalStateException
(binding [a 0] (inc-by-a 0))))
;; if we opt into this madness, all bets are off
(def ^:dynamic a 5)
(is (= (binding [a 0] (inc-by-a 0)) 0))I thusly conclude that Clojure's macro system, while hygenic, is inferior to most of the scheme macro systems, which would do The Right Thing in this case, without you having to append every function with a hash. Except for er, of course, where rename is explicit.
Hygiene in this context was coined by Henk Barendregt in the context of the lambda calculus:
> For reasons of hygiene, it will always be assumed that the bound variables that occur in a certain expression are different from the free ones. [0]
This may seem nonsensical at first, but consider two different functions, f(x) and g(x): although both f and g take an x, they are in fact different variables. So, if some expression has x free, and another has x bound, and one is substituted into the other, you must remember to differentiate between the two homonyms. One way to do this is to consistently rename one or both variables so that they no longer appear to be the same, but it's important not to get too hung up on the concept of renaming per se, because the only requirement is that they be regarded as different (Andre van Tonder's delightful implementation of syntax-case uses a painting algorithm rather than a renaming one, but the net effect is the same).
In the case of syntactic transformers (macros), we're not concerned about the variables of the transformer itself, but rather the variables of the transformed code. Consider that full macro expansion occurs as a sequence of single expansions (transcription steps) until no further expansion can be done. Now, a macro is hygienic if all identifiers introduced during any given transcription step are unique to that transcription step (regardless of name). The introduced identifiers are analogized with bound variables while all other identifiers are analogized with free variables, just like how a lambda abstraction introduces its parameter variable.
Note that even unhygienic macro systems are capable of expressing at least some hygienic macros: macros that do not introduce any identifiers are trivially hygienic. I contend that the explicit-renaming macro system is not actually hygienic, even though it allows you to write any hygienic macro. The reason being that, in essence, hygienic macro expansion is precisely the notion of lexical scoping applied to syntactic transformation; and just as we don't need to manually specify the scope of each variable in our basis language, we should not need to do so in our meta language. For the same reason, Clojure's requirement to gensym identifiers -- while it offers convenient shorthand for the operation, it is still explicit (and it solves only one of the two problems of unhygienic expansion, namely variable capture, while providing no protection from shadowing). In that vein, syntax-rules, syntax-case, and implicit-renaming macros all fit the bill.
Just as dynamically-scoped variables can come in handy sometimes even in a lexically-scoped language (many Lisps have them, variously called "fluid variables", "special variables", or "parameters"), unhygienic injection of identifiers can come in handy sometimes (the classic example being anaphoric macros). The traditional way to do this is to simply circumvent, or "break", hygiene for a particular identifier, but Eli Barzilay and crew have also come up with the notion of "syntactic parameters" analogous to dynamically-scoped variables.
[0]: Henk Barendregt, "Introduction to the Lambda Calculus".
I'd say I'd check out what Eli &co did, but I probably won't. TBH, Racket leaves a bad taste in my mouth: the core is too complex for such little benefit. If some of it were in libraries, I wouldn't mind so much, but most of it's there by default. And I share Alex Shinn's opinions on syntax-case.
It's hygienic in that it allows you to write any hygienic macro, yes. However, it's not "hygienic by default" -- you have to go out of your way to manually and explicitly ensure your er-macros are hygienic. Leave out a ,(rename 'foo) somewhere, and suddenly you've broken hygiene. Same problem with gensym, really, except that rename is a little nicer to use. Compare with syntax-rules, syntax-case, or ir-macros, where any macro you write is hygienic automatically and by default -- you have to go out of your way to manually and explicitly inject free identifiers.
> I'd say I'd check out what Eli &co did, but I probably won't. TBH, Racket leaves a bad taste in my mouth: the core is too complex for such little benefit.
It's also a proposed addition for R7RS-large (SRFI-139).
> If some of it were in libraries, I wouldn't mind so much, but most of it's there by default.
It's all in libraries. `#lang racket` is just a "metalibrary" that exports a bunch of smaller libraries. You can always do `#lang racket/base` and include the subset of libraries you need.
> And I share Alex Shinn's opinions on syntax-case.
Not to talk shit about Alex -- he's a smart guy and Chibi is a very nice system -- but he's both seriously misinformed about syntax-case and hopelessly obstinate about it. Despite his claims that syntax-case is super complicated to implement, it's really not much more complicated than syntax-rules: it uses the same pattern language, but rather than a template, it's associated with an expression. The only thing "extra" that's needed is a disjoint `syntax` type, which is just a wrapper around the other primitives with a bit of implementation-defined context (the crucial bit of context, for purposes of hygiene, being the syntactic environment; apart from API, a `syntax` value is really just a syntactic closure). The thing is, syntax-case has been shown to be able to express anything er-macros and synclos can express, but the reverse is not true, meaning that it's very possible that syntax-case is strictly more powerful than the alternatives.
I honestly think Dybvig and company did a disservice to syntax-case by making psyntax so complicated. I think one of their goals was to make psyntax production-quality, but I think it would have been better to make it as simple as possible -- just the bare essentials, for illustrative purposes. People tend to take it as exemplary because it's what they've got, but it really is a bad example because in its desire to be real-world and practical it obscures the intuition behind the system. Dybvig's entry in "Beautiful Code"[0] helps considerably, but I still maintain that the essence of the system is simpler than it's portrayed even there.
While I think that ir-macros are often very nice, there's no denying that the pattern matching offered by syntax-rules/syntax-case is extremely convenient and, more often than not, extremely clear. Many of the macros that are full of cadrs and caaaddddrs and cddaaars find a much more convenient and readable expression in the pattern language.
>However, it's not "hygienic by default" -- you have to go out of your way to manually and explicitly ensure your er-macros are hygienic. Leave out a ,(rename 'foo) somewhere, and suddenly you've broken hygiene. Same problem with gensym, really, except that rename is a little nicer to use.
Well, I didn't say it was a great system, I just said if was hygenic, for the given value of hygenic.
>It's also a proposed addition for R7RS-large (SRFI-139).
Thanks. That actually does look pretty neat, although a bit odd in some ways.
>It's all in libraries. `#lang racket` is just a "metalibrary" that exports a bunch of smaller libraries. You can always do `#lang racket/base` and include the subset of libraries you need.
Well, there is that... But I'm still not a huge fan of Racket: I dislike where they diverged from Scheme (mcons? really?), syntax-case STILL leaves a bad taste in may mouth (see below), and the system as a whole just feels overly baroque to me.
>Not to talk shit about Alex -- he's a smart guy and Chibi is a very nice system -- but he's both seriously misinformed about syntax-case and hopelessly obstinate about it. Despite his claims that syntax-case is super complicated to implement, it's really not much more complicated than syntax-rules:
...I don't think that was Alex's objection to syntax-rules. It isn't mine. My objections are as follows:
-Breaking the standard macro abstraction by saying that expressions aren't, in fact, sexprs.
-Presenting an overy complex API to the programmer: lots and lots of functions, and a whole new read syntax.
-the (lambda (stx)) wrapper basically breaks every existing macroexpander, which, if it had a defined format in a given Scheme, probably used the format defined by sc. Why define that anyways?
http://www.cs.utah.edu/plt/scope-sets/
The new model has also been used by Tim Disney and others to make macro expander for JavaScript (see the sweet.js project).But this is just a concrete instance of my opinion of Racket in a nutshell: Cool ideas, but sometimes overly complex for my liking, interesting systems that don't always mix well, a syntax over the ideas that I find distasteful on occasion, and documentation that is comprehensive, but hard to understand.
It's okay if you like it, and it's a good language for Getting S%#! Done, but it's not really my thing: I prefer Chicken: Simpler, less academic and cutting edge, but still really good at Getting S%#! Done. But that's my problem, not Racket's
It is also nice to have a CL package, which makes porting of relatively easy.
And FemtoLisp is a beautiful thing, of course.
As a daily almost-everything-in-emacs user, I definitely understand the shortcomings of elisp, but generally speaking it does the job. Yeah guile would be an improvement, but I'm not sure it is worth rocking the boat.
It doesn't seem that the idea that elisp needs replacing has that much currency.