TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Not everything is an expression

125 点作者 thomasballinger大约 10 年前

16 条评论

weavejester大约 10 年前
I&#x27;ve read through the article twice, and I still have no idea what the author is getting at.<p>The author suggests that &quot;the obvious way to implement a DSL as a macro, as we saw with if-match, hard-codes the form of the new syntax class&quot;. I disagree. That&#x27;s not what I&#x27;d consider the obvious way at all.<p>I&#x27;d consider the most obvious approach would be to pass the macro onto a polymorphic function of some description:<p><pre><code> (defmulti if-match* (fn [pat _ _ _] (if (list? pat) (first pat) (type pat))) (defmacro if-match [pat expr then else] (if-match* pat expr then else)) </code></pre> Macros have all the same capabilities for extensibility as regular functions. In Clojure at least, macros <i>are</i> just functions with some metadata attached.
评论 #9280571 未加载
评论 #9281424 未加载
kerkeslager大约 10 年前
This is an interesting approach.<p>I&#x27;m working on a Lisp variant that recognizes the difference between expressions and... non-expressions? But taking the opposite approach: I found a way to make it so that everything <i>is</i> an expression while allowing one to do everything you would do with a non-expression via expressions. To achieve this, there are two kinds of expression: mutations and functions.<p>There are two things to understand about this:<p>1. All expressions take in the environment. Most functions don&#x27;t use it, while most mutations do.<p>2. All expressions run inside a trampoline that evaluates them. The difference between a mutation and a function is that when the trampoline evaluates a function, it places its result into the return register (where it can be picked up by something else). In contrast, when the trampoline evaluates a mutation, it <i>replaces the environment with the result</i>. This is why mutations typically use the environment--rather than destroying the environment, you usually want to build the new environment with most of the old environment.<p>Some examples:<p><pre><code> ((mut () env (assoc env :foo 1))) ; equivalent to (define foo 1) ((mut () env (assoc env :my-define (mut (dest src) env (assoc env dest src))))) ; this is actually how `define` is defined ((mut () env (map))) ((+ 1 1)) ; throws exception &quot;undefined symbol +&quot; because previous line emptied the environment </code></pre> The &quot;everything takes env&quot; bit is inspired by J. Shutt&#x27;s paper on his Kernel programming language: <a href="https://www.wpi.edu/Pubs/ETD/Available/etd-090110-124904/unrestricted/jshutt.pdf" rel="nofollow">https:&#x2F;&#x2F;www.wpi.edu&#x2F;Pubs&#x2F;ETD&#x2F;Available&#x2F;etd-090110-124904&#x2F;unr...</a> and a lot of what I&#x27;m working on is built on his work.
评论 #9279661 未加载
评论 #9280207 未加载
ggchappell大约 10 年前
Interesting article. A few thoughts:<p>The fact that Lisp does not distinguish between statements and declarations is closely tied to the fact that Lisp is very much a dynamic language (in particular, it is dynamically typed). The article uses the example of Python declaration vs. statement; but actually Python declarations are statements, too. This is typical of dynamic languages.<p>On the other hand, in a statically typed language there is necessarily a distinction between code that is executed at runtime and (although we often don&#x27;t talk about it this way) code that is executed at compile time. Declarations happen at compile time. Expressions and statements happen at runtime. The two categories almost always use very different syntax.<p>Among statically typed languages, Haskell is particularly interesting, because, while it necessarily makes a strong distinction between expressions and declarations, it has erased the distinction between expression and statement: the latter is represented by an expression that returns a list of side effects.<p>Another interesting take on this issue can be found in Daan Leijen&#x27;s Koka programming language[1]. In Koka, whether a function has side effects is part of its type. So <i>effect inference</i> can be done. The result, if I understand things correctly, is that the expression-or-statement issue becomes more than just a yes&#x2F;no thing. I think these ideas are worth further exploration.<p>Lastly: an extensible pattern set. My goodness, yes. That&#x27;s the big lack I feel in Haskell; I want to define new kinds of patterns. I&#x27;ve read that F# has good support for this, but I know nothing about it; can anyone comment?<p>[1] <a href="http://research.microsoft.com/en-us/projects/koka/" rel="nofollow">http:&#x2F;&#x2F;research.microsoft.com&#x2F;en-us&#x2F;projects&#x2F;koka&#x2F;</a>
评论 #9279823 未加载
评论 #9280004 未加载
ThatGeoGuy大约 10 年前
I don&#x27;t mean to be a pedant, but the author mentions a &quot;syntax for patterns&quot;, basically claiming that Lisp doesn&#x27;t have one. But, isn&#x27;t syntax-rules (a la scheme) already a form for matching &#x2F; macro-ing patterns? From my understanding the author seems to want macros that can be specialized for new forms.<p>I may be confused about what the exact claim is here, but I don&#x27;t see how this has anything to do with whether or not something is an expression. I don&#x27;t quite understand how having &quot;not everything is an expression&quot; helps solve this problem.
评论 #9279630 未加载
ICWiener大约 10 年前
Macros will expand into lisp forms, not only expressions. Whether a form is an expression, a declaration or a pattern depends on the surrounding context.<p>I would say that declarations, ... are not syntax but semantic classes.<p>Too bad the conclusion does not offer a glimpse of what would the extension mechanism look like. Still, nice article.
评论 #9279050 未加载
评论 #9279665 未加载
endlessvoid94大约 10 年前
If you haven&#x27;t had the chance to read &quot;The Art of the Metaobject Protocol&quot; [0], I highly recommend it. It deserves to be mentioned anytime something like OMeta is mentioned.<p>[0] <a href="http://www.amazon.com/Art-Metaobject-Protocol-Gregor-Kiczales/dp/0262610744/ref=sr_1_1?ie=UTF8&amp;qid=1427490532&amp;sr=8-1&amp;keywords=the+art+of+the+metaobject+protocol" rel="nofollow">http:&#x2F;&#x2F;www.amazon.com&#x2F;Art-Metaobject-Protocol-Gregor-Kiczale...</a>
taeric大约 10 年前
While it is generally held that everything in lisp is an expression. Isn&#x27;t the more pertinant fact that everything is a list? That is, I thought macros hinged on the fact that everything is a list, not that everything is an expression.
评论 #9279035 未加载
评论 #9279988 未加载
spenczar5大约 10 年前
Recurse is really publishing some fantastic stuff. This entire second issue has been just great.
yawaramin大约 10 年前
Sure, not everything is an expression; but an expressive language provides an expression that can <i>contain</i> other syntax classes and confine all their effects within the bounds of the expression. E.g., SML&#x27;s &#x27;let&#x27; expression.<p>In a language that doesn&#x27;t allow that, we end up having to do some really weird things (<a href="https://github.com/yawaramin/lambdak" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;yawaramin&#x2F;lambdak</a>).
IshKebab大约 10 年前
Totally off-topic, but I was curious if the recursive Rust `sum` function is actually optimised correctly.<p>Code:<p><pre><code> fn sum(l: &amp;[i64]) -&gt; i64 { match l { [] =&gt; 0, [x, xs..] =&gt; x + sum(xs) } } </code></pre> Assembly:<p><pre><code> _ZN3sum20hf66fc5855a7cf5fc3aaE: .cfi_startproc cmpq %fs:112, %rsp ja .LBB2_2 movabsq $24, %r10 movabsq $0, %r11 callq __morestack retq .LBB2_2: pushq %rbx .Ltmp10: .cfi_def_cfa_offset 16 subq $16, %rsp .Ltmp11: .cfi_def_cfa_offset 32 .Ltmp12: .cfi_offset %rbx, -16 movq 8(%rdi), %rcx xorl %eax, %eax testq %rcx, %rcx je .LBB2_4 movq (%rdi), %rax decq %rcx movq (%rax), %rbx addq $8, %rax movq %rax, (%rsp) movq %rcx, 8(%rsp) leaq (%rsp), %rdi callq _ZN3sum20hf66fc5855a7cf5fc3aaE addq %rbx, %rax .LBB2_4: addq $16, %rsp popq %rbx retq </code></pre> So... no.
评论 #9281512 未加载
escherize大约 10 年前
I&#x27;m pretty confused by the author&#x27;s definition of statements. FTA: &quot;Statements are executed to take some action. Variable assignments, loops, conditionals, and raising exceptions are examples of statements.&quot;<p>With respect to variable assignments or raising exceptions (though examples exist of the others...) aren&#x27;t these by the author&#x27;s definition statements?<p><pre><code> (def a &quot;apple&quot;)</code></pre> or (throw (Exception. &quot;my exception message&quot;))
robgibbons大约 10 年前
It seems to me that it&#x27;s possible to define most statements in an expressive syntax, at least in any language which allows for both constructs.<p>For instance, in JavaScript one can use ternary syntax in place of an if-statement. Is a ternary condition actually an expression? It seems more of an expression than a statement, but one could argue it&#x27;s just a simplified syntax of a conditional statement.
siscia大约 10 年前
I don&#x27;t really get what the author is claiming...<p>I would have code his `if-mathch` in a simpler way in clojure:<p><pre><code> (case (f data-structure) 0 (do-something) 1 (do-something-else) (do-default)) </code></pre> Where `f` can be a function defined in a protocol or a multimethod, so you can actually implement your own `f` for any data structure you like.<p>Now, what I am missing ?
lispm大约 10 年前
If you look at the literature there are numerous examples of extensible macros. Often this is done for rule-based systems, which also involves matching or unification. Typically one wants to define these rules individually, update them individually, etc.<p>One needs a registry, an interning function and a driving function. Below is just an example:<p><pre><code> (defvar *patterns* (make-hash-table)) (defparameter *pattern-names* nil) (defun intern-pattern (name if-pattern then-pattern) (setf *pattern-names* (append *pattern-names* (list name))) (setf (gethash name *patterns*) (list (compile nil `(lambda (pat) ,if-pattern)) (compile nil `(lambda (pat expr then else) (declare (ignorable pat expr then else)) ,then-pattern)))) name) (defmacro if-match (pat expr then else) (loop for name in *pattern-names* for (if-part then-part) = (gethash name *patterns*) when (funcall if-part pat) do (return (funcall then-part pat expr then else)))) (intern-pattern &#x27;variable &#x27;(and pat (symbolp pat)) &#x27;`(let ((,pat ,expr)) ,then)) (intern-pattern &#x27;literal-atom &#x27;(atom pat) &#x27;`(if (equalp &#x27;,pat ,expr) ,then ,else)) (intern-pattern &#x27;cons &#x27;(eq &#x27;cons (car pat)) &#x27;(destructuring-bind (_ p-car p-cdr) pat (declare (ignore _)) (let ((tmp (gensym))) `(let ((,tmp ,expr)) (if (consp ,tmp) (if-match ,p-car (car ,tmp) (if-match ,p-cdr (cdr ,tmp) ,then ,else) ,else) ,else))))) </code></pre> Writing the macro DEFPATTERN is then trivial...<p>I help maintain an old Lisp-based web server, which was written in the mid-90s on the Symbolics Lisp Machine. It literally has zillions of these registry&#x2F;intern&#x2F;machinery&#x2F;defining-macro combinations...<p>It&#x27;s just: one has to program those. But it has been done many many many times.
gumby大约 10 年前
Not sure why it&#x27;s a surprise that languages inherently require metasyntactic operations. This has been clear since Church and Gödel.<p>There&#x27;s lot of good work on programming languages that allow metasyntactic runtime extensions, going back to Brian Smith&#x27;s work at PARC.
zem大约 10 年前
minor nitpick - even if &quot;OCaml has no equivalent of Haskell&#x27;s Ordering or SML&#x27;s order types&quot; there is Pervasives.compare. it returns {0,-1,+1} rather than {EQ, LT, GT}, but you can still say<p><pre><code> match compare x v with | 0 -&gt; ... | 1 -&gt; ... | _ -&gt; ... </code></pre> with the minor wart that since compare returns an int, you need to match the last case with a default to prevent the compiler from warning you about possibly not matching other int values.