In Nu, the default namespace is the one corresponding closely with Arc 3.1. And in this namespace, the default definition of 'map is pretty much Arc 3.1's. Therefore, "it's not the default" is true in Nu.
Another namespace in Nu is Arubic. Using this namespace is like using a language other than Arc 3.1, where 'map is a macro. If your problem is with Arubic itself, you're free to ignore the parts of Arubic you don't like (even if you want to use an library written in full Arubic). If the point is you think people shouldn't dilute the overall meaning of "map" with the meaning Pauan gives it in Arubic, that's a moral position, with Pauan as an unintentional villain!
---
What I think is that the meaning of "map" isn't tainted here, at least not any more than usual. In fact, I wanted 'map to be an 'each -like macro before I knew Pauan had the same thing in mind.
If I don't know what language we're talking about and I hear "map," I assume it'll be an abstraction that applies a given transformation to elements of a given data structure (whatever "abstraction," "transformation," "data structure," and "given" mean), along with some accidental complexity suitable to the language, such as the timing of computation, side effects permitted in the transformation, or late-bound dependencies (like 'map calling out to 'cons, such that rebinding 'cons changes what happens).
In a language where macros like 'after and 'each are more convenient to use than higher-order functions like 'protect and Anarki's 'trav, a macro for map is to be expected.
In Arc, almkglor[1] calls this macro "mapeach" in Anarki, and I call it "maplet" in Lathe. Pauan and I call it "map" as long as we don't have naming conflicts to worry about. As long as this macro is the primary way we use the map concept, using that name keeps our programs brief and frank.
All constant-time procedures and operations provided by Racket are
thread-safe because they are atomic. For example, set! assigns to a variable
as an atomic action with respect to all threads, so that no thread can see a
“half-assigned” variable. Similarly, vector-set! assigns to a vector
atomically. The hash-set! procedure is not atomic, but the table is
protected by a lock; see Hash Tables for more information. Port operations
are generally not atomic, but they are thread-safe in the sense that a byte
consumed by one thread from an input port will not be returned also to
another thread, and procedures like port-commit-peeked and write-bytes-avail
offer specific concurrency guarantees.
It mentions that hash table assignment is not thread-safe, however if you then go to the hash table page[1], it says this:
A mutable hash table can be manipulated with hash-ref, hash-set!, and
hash-remove! concurrently by multiple threads, and the operations are
protected by a table-specific semaphore as needed. Three caveats apply,
however [...]
In other words, Racket already handles everything, according to the docs. If you're ever worried enough, or run into any problems, it's not hard to wrap it in `atomic` yourself. I'd rather not have the cost of `atomic-invoke` for every assignment, especially if you run all your code in one thread (like I do).
By the way, Nu doesn't use `set!` for global assignment, so I'm not sure if global assignment in Nu is thread-safe or not. But I'd assume it is, since I think `namespace-set-variable-value!` is constant-time.
This ensures that car.foo, (bar), and (scar gs1 val) all happen without interference in between. I suspect Racket at most protects those on an individual basis.
"In pg-Arc, '= on a variable is 'assign without 'atomic. Where 'atomic comes in is when there's a setforms thing to worry about."
I am aware. It still seems to me that if you're dealing with threads, you should wrap assignment in atomic yourself if you're worried about such things. Code that doesn't deal with threads shouldn't have to use atomic.
Perhaps there should be an `a=` macro that's just like `=` but it calls `atomic`. Hm... I wonder... would it be possible to detect whether code is running in the default thread and if not, automatically wrap it in atomic...? May be more trouble than it's worth, though.
That's what I think. Anyone who cares can say (atomic:= ...) or (atomic:zap ...), so I only see a couple of reasons why we'd want to have the 'atomic implicit:
- We want to use it all the time anyway. (I doubt it, but it's hard to tell. I haven't used threads, and therefore I've never bothered to find a way to squeeze utility out of it.)
- There are people who do care, and they'd be better off if the people who didn't care still used 'atomic by accident. (Again, it's hard for me to tell if this is true.)
"It truly does not make any sense to me why zap is defined like this"
'zap is the only use I typically have for the 'setforms "binds" list (which ensures the subexpressions of 'place are only evaluated once).
Still, 'zap doesn't need to work that way: as long as I'm using a language where I know 'zap evaluates its place twice, I'm pretty much okay with it. It's a wart, but it's not an impediment.
To explore Arc-3.1-like options for a bit, here's a cleanup of Arc 3.1's definition of 'zap:
(mac zap (op place . args)
(with (gop (uniq)
gargs (map [uniq] args)
(binds val setter) setforms.place)
`(atwiths (,@binds ,gop ,op ,@(mappend list gargs args))
(,setter (,gop ,val ,@gargs)))))
If we allow 'setter and 'val to compile and evaluate before 'op and 'args, it gets shorter:
(mac zap (op place . args)
(let (binds val setter) setforms.place
`(atwiths ,binds
(,setter (,op ,val ,@args)))))
I prefer to arrange the compilation and evaluation orders from left to right ('op, 'place, 'args), using a technique like this:
(mac place (place)
(let (binds val setter) setforms.place
`(withs ,binds
(list (fn () ,val) ,setter))))
(mac zap (op place . args)
`(atomic:fn-zap ,op (place ,place) (list ,@args)))
(def fn-zap (op (getter setter) args)
(setter:apply op (getter) args))
"'zap is the only use I typically have for the 'setforms "binds" list (which ensures the subexpressions of 'place are only evaluated once)."
Hm... yes, you're right, `(zap + (foo (bar qux)) 1)` evaluates `(bar qux)` twice, and I don't see an easy/obvious way to fix that in `=`. I'll need to think about this.
"I'm still unsure what the term 'animated' means in this context. Rocketnia?"
For the benefit of people here, this is what David Barbour had to say over email to akkartik and myself:
"Animated term rewriting means that the term is changing over predictable time, in a predictable manner - i.e. you could literally set up a viewer and watch the term change in real-time. If the term specified something like which jpeg image to display, or how to position a robot, this could easily be lifted to visual or physical animations. Animation, in general, is about controlling an external system over time."
I think the benefit here isn't (only) the ability to express animation, but (also) the ability to use a divergence-capable term rewrite system in a way that isn't the end of the world when the system actually does diverge.
"One thing that expressive languages like Ruby, Smalltalk, and Lisp teach us is that many 'design patterns' are actually language smells. The 'fluent interface' design patterns is just that: A sign that a language is missing a cascading message feature."
When a language has a (sub)community that uses so many side-effectful interfaces that cascading message syntax sounds like a relief, that community-wide design pattern might be the more appropriate thing to repair. :-p
Anyway, I use my own custom infix operator for such purposes: ;it.
-- who needs this ---
array
.pop()
.pop()
.pop()
-- when there's this --
var it = array ;it.
pop() ;it.
pop() ;it.
pop();
Just kidding around. I haven't developed a pattern in these cases, probably because I program with few side effects and few OO interfaces and because I sooner or later bury any interface I'm working with under layers of my own.
I'd like to know what makes that ssyntax too. I've heard ssyntax called "symbol syntax"[1] for its low-level details, as well as "special syntax"[2] for, well, reasons I haven't figured out, which is why I ask. :-p
Looking back at "Being Popular" and "Arc at 3 Weeks,"[3] I wonder if pg just used "ssyntax" as an in-code abbreviation for "syntax for s-expressions," in which case that's something lark totally is exploring. But even then, just the term s-expression can mean different things to different people, and I'm curious.
(def foo (a b)
(list a b))
(foo 1 2) -> (1 2)
(foo :b 2 :a 1) -> (1 2)
That doesn't work in Racket. You need to explicitly say that the arguments are keywords:
(def foo (:a :b)
(list a b))
In other words, arguments are either positional or keyword-based, but not both at the same time. This is different from Python, which lets you treat an argument as either one:
You're not going to be able to use (with hash ...) with a local variable 'hash unless you have at least one of these:
- Fexprs. An fexpr implementation of 'with can use the complete value of 'hash as it determines how to treat the unparsed body.
- Static typing with record types, so that a macro can use the type of 'hash as it determines how to treat the unparsed body.
- Some variant of JavaScript-style scope chain semantics, in the sense that a bare variable reference means (or can mean) a field lookup in general. IMO, this would be the most straightforward to add to an Arc-3.1-like compiler, since it's a matter of compiling foo to (scope 'foo), (with foo ...) to (let ((scope (shadow (scope 'foo) scope))) ...), and other scope-related things in their own analogous ways.
Awesome! I've been looking forward to seeing some development on ar. ^_^ I like what you're doing with the argument lists. Have you found your arc2js progress useful for this? (Part of me wonders how Racket's local (define ...) forms compare against its other options in terms of performance. If they're faster, that could pose the same compilation annoyances as arc2js deals with. ;) )
As far as the name "Nu" goes, it's actually been taken by another lisp (and I'm not even talking about NewLISP :-p ): https://github.com/timburks/nu. Early this year I did a couple of months of Objective-C programming for work, and I GitHub-followed Nu in envy. XD
"I like what you're doing with the argument lists."
You mean the "destructuring is just lambdas" thing?
---
"Have you found your arc2js progress useful for this?"
No, except insofar as writing arc2js taught me some useful compiler things. For the most part, it's been pretty simple and easy, so I don't think arc2js helped much, if at all.
---
"Part of me wonders how Racket's local (define ...) forms compare against its other options in terms of performance."
(let a nil (%nocompile (racket-define a 5)) a)
(let a nil (= a 5) a)
Time: 1500-1600 ms
(let a nil (%nocompile (racket-let* ((a 5)) a)))
(let a nil (%nocompile (racket-let ((a 5)) a)))
(let a nil (let a 5 a))
(let a nil a)
Time: 1400-1500 ms
It would seem Racket optimizes lambdas very heavily.
---
"As far as the name "Nu" goes, it's actually been taken by another lisp (and I'm not even talking about NewLISP :-p )"
Darn. I still like the name "Nu" enough that I want to keep it, though.
It took me a while to reply because I was planning to do some of my own testing before posting, but I haven't found the time.
---
"You mean the "destructuring is just lambdas" thing?"
No, I don't know what to think about that yet. I like it if and only if this works:
(let (a . b) '(1 . 2)
...)
Even if that works, I almost expect Racket to copy a mutable argument list into an immutable one for use inside the lambda, so here's a second test case:
(withs (foo (list 1 2 3)
(a . b) foo)
(= b.0 4)
(is foo.1 4))
What I definitely do like is the use of Racket's optional arg syntax where possible. ^_^
---
"It would seem Racket optimizes lambdas very heavily."
IMO, those examples aren't normal uses of (define ...), since they're contexts where the variable is already defined. I'd like to test these cases:
; Racket code
(let ((a 5)) a)
((lambda (a) a) 5)
(begin (define a 5) a)
; NOTE: Make sure this (begin ...) isn't at the top level, or the
; definition will be global.
(let () (define a 5) a)
((lambda () (define a 5) a))
* Internal-definition expansion has changed to use `let*' semantics
for sequences that contain no back references. This change
removes a performance penalty for using internal definitions
instead of `let' in common cases, and it only changes the meaning
of programs that capture continuations in internal definitions.
Internal definitions are now considered preferable in style to
`let'.
Then again, if they're going to make more semantic changes like this one, it might be better to avoid compiling to an internal-definition style. :-p
Anyway, from the phrasing in the changelog, it sounds like internal definitions are implemented in ways the programmer can already control at a lower level with things like 'let*, so you'll probably continue to find that internal definitions are at least as slow as other options.
The reason is because racket-mlist->list expects a proper list. I can fix that easily.
Update: I fixed racket-mlist->list, but Racket's apply absolutely positively requires a proper list, so it looks like I can't use nested lambdas if I want to fix that: I'll have to use a Racket let*
---
"I almost expect Racket to copy a mutable argument list into an immutable one for use inside the lambda"
It's true, it does copy the list, so that returns nil. I wonder if I can work around that...
---
"One reason it's on my mind is the Racket 5.2 changelog"
I'm not using Racket 5.2. And even if I were, the reason for the speed tests was to see how it would perform within the (fn ...) expansion, where it does indeed overwrite an already-existing variable. (Though I could change it to use a gensym...)