I really, really like Clojure-style maps:
{:keyword "value"
:keyword2 'value2}
which can be used like
(get my-map :keyword)
or, even better,
(:keyword my-map)
Can I teach Elisp how to do this?
I really, really like Clojure-style maps:
{:keyword "value"
:keyword2 'value2}
which can be used like
(get my-map :keyword)
or, even better,
(:keyword my-map)
Can I teach Elisp how to do this?
I don't believe it's possible to teach Elisp to default to this behavior, but you can write a function that does the job for a defined set of keywords:
(defun defgetters (&rest keywords)
(when-let ((this-keyword (car keywords)))
(defalias this-keyword
(lambda (plist) (plist-get plist this-keyword)))
(apply #'defgetters (cdr keywords))))
You can do something like this with a lexical closure also.
#+BEGIN_SRC emacs-lisp :lexical t :results raw
(let ((data '(:keyword "value" :keyword2 'value2)))
(defalias 'd
(lambda (key)
(plist-get data key))))
(d :keyword2)
#+END_SRC
#+RESULTS:
(quote value2)
(:some-keyword '(:a 2 :some-keyword some-symbol))
yields some-symbol
.
Apr 18, 2017 at 16:18
I think to get this to work the way you want you have to change the elisp reader.
There is a proof of concept here: https://github.com/mishoo/elisp-reader.el
To use it, we define a new syntax like the one you want from clojure anyway. Then, while reading it define the keywords on the fly.
(require 'elisp-reader)
(def-reader-syntax ?{
(lambda (in ch)
(let ((list (er-read-list in ?} t)))
(cl-loop for (key val) on list by #'cddr
do
(message "making %s" key)
(defalias key
`(lambda (lst)
(cdr (assoc ,key lst)))))
`(list ,@(cl-loop for (key val) on list by #'cddr
collect `(cons ,key ,val))))))
(setq d { :foo 1 :bar "string"})
(:bar d) ; -> string
(:foo d) ; -> 1
(:foo { :foo 5 }) ;-> 5
This seems to work for me.
Here is another approach to define functions with a limited scope. It might be a mistake to make global functions for a dictionary because you might get name clashes/shadowing for different dictionaries. This macro creates a scope where the keyword functions exist. The syntax isn't quite what you asked for; it is simpler because you don't need to pass the mapping as an argument here.
(defmacro with-dict (key-vals &rest body)
(let* ((g (if (symbolp key-vals)
(symbol-value key-vals)
key-vals))
(keys (-slice g 0 nil 2)))
`(labels ,(loop for key in keys
collect
(list key '() `(plist-get ',g ,key)))
,@body)))
(with-dict (:a 1 :b 'some-symbol :c 3)
(:b))
You can override macroexpand
to rewrite forms like (:keyword foo)
into (plist-get :keyword foo)
. This code is inspired by the cl-symbol-macrolet
implementation. It worked when I tried it, but I would not be surprised to learn that it breaks things.
(defun macroexpand+keyword (macroexpand-orig exp &optional env)
(let ((macroexpand-all-environment env))
(while (pcase (funcall macroexpand-orig exp env)
(`(,(and kw (guard (keywordp kw))) ,plistform)
(setq exp `(plist-get ,plistform ,kw))))))
exp)
(advice-add 'macroexpand :around #'macroexpand+keyword)
(advice-add 'macroexpand-1 :around #'macroexpand+keyword)
(require 'cl-lib)
(defun macroexpand-first (eval-args)
(cl-callf macroexpand (nth 0 eval-args))
eval-args)
(let ((my-map '(:one 1 :two 2)))
(:one my-map)) ;=> 1
pcase
pattern: (guard BOOLEXP) matches if BOOLEXP evaluates to non-nil.
eval
(which seems to be what lispy-eval
does). I've now added advice to eval
as well, what could possibly go wrong? :P