The issue is with automaton; you're trying to use a macro (mkrule) like a function with map1. Did you turn mkrule into [mkrule _] because you got this error?
Can't coerce #(tagged mac #<procedure: mkrule>) fn
Simply using square brackets isn't going to fix that.
---
In general, creating macro helpers for macros is harder to debug. Instead create function helpers to create the right code as a list and invoke it within the top-level macro to turn it into code. Here mkrule should probably be a function.
(This is what people usually mean when they say *don't overuse macros". And then the dictum gradually gets over-generalized into "macros bad" and finally, "lisp? blub blub.")
In the definition of 'automaton, you have the macro call (mkrule _). I don't think you want 'mkrule to be a macro, because I don't think you actually care about the symbol _. The first thing I suggest is to replace "mac mkrule" with "def mkrule".
After this change, I'm still not quite sure what your code is trying to do (I haven't clicked your links yet), so I'll try some example cases:
For one thing, it looks like you might want that (car (1 (2 3) (4 5))) to be just 1. You can fix this by changing (car ,i) to ,(car i).
Since you end up with function calls like (3 (cdr str)), it looks like you're trying to make several functions under the same scope (using Anarki's 'withr/p), and it looks like 'str is a variable that contains the remaining inputs of the automaton.
In this case, I recommend changing `(list (cdr ,i) (fn ...)) so that it's (list (cdr i) `(fn ...)), since you probably didn't want that 'list symbol in the 'withr/p call.
To make this easy to test, here's the complete code, with Anarki's definition of 'withr/p and my updates to 'mkrule:
; a 'with that works for defining recursive fns
(mac withr/p (bindings . body)
" Scheme's 'letrec.
See also [[withr]] [[where]] "
`(let ,(map1 car bindings) nil
,@(map [cons 'assign _] bindings)
,@body))
(def mktransition (tn) (list (car tn) (list (last tn) '(cdr str))))
(def mktransitions (ts)
(accum accfn
(each x (map1 mktransition ts)
(accfn (car x))
(accfn (last x)))))
(def mkrule (r)
(let i r
(list (car i) `(fn (str)
(if (empty str) t
(case (car str)
,@(mktransitions (cdr i))))))))
(mac automaton (i r) `(withr/p ,(map1 [mkrule _] r) ,i))
Seems to work!
(= matches-aabb
(automaton step-a
(
(step-a
(a step-a)
(b step-b))
(step-b
(b step-b)))))
arc> (matches-aabb '(a a a a b b b b))
t
arc> (matches-aabb '(a a a a b b b b a))
nil
arc> (matches-aabb '(a a a a))
t
arc> (matches-aabb '(b b))
t
arc> (matches-aabb '())
t
arc> (matches-aabb '(b a))
nil
arc> (matches-aabb '(c))
nil
Now that I look at your Gist, I realize I could have saved myself some time figuring this out. :) I also realize you were aiming for a slightly different interface:
It's currently missing extensible setforms, but all the standard ssyntax and brackets should work. They're detected and expanded (awkwardly right now) at the reader level.
It's hard to say since Arc doesn't have a formal spec. The end goal is for everything to just work like it would in pg's Arc. I'm slowly working toward that goal.
Right now, Arc s-exp editing macros are straight-up unsupported. In addition to pattern matching, you have to explicitly break hygiene with leak and bind:
Good error reporting is pretty disagreeable to me, lol. :-p
I think Arc's main advantages over Scheme, once all the Arc-like naming conventions and macros are in place, are setforms, defcall (not in pg's Arc), and the ssyntaxes (a:b c), a.b, and a!b. Going by the examples, Rark has setforms, and its ability to unwrap data structures using function calls indicates defcall wouldn't be hard to add if it isn't there already, but I don't see anything about ssyntax. I've been meaning to download and run Rark to see if ssyntax support is actually there after all.
The abstract "Scheme" I'm talking about might have a certain advantage over Racket, but I might just be doing it wrong: Is possible to write a macro and use it in the same file? I've had to break my utilities into three files just to have multiple layers of macros. I'm interested in seeing whether Rark makes this any easier.
BTW, a friend mentioned that APL does indeed have an 'english' mode as a debugging aid. Perhaps it's indicative that people are using the more concise notation in spite of the availability of alternatives.
"This would allow robomartin to have his "notation as a tool for thought", while still having another syntax that's easier to understand, for those that want it."
For the past 6 months I've been going to a hackerspace in Oakland every monday evening, and trying to learn how to teach programming to non-programmers. So I've been thinking about this a lot.
> Take a page full of intense mathematical notation or an orchestral score and translate it into either full words (i.e.: "integral" in place of the integral symbol) or one, two or three character ASCII mnemonics. You should realize that the original is far more powerful a tool for communicating thoughts, ideas and the subject at hand than a seemingly random dump of ASCII on paper.
I've heard this idea before: have a language that separates the chrome of the top-level syntax from the underlying code.^1 That is, you can have the same language use different syntaxes ^2. This would allow robomartin to have his "notation as a tool for thought", while still having another syntax that's easier to understand, for those that want it.
But, of course, this would be difficult, especially if you want to allow mixing of multiple syntaxes in the same line of code.
For this specific example, you could have aliases for APL tokens to (possibly) multiple J tokens. The reader of the code could decide how to view it.^3 The writer could write whatever he or she prefers. But this doesn't even seem to be enough here; I don't think J and APL have all the same operators; unless you can make APL operators easily by combining J ones, this won't work. Of course, that doesn't mean a newly-designed language won't work.
Also, it's hard enough to get one language adopted by people; trying to get a language with variable syntax must be significantly harder. The toolchain will be an extremely important part of language evangelism; if there's no good way for people who don't use, say, either Emacs or Vim to use this language, no one will.
Perhaps start with an even simpler J version; instead of
avg=: +/ % #
you would write ^4:
avg=: fold(+) divide list-len
Why do I think people would tolerate having multiple syntaxes for the same thing? Let's take a musical interlude here. Now, music is traditionally written on as sheet music: on a staff, with different notes to indicate length, and the vertical position on the staff to indicate pitch. However, for guitars, there's a more common way of writing it: guitar tab. This throws away information like timing, and simply says "fret X on string Y".^5 Like our J-analogue for APL, tab is much simpler; if you can play the guitar, you can understand tab in a few minutes. Staff takes much longer; weeks or months. People don't go back and forth between one or the other; generally, if you're comfortable working with sheet music, you use that if you have the choice, only choosing tab if you don't have sheet music. I don't know if someone's designed a tool to translate between sheet music and tab.
[1] Heck, even Lisp started that way, with M-expressions: https://en.wikipedia.org/wiki/M-expression . That no one used them is possibly an indication this is a bad idea.
[2] It's a fool's argument, but not technically wrong, to say we have that with different languages that compile to assembly, or to C, or scheme, or whatever. That's missing the point.
[3] This sounds like a nice Emacs mode.
[4] Yes; I'm suggesting a "simple" version include the terminology "fold". It's an implementation detail; perhaps this is better:
avg=: +-list divide list-len
[5] Note that guitar tab specifically says which string to use: since a single note can be played multiple ways on a guitar (e.g., the open A string is enharmonically the same to the fifth fret on the low E), guitar tab has information which sheet music does not. Whether this is a benefit or not is hotly debated.
This is my fun little side project - the aim being to get Arc running inside of Racket with a little less friction: better performance, profiling support and source locations for errors.
It can run some Arc code unmodified. You'll likely be more comfortable with rark/load (which defines everything at the top level, and is hopeless [1]).
"If you looked at it closely, I made many changes in the spirit of getting something running that PG (and you!) would likely not agree with."
I didn't see the repo while it was online, but I've always looked forward to Arc (or at least Anarki) becoming usable as a Racket language at some point, even if it were an awkward fit. Just how disagreeable are we talking, here? :)
Yes, I was phrasing it poorly. I meant that since just reusing the binding sometimes doesn't work, the idiom has evolved to always rebind. Even for non-nil lists, and even for destructive operations that are safe to just operate on like nconc. But I'm not a huge expert on what the idioms are :)
"But there's a reason lisp does seemingly the wrong thing. According to the spec, nconc skips empty arguments (http://www.lispworks.com/documentation/HyperSpec/Body/f_ncon...) Reading between the lines, I'm assuming this makes sense from a perspective in lisp where we communicate even with 'destructive' operations through their return value."
This isn't just an idiomatic thing. Since nil is immutable, (nconc a '(34)) couldn't change a if it tried.
And whereas Python's list.append() mutates a single object, Lisp operations like 'nconc and 'nreverse destructively create a whole tree of objects. Particularly in the case of 'nreverse, the new tree's root isn't specified to be any particular object the caller has seen before, so the caller has to get a reference to it somehow: as the return value.
Incidentally, the spec for 'nconc leaves less unspecified, so the programmer doesn't actually need to use the return value. Given a non-returning version, they can reconstruct a returning version like so: