Most of those errors are saying it can't find a global variable named "stack". That's because when you do (= stack (newSA)), you're creating a global variable called "stack" in Arc but it's called "_stack" in Racket. In your macros, you're generating Racket code that uses the Arc variable name, so it's looking for a "stack" Racket global that doesn't exist. You can potentially fix this in your macros... but why use macros when you already have functions that do what you want? :)
It's fast because it's not bloated. It returns fairly minimal (if not elegant) HTML, and loads few additional resources.
The HN front page is about 7kB - many times smaller than even minimized jQuery (83kB as of 2.1.4).
The data is small and all fits in RAM on a single server during normal operation, so there's no additional network latency hitting a DB over the network.
I think those simplifying assumptions are consistent with what dagfroberg was saying. I think "Polish notation" and "shunting-yard algorithm" make sense as terminology here, even if dagfroberg and I might each have slight variations in mind.
---
"so no higher-order functions"
I think we can get read-time arity information for local variables, but only if our read-time arity information for the lambda syntax is detailed enough to tell us how to obtain the arity information for its parameter. For instance...
-- To parse 3: We've completed an expression.
3 =:: Expr
-- To parse +: Parse an expression. Parse an expression. We've
-- completed an expression.
+ =:: Expr -> Expr -> Expr
-- To parse fn: Parse a variable. Parse an expression where that
-- variable is parsed by (We've completed an expression.). We've
-- completed an expression.
fn =:: (arg : Var) -> LetArity arg Expr Expr -> Expr
-- To parse letArity: Parse a variable. Parse an arity specification.
-- Parse an expression where that variable is parsed by that arity
-- specification. We've completed an expression.
letArity =:: (arg : Var) -> Arity n -> LetArity arg n Expr -> Expr
-- To parse arityExpr: We've completed a specification of arity
-- (Expr).
arityExpr =:: Arity Expr
-- To parse arityArrow: Parse a specification of some arity m. Parse a
-- specification of some arity n. We've completed a specification of
-- arity (Parse according to arity specification m. We've completed a
-- parse according to arity specification n.).
arityArrow =:: Arity m -> Arity n -> Arity (m -> n)
-- ...likewise for many of the other syntaxes I'm using in these arity
-- specifications, which isn't to say they're all *easy* or even
-- *possible* to specify in this metacircular way...
This extensible parser is pretty obviously turning into a half parser, half static type system, and now we have two big design rabbit holes for the price of one. :)
Notably, Telegram's binary protocol actually takes an approach that seems related to this, using dependent type declarations as part of the protocol: https://core.telegram.org/mtproto/TL
"We can't parse ... unless we know all the operators that it uses."
Yes, that was my reaction as well. I thought https://en.wikipedia.org/wiki/Polish_notation requires either a clear separation between operators and values (so no higher-order functions) or all operators to be binary. Parentheses (whether of the lisp or ML or other variety) permit mult-iary higher-order operations.
Also, doesn't https://en.wikipedia.org/wiki/Shunting-yard_algorithm require some sort of delimiter between operations? I thought it didn't work for nested operations without commas or parens, or the simplifying assumptions in the previous paragraph.
Much of what you're saying sounds feasible and even familiar. Removing parens from Lisp and adding infix operators is a popular idea, popular enough that we've made a list of projects that explore the possibilities:
As I've learned more about the ML family of language syntax (e.g. SML, OCaml, Haskell, Elm, Agda, Idris), I've come to the conclusion that ML-style language designs treat their syntax as s-expressions anyway. Where Lisp function call expressions are nested lists, ML expressions are nested "spines." ML languages treat infix spines as sugar for prefix spines, just like we're talking about for Lisp.
gcd (a + b) (c - square d) (* ML *)
(gcd (+ a b) (- c (square d))) ; Lisp
(gcd ((+) a b) ((-) c (square d))) (* ML again *)
In terms of precedence, ML's prefix calls associate more tightly than infix ones. That's the other way around from Arc, where the infix syntaxes associate more tightly than the prefix ones:
(rev:map testify!yes '(no no yes)) ; returns (t nil nil)
I think ML's choice tends to be more readable for infix operators in general. The alternative form of the gcd example would look like this:
gcd a + b c - (square d)
In this example, the + and - look like punctuation separating the phrases "gdc a", "b c", and "(square d)", and it takes me a moment to realize "b c" isn't a meaningful expression.
So I think the ML syntax is a good start for Lisp-with-syntax projects. For the syntax I've been designing, I've stuck with Lisp so far, but I want to figure out a good way to integrate ML-style syntax into it.
---
That said, there's a reason I've stuck with Lisp for my syntax: We can't parse ML syntax into nested spines unless we know all the operators that it uses. If we want to support custom infix operators, then the parser becomes intertwined with the language of custom operator declarations, and we start wanting to control what operator declarations are created during pretty-printing. These extra moving parts seem like an invitation for extra complexity, so I want to be careful how I integrate ML-style infix into my syntax, if at all.
... or you want it the other way? Then the next step is easy to put back parenthesis.. parse it and write parenthesis back around each construct. Put it then in Lisp interpreter. Will it run after such a prettyprinter juggling?
... oh, why did I mentioned Shunting Yard that's the most "unlispy"? Well it ensures the code translates to "Lisp without parenthesis"! Write with '(' and ')' if you want, it will remove it in the prettyprint intermediate code anyway. And you even write mathematical expressions in a usual way and the outcome will be prefixed! Your problems are solved..
Obviously Arc is somewhat of an unconventional choice, so you might run into more problems. I'm not saying that to discourage you, just to make you aware of what you're getting into.
You might also look into Clojure, if you want something with more libraries.
But if you decide on Arc, I'd say (as usual) that you can just do development on whatever machine you're working on -- you won't be doing anything heavyweight enough that you need a separate dev machine.
Having not run Arc in production, I'll defer to akkartik's suggestions for picking a cheap VPS.
For hosting there's tons of cheap unix VPS providers. Not much differentiating them; just go with one, and you can switch if you don't like it. Start with https://linode.com or https://www.digitalocean.com.
I'm starting to feel old :) I've been programming for 16 years now.
Feel free to ask me more questions. I'm not looking for help, rather to help. But there's still some way to go. In the meantime I'll help in any other way. Valgrind is super useful for C programming.
Impressive project, how long have you been programming for? I'm not sure if I can be of much technical assistance but if you recommend a few tools I could probably learn more about testing and find bugs. Earlier this week I installed valgrind to check for errors and memory leaks and I'm looking for a debugger that will work with lubuntu. I am also learning assembly.
Thanks for trying it! It's not actually a Lisp :) Check out the examples in the readme.
The repl is sadly not done. Repl feels like the wrong metaphor. I'm writing on a more useful version in edit.mu, but that's far from done. For now sadly you have to type code into a file and run it as a separate step. Just for a couple more weeks, hopefully.
The project looks interesting. I would like to learn more about assembly to, have greater understanding and control over what my computer actually does. All the tests passed but I made mu crash using repl.mu, if this help in finding bugs?
./mu repl.mu
ready! type in an instruction, then hit enter. ctrl-d exits.
(+ 1 1)
missing type in {name: "1", properties: ["1": ]}
mu: 041name.cc:82 bool disqualified(reagent&): Assertion `!x.types.empty()' failed.
Aborted (Core dumped)
Also my left, middle and right mouse button clicks were printing rubbish to the terminal after that. I restarted the terminal and mouse functionality was normal again.
If you're into assembly I'd love to hear what you think of my current project to teach programming using a sort of idealized assembly: http://github.com/akkartik/mu. I'm co-designing the assembly language with the OS around it, in the spirit of C+unix.
Thanks, I didn't see anything suspect. It should be a good learning experience calling c from Anarki. I saw a tutorial today how to create c callable functions using NASM x86-64 assembly code and it worked. Do you think assembly and Anarki can talk to each other through c?
(There's a password for my mail delivery service in that repo, but that account is now defunct. Hopefully nothing else is confidential. Please be nice and let me know if you see something there that might damage me :)
Nice! I should warn you that arc is very deeply permeated with the assumption that there's only one thread of execution. See atomic, for example, which all the assignment operators use: https://arclanguage.github.io/ref/atomic.html. Couple with the statement that "The distinction between “future safe” and “future unsafe” operations may be far from apparent at the level of a Racket program," (http://docs.racket-lang.org/guide/parallelism.html) and I think you might be in for some interesting debugging times :)
Hmm, future-event seems like it's just for performance logging? Maybe ignore it for now and try to build something non-trivial with the primitives you have?
Well, Arc doesn't have a way to open processes that take stdin. Fortunately, Racket does, and if you're using Anarki, the $ macro makes it easy to write little pieces of Racket code in your Arc programs when necessary.
(let (i o . ignored) ($.process "gtk-server -stdin")
(= gtk-in i)
(= gtk-out o))
The ($.process ...) call returns a list, and (let ...) destructures that list to make local bindings for i and o. This doesn't set up any global bindings, so I do that inside the (let ...).
The biggest difference is that I'm using (disp ...) where you were using (write ...). The functionality of (write ...) is to output s-expressions according to the same syntax you write code with, and this means written strings will always have quotation marks around them, which probably isn't what you want.
Hmm, looking at the implementation, 'alet is a utility for wrapping a lambda to do various things:
a) The letargs let you set up some bindings the function can refer to.
b) The body lets you do some things with the function in scope before returning it.
c) It wraps the function in a mutable binding named 'this, and it actually returns another function that dereferences this binding and calls whatever's inside. That way you can dynamically replace the entire behavior of the function by assigning to 'this.
So it seems to be geared for a certain object-oriented style where objects have a) private fields, b) a programmable constructor, and c) a private "become" operation.
It looks like 'dlambda is some kind of a multi-branch, destructuring lambda. In this context of this object-oriented style, it effectively lets your object have multiple methods.
I think you might have made a slight mistake translating 'alet. Here's a minimal fix:
;Anaphoric let
(mac alet (args . body)
- `(with ,args
+ `(with (this nil ,@args)
(assign this ,(last body))
,@(butlast body)
(fn params
(apply this params))))
The code probably seemed to be working, but it was actually setting a variable 'this from the surrounding scope rather than its own 'this.
In a way it's fitting to bring that up, because 'alet-fsm is designed to modify the 'this from the surrounding scope....
Er, speaking of which, your translation of 'alet-fsm is pretty surprising. (You're assigning a bunch of nonlocal variables, and you're even redefining a global macro at run time, which is usually too late because the program has already been macroexpanded. Furthermore, instead of returning the first state of the FSM, you're returning the implementation of a macro.) I'll start from the original Common Lisp code:
(defmacro alet-fsm (&rest states)
`(macrolet ((state (s)
`(setq this #',s)))
(labels (,@states) #',(caar states))))
While Arc doesn't have (macrolet ...), in this case we can achieve something very close by just making this macro global, since it has no local dependencies.
(mac state (s)
`(assign this ,s))
What (labels ...) does in Common Lisp is introduce some local variables that hold functions, in such a way that every function's definition can see every other function. To keep my code simple to read, I'm going to resort to a utility from Anarki that achieves the same kind of scope, namely (withr ...):
(mac alet-fsm states
`(withr ,states
,car.states))
The expansion of (withr (a (fn ...) b (fn ...) c (fn ...)) ...) is something like this:
(with (a nil b nil c nil)
(assign a (fn ...))
(assign b (fn ...))
(assign c (fn ...))
...)
Note how the local variables are introduced in a (with ...) scope that fully surrounds the (fn ...) expressions, so all the variables are in scope for all the functions.
Hmm... I haven't looked at the whole context of what you're doing. but I see you said "The macrolet conversion is where I am having difficulties." That makes sense. Arc doesn't have macrolet! (I think even Anarki doesn't have it... does it?)
Generally, I'd try to convert (macrolet ...) into local functions instead of local macros.
I don't know Common Lisp very well, but judging by the "surprising" example at http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node85.html, maybe there was no particular reason for this example to use (macrolet ...) in the first place:
This ends up simplifying to the exact same code as ichain-intercept%.
I think that makes sense. This example only accomplished the convenience of writing (intercept acc) instead of (return-from intercept acc), but your Arc code already had (intercept acc) to begin with.