Arc Forumnew | comments | leaders | submitlogin
1 point by Pauan 4736 days ago | link | parent

I finally added in Arubic to Nu. Now all you have to do is `./arc -i arubic` and you'll get a REPL with Arubic functionality. You can also use `(import arubic)` to write a file using Arubic.

Why would you want to do that? Well, here are the current changes between Arc and Arubic, though I plan to add more over time:

  (map x foo ...)  ;; Arubic
  (map [...] foo)  ;; Arc 3.1
Ditto for mappend, some, all, and keep. This is especially convenient when using destructuring:

  (map (x y) foo ...)        ;; Arubic
  (map (fn ((x y)) ...) foo) ;; Arc 3.1
I changed the [] and {} syntax as well:

  [1 2 3]   -> (list 1 2 3)
  {a 1 b 2} -> (obj a 1 b 2)
Because of the changes above, you no longer need the [] syntax for functions most of the time, so I'm repurposing them to create lists, which I've found to be far more common.

`isa` accepts multiple arguments:

  (isa x 'foo 'bar 'qux)      ;; Arubic
  (in type.x 'foo 'bar 'qux)  ;; Arc 3.1
`prn` prints spaces between each argument:

  (prn "foo" "bar" "qux") -> "foo bar qux\n"
You can also use `str` and `str?` as aliases for `string` and `string?`.

Small changes, but they do make writing code more pleasant.



2 points by Pauan 4735 days ago | link

I just removed `setforms` from every place in arc.arc except for `rotate` and `=`[1]. Why? Well, there's two reasons:

1) It truly does not make any sense to me why zap is defined like this:

  (mac zap (op place . args)
    (with (gop    (uniq)
           gargs  (map [uniq] args)
           mix    (afn seqs
                    (if (some no seqs)
                        nil
                        (+ (map car seqs)
                           (apply self (map cdr seqs))))))
      (let (binds val setter) (setforms place)
        `(atwiths ,(+ binds (list gop op) (mix gargs args))
           (,setter (,gop ,val ,@gargs))))))
Rather than this:

  (mac zap (f x . args)
    `(= ,x (,f ,x ,@args)))
2) This helps out a ton with arc2js. In arc2js, now all I need to do is provide an `=` macro and I'll get zap, or=, push, pull, swap, etc. all for free. Without this change, I'd have to define my own custom versions of zap, or=, etc...

---

* [1]: https://github.com/Pauan/ar/commit/e58a46fb47802394031dc4afe...

-----

3 points by rocketnia 4735 days ago | link

"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))

-----

1 point by Pauan 4735 days ago | link

"'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.

-----

1 point by Pauan 4735 days ago | link

And I just removed setforms from rotate too[1]. An improvement? You be the judge:

  ;; Arc 3.1
  (mac rotate places
    (with (vars (map [uniq] places)
           forms (map setforms places))
      `(atwiths ,(mappend (fn (g (binds val setter))
                            (+ binds (list g val)))
                          vars
                          forms)
         ,@(map (fn (g (binds val setter))
                  (list setter g))
                (+ (cdr vars) (list (car vars)))
                forms))))

  ;; Nu
  (mac rotate places
    (w/uniq u
      (let shift (join (cdr places) (list u))
        `(let ,u ,(car places)
           (= ,@(mappend list places shift))))))
---

* [1]: https://github.com/Pauan/ar/commit/890209c76356da2993a35ed4b...

-----

1 point by Pauan 4735 days ago | link

And now `=` no longer calls `atomic-invoke` for complex assignments. Based on this information here:

http://docs.racket-lang.org/reference/threads.html

It would appear that the `=` operator is already thread-safe even without `atomic-invoke`. In any case, if you're terribly worried, you can always wrap it yourself.

-----

1 point by Pauan 4735 days ago | link

I just got rid of `setforms` completely[1]: not even `=` uses it anymore. I then reimplemented `=` in a much shorter and clearer way[2].

Arc 3.1 takes 80 lines to implement expand=, but Nu takes only 31. And Nu is much clearer and easier to understand as well. In addition, Nu's output is much shorter and is faster:

  ;; Nu
  > (macex1 '(= foo!bar 5))
  (do (sref foo 5 (quote bar)))

  ;; Arc 3.1
  > (macex1 '(= foo!bar 5))
  (do (atwith (g1 foo g3 (quote bar) g4 5) ((fn (g2) (sref g1 g2 g3)) g4)))
---

* [1]: setforms is included in compat.arc for backwards compatibility with Arc 3.1, but it's not actually used anywhere.

* [2]: https://github.com/Pauan/ar/blob/c835e67d919d7a555a1c856812a...

-----

1 point by rocketnia 4734 days ago | link

I can't find whatever information you're talking about. Would you mind quoting it and/or elaborating?

Don't put too much work into the explanation, 'cause I'm likely to come in at the end and say "but what about X?" :-p It sounds too good to be true.

-----

1 point by Pauan 4734 days ago | link

It's right near the top of the link:

  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.

---

* [1]: http://docs.racket-lang.org/reference/hashtables.html

-----

2 points by rocketnia 4734 days ago | link

In pg-Arc, '= on a variable is 'assign without 'atomic. Where 'atomic comes in is when there's a setforms thing to worry about.

And in that case, this...

  (= (car car.foo) (bar))
...turns into something like this:

  (atomic:with (gs1 car.foo gs2 (bar))
    ( (fn (val) (scar gs1 val))
      gs2))
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.

That said, I don't care about 'atomic myself. :-p

-----

1 point by Pauan 4734 days ago | link

"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.

-----

2 points by rocketnia 4734 days ago | link

"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.)

-----

1 point by Pauan 4735 days ago | link

I just implemented `eachfn` and then changed Arc so the `each` macro just calls `eachfn`. This had a significant speedup, with no cost at all in functionality. At this rate, Nu will end up being just as fast as Arc 3.1, with all the shiny extra features and bug fixes.

So, the rule of thumb is: don't write macros that do a lot of work. Instead, write a function that does the work, and then write a macro that just calls the function. This goes double for macros like `each` that do a type-check at runtime.

-----

1 point by akkartik 4735 days ago | link

  (map x foo ...)  ;; Arubic
  (map [...] foo)  ;; Arc 3.1
I don't follow. Can you provide an example?

-----

1 point by Pauan 4735 days ago | link

Basically, map, mappend, some, etc. behave like the "each" macro:

  (= foo '(1 2 3 4 5))

  ;; Arubic
  (map x foo (prn x)) -> (1 2 3 4 5)

  ;; Arc 3.1
  (map [prn _] foo)   -> (1 2 3 4 5)

-----

1 point by akkartik 4735 days ago | link

Hmm. Can x also be a function?

-----

1 point by Pauan 4735 days ago | link

No.. it can't be. If you want that, you should use mapfn:

  (mapfn x foo)
Which behaves exactly like how map currently behaves in Arc 3.1.

-----

1 point by akkartik 4735 days ago | link

Why not just use each?

map has a pretty consistent semantic over the decades; I wouldn't mess with what it means.

-----

1 point by Pauan 4735 days ago | link

...because each does not map. Nor does it some. Nor does it mappend. Nor does it all. Nor does it keep. Nor does it rem.

I don't think you understand what I'm talking about. In Arubic, map is just a macro that expands into mapfn. The semantics are exactly the same as Arc 3.1, it's just easier to say...

  (map x foo ...)
...rather than:

  (map (fn (x) ...) foo)
The only point of it is to remove the (fn ...) bit, resulting in shorter and easier to read code.

-----

1 point by akkartik 4735 days ago | link

...because each does not map. Nor does it some. Nor does it mappend. Nor does it all. Nor does it keep. Nor does it rem.

Sorry, that makes no sense to me.

Focus on just map. Are you saying it doesn't return a transformed list? Ok. But can we call it something different? You'll have to pry

  (map car xs)
from my cold dead hands :)

-----

1 point by Pauan 4735 days ago | link

"Focus on just map. Are you saying it doesn't return a transformed list?"

No. You are still misunderstanding me. I will be as clear and precise as I possibly can. In Arc 3.1, you call `(map x foo)` where `x` is a function and `foo` is a list.

In Arubic, you would say that as `(mapfn x foo)`. It's exactly the same, except you use the name `mapfn` rather than `map`. Now, Arubic also provides a macro called `map`, which expands into `mapfn`. In other words:

  (mac map (var x . body)
    `(mapfn (fn (,var) ,@body) ,x))
The sole purpose of this macro is so that you don't need to type out the `(fn ...)` part in `(mapfn (fn (x) ...) foo)`. That's it. The meaning of `map` is the same, it's just that now that it's a macro, it's a lot more convenient to use. But you can still pass in a function directly by using `mapfn`.

---

"You'll have to pry (map car xs) from my cold dead hands :)"

If you dislike any parts of Arubic (whether misunderstood or not), simply do not use those parts. The point of the namespace system is that you can change what you want and use what you want, rather than being tied down to one semantic/syntax/meaning/language. Thus, it is possible (and relatively easy) to use some of the things from Arubic that you like, while not using the parts you dislike.

In this particular case, however, you are misunderstanding me. Your example would be `(mapfn car foo)` in Arubic.

-----

1 point by akkartik 4735 days ago | link

In this particular case, however, you are misunderstanding me. Your example would be `(mapfn car foo)` in Arubic.

No, I'm now pretty sure I'm not misunderstanding you.

My entire objection is to the name switch. Of course I know I can change the default. It's not a sensible default, it's like defining 1 to be 0.99. You're fragmenting the semantics of a name that has thus far had a pretty clear meaning. You're welcome to do this, but it's going to hinder users with any programming background.

I don't understand why the thing you want must be called map. Why not mapmc, for example? Or some other 3-letter name?

-----

1 point by Pauan 4735 days ago | link

"My entire objection is to the name switch. Of course I know I can change the default. It's not a sensible default, it's like defining 1 to be 0.99."

It's not the default. The default is Arc 3.1. You have to opt-into Arubic. In any case, how is it not sensible? It provides the shortest, easiest to read, and fastest code.

---

"You're fragmenting the semantics of a name that has thus far had a pretty clear meaning."

And this is why I think you're still misunderstanding me. The meaning of map is the same. It's just that now you can write (map x foo ...) rather than (map (fn (x) ...) foo), which is shorter and easier to read.

If you're worried that it'll mess up Arc programmers who are used to the naming of map, then as already said, Arc 3.1 is the default. You only get the new syntax if you import Arubic.

I do not see how changing the meaning of names in a new language is a bad thing. That's like me saying that wart should use exactly the same names as Arc, which is absurd: they're different languages. If you want to use a different name for something in wart, then go for it.

Perhaps you still do not grasp that Arubic is a different language from Arc, implemented in a separate namespace so as not to mess up Arc's namespace.

---

"I don't understand why the thing you want must be called map. Why not mapmc, for example? Or some other 3-letter name?"

....

I'm done arguing. Goodbye.

-----

1 point by akkartik 4735 days ago | link

"I'm done arguing. Goodbye."

Suit yourself. I don't understand what I have said to merit hostility.

I know we can all do what we want. I also am aware of lots of things I can try if I don't like something you do. I don't understand these defensive reactions. Was I rude somewhere?

All this while you keep insisting you know what I am misunderstanding. Is that not rude?

Arubic's map is a macro, not a function. Until it grows fexprs the meaning is by definition not the same.

"This is how I like it" is a perfectly valid reason (and would have ended this conversation eons ago). Just don't insist it's all still the same when it isn't.

-----

1 point by akkartik 4735 days ago | link

I do not see how changing the meaning of names in a new language is a bad thing.

That's an equally good response to any feedback you get.

Do you seriously think changing the meaning of 42 is a 'good' thing?

I don't even feel that strongly about this conversation. It's only gone on so long because:

a) You kept insisting I am too dense to comprehend your subtle creations.

b) You kept insisting you're changing nothing.

-----

1 point by akkartik 4735 days ago | link

It's not the default. The default is Arc 3.1.

No, the default is racket. No, it's machine code. Or is it cosmic background radiation? WTF, are you seriously saying that creating a new language isn't an act of choosing defaults?

-----

2 points by rocketnia 4734 days ago | link

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.

[1] I'm not sure about this credit, but I found it at https://github.com/nex3/arc/blame/arc2.master/arc.arc and verified it with a Web search.

-----

2 points by rocketnia 4734 days ago | link

Whoops, Anarki's 'trav is a macro. I meant 'walk. ^_^;

-----