Arc Forumnew | comments | leaders | submitlogin
Poll: What do you most want changed/fixed/added in Arc?
26 points by pg 6139 days ago | 64 comments
Suggest new choices in the comments and I'll add them.
Less opaque error messages
41 points
Currying in some form
26 points
Arrays
25 points
A wiki for this site
46 points
Foreign function interface
33 points
Ability to define new intrasymbol syntax
17 points
Better performance
9 points
Control over sref
7 points
A hosted repl
19 points
Modules
30 points


3 points by almkglor 6138 days ago | link

About arrays: I think the major concern of most people has more to do with efficiency than usability. Generally, lists are more useable (easier to construct and insert into) than arrays, but arrays have O(1) indexed lookup compared to list O(n).

Since lists in Arc have the cute (lst ind) syntax, indexed lookups are expected to be common (because the syntax is simple) but the problem is efficiency.

However, I would like to propose the use of an unrolled linked list as an implementation of lists:

http://en.wikipedia.org/wiki/Unrolled_linked_list

Unrolled linked lists have O(n/m) lookup time, at the cost of significant insertion time boosts. We expect insertion to be less common, however.

The naive implementation of unrolled linked lists cannot have safe scdr implementations (i.e. indistinguishable from singly-linked list scdr). However, with some thinking, scdr can be implemented.

Instead, we should use the following structures (Arc mockup; obviously this is much, much more efficiently expressed in C++):

  (= m 16) ; the unrolling factor: larger numbers trade off memory for speed
  (deftem unrolled-list
    'elements (vec m) ; where (vec m) returns a basic true array
    'start m
    ; best implemented as a C++ stl::map
    'cdr-lookup (table-that-allows-nil))
  ; this is the actual cons cell
  (deftem cons
    'list-struc (inst 'unrolled-list)
    'index 0)
Now in order to implement a cons operation, we first check if the second argument is a cons cell. If it is, we check its list-struc's start parameter. If the index and the start parameters are equal and non-zero, we just construct a new cons cell with the same list-struc; otherwise we also construct a new unrolled-list list-struc.

  (def cons (a d)
    (if (and
          (isa  d 'cons)
          (is   (d!list-struc 'start) d!index)
          (isnt (d!list-struc 'start) 0))
      ; use the same array
      (withs (list-struc d!list-struc
              elements list-struc!elements)
        ; decrement the start
        (-- list-struc!start)
        ; add the element
        (= (list-struc!elements list-struc!start) a)
        ; create a new cons cell, sharing structure
        (inst 'cons
              'list-struc list-struc
              'index list-struc!start))
      ; *don't* use the same array; create a new one
      (withs (elements (vec m)
              start (- m 1)
              list-struc (inst 'unrolled-list
                               'elements elements
                               'start start
                               'cdr-lookup
                               (fill-table (table-that-allows-nil)
                                 `(,start ,d))))
        ; add it to the new array
        (= (elements start) a)
        ; create the cons cell
        (inst 'cons
              'list-struc list-struc
              'index start))))
Getting the car of the cons cell requires us to only look it up directly from the list-struc's elements:

  (def car (l)
    (let elements (l!list-struc 'elements)
         (elements l!index)))
Setting the car is similar:

  (def scar (l a)
    (let elements (l!list-struc 'elements)
      (= (elements l!index) a)))
Getting the cdr is more difficult: first we need to look up our index in the cdr-lookup of the table. If it's in there, we return the result of the cdr-lookup. If it's not, we create a cons cell that refers to it for us.

  (def cdr (l)
    (withs (list-struc l!list-struc
            elements list-struc!elements
            cdr-table list-struc!cdr-table)
      ; cdr-table must support values that are nil
      (if (has-key cdr-table l!index)
          (cdr-table l!index)
          (inst 'cons
                'list-struc list-struc
                'index (+ 1 l!index)))))
The above means that comparing cons cells require us to compare not the cons objects themselves (which could be different) but what they refer to:

  ; the anarki redef binds the old definition to `old'
  (redef is (a b)
    (if (and (acons a) (acons b))
        (or (old a b)
            (and
              (is a!list-struc b!list-struc)
              (is a!index b!index)))
        (old a b)))
Now setting the scdr requires us to first determine if the index is in the cdr-table. If it is, we modify the cdr-table; if it isn't, we insert it.

  (def scdr (l d)
    (withs (list-struc l!list-struc
            cdr-table list-struc!cdr-table)
      ; insertion and replacement use the
      ; same semantics.
      (= (cdr-table l!index) d)))
Since cdr first checks the cdr-table, any other cons cells which point to the same list-struc will also see the replacement.

Lookups must first check if the index would go out of range to the nearest highest cdr-table entry:

  (defcall cons (l i)
    (withs (start-index l!index
            index (+ start-index i)
            list-struc l!list-struc
            cdr-table list-struc!cdr-table
            elements list-struc!elements
            nearest-cdr-table
            ; the arc-wiki 'breakable creates a control structure
            ; that can be returned from by (break <value>)
            ; this part should really be done by binary search;
            ; C++ stl::map fortunately sorts keys, so binary
            ; search should be easy if that is the underlying
            ;implementation
            (breakable:each i (sort < (keys cdr-table))
              (if (> i start-index)
                  (break i)))
      (if (> index nearest-cdr-table))
          ; pass it on, then
          ((cdr-table nearest-cdr-table) (- index nearest-cdr-table 1))
          ; it's in this array
          (elements index))))
Although the part above where it looks up nearest-cdr-table may seem expensive, for reasonably-clean lists (those where scdr hasn't been used often) will only have one entry in the cdr-table anyway; the checking here also doubles as an array bounds check! At the same time, even if scdr has been used often, the lookup and cdr operations work exactly as if it were singly-linked lists.

We can also build a 'copy-list operation which "optimizes" lookup - it measures the length until the source list is improper (cdr is not a cons - either nil or some other value) and returns a cons whose list-struc!elements contains the entire proper list, with the (cdr-table (- (length source) 1)) being the final cdr.

-----

2 points by nex3 6138 days ago | link

This is a cool idea, but I'm not sure that it's a good idea in general to add complexity to cons cells. Even if the interface is the same, the performance characteristics are different (that's the whole point, I suppose), and that makes reasoning about them more complicated.

Also, from a more wishy-washy perspective, it just feels right to me that the core data structure of Lisp has such an conceptually simple implementation. Two pointers: a car and a cdr. That's it

It seems to me that it's worth just making arrays available (which we'd have to do anyway, really) to keep lists as simple as possible.

Maybe I'm being naïve, though ;-).

-----

3 points by almkglor 6138 days ago | link

Come now. It's always possible to "seamlessly" funge the division between lists and arrays. For the most part, they have virtually the same purpose: to keep a sequence of objects. One can imagine an implementation which keeps lists as arrays underneath, but if something really difficult to do in array - say insertion, or scdr - to switch over to singly-linked lists.

Really, though, I think what's missing in Arc is the layers of abstraction. We don't need two sequences - singly-linked lists and arrays. What we should have is the concept of a sequence. Iterating over a sequence, regardless of its implementation, should be handled by 'each, 'map, etc. I think as pg envisioned it, a profiler would eventually be integrated into Arc, which would measure the performance difference in using either of the implementations. If insertion is common in one place, and then indexed access in another, the compiler+profiler should be able to figure out that it should use linked lists in one place, then copy it over to the other place as an array to be used as indexed access.

Basically, what I'd like is a layer of abstraction - "this thing is a sequence, I'll choose array or linked-list later". Then, after profiling, I'll just add some tags and magically, this part of the code building the array uses linked-lists and that part doing weird indexed stuff (e.g. heap access) uses arrays.

When you look at it, most other dynamic languages don't have a separate linked-list type. They're all just "array", and not all of them use a sequential-cells-of-memory implementation of arrays. Say what you will about lesser languages, but first think - if everyone's doing it, there must be a reason. They could just be copying each other, of course, but one must always consider whether having two types of sequence, whose only true difference is their difference in access, is such an important thing.

-----

4 points by nex3 6138 days ago | link

I don't see lists in Lisp as just sequences. Rather, I don't see cons cells as just sequences. They're so much more versatile than that, which is part of what gives Lisp its power (cue "hundred operations on one data structure" quote). They can be used as maps, trees, etc. I think it would be a mistake to say, "these are mostly the same as arrays, let's implement them as arrays most of the time." Cons cells aren't the same as arrays.

I guess you're right in that arrays have more-or-less a subset of the functionality that cons cells do. Maybe it would be a good idea to have lists as the default and switch to arrays under some circumstances (lots of indexing or index-assignment?). But I'm skeptical about this as well.

Also, I foresee some unexpected behavior if the transition between conc cells and arrays is entirely behind-the-scenes. For example:

  ; Suppose foo is an array acting like a cons cell
  (= bar (cons 'baz (cdr foo)))
  (scdr (cdr bar) 'baz)
Now we'd need to somehow update the foo variable to point to a cons cell rather than array. You could imagine this getting even more tricky, even incurring large unexpected cost, with many variables pointing at different parts of an array-list and one of them suddenly scdr-ing.

-----

2 points by almkglor 6137 days ago | link

All of that solved by the unrolled-list mockup. It works, and works with scdr, no matter how many pointers point in the middle of otherwise-proper lists, and even works with lists split by scdr, handling both the parts before and after the scdr-split seamlessly. Costs are also not very large - mostly the added cost is in the search through cdr-table for the next highest key. That said the added cost is O(n) where n is the number of individual cons cells scdr has been used on.

So yes, the above code you show will add cost. But how often is it used in actual coding anyway? The most common use of cons cells is straight sequences, and quite a bit of arcn can't handle improper lists (quasiquote comes to mind) - yet arc is useful enough to write news.yc.

Edit: Come to think of it, for the common case where scdr completely trashes the old cdr of a cons cell (i.e. no references to it are kept), the linear lookup through cdr-table will still end up being O(1), since keys are sorted, and the earlier cdr-table entry gets found first.

-----

2 points by tokipin 6138 days ago | link

i don't know the technical terms, but probably one of the things that gives Lua its speed is that if you have multiple strings in the program that are the same, the VM assigns them the same pointer. string comparisons are therefore trivial and i imagine this mechanism would make table lookup very direct

-----

5 points by absz 6138 days ago | link

That's what Arc's symbols are for. Generally speaking, you should be keying your tables with symbols for exactly that reason: every 'a is at the same place in memory, but every "a" is not (which allows mutable strings).

-----

3 points by kens1 6136 days ago | link

You had me worried, but I'm pretty sure there's absolutely no problem with using strings as the keys for tables.

The MzScheme documentation says: make-hash-table ... 'equal -- creates a hash table that compares keys using equal? instead of eq? (needed, for example, when using strings as keys).

Checking ac.scm, sure enough:

  (xdef 'table (lambda () (make-hash-table 'equal))
Likewise, the "is" operation in Arc uses MzScheme's string=? . (That's a statement, not a question :-) So string comparison works, although in O(n) time.

Net net: strings are okay for comparison and table keys in Arc.

-----

2 points by absz 6136 days ago | link

I wasn't saying that they weren't usable, just that they were, in fact, slower; that's what you observed. Symbol comparison is O(1) time, whereas string comparison is O(n) time. That's all.

-----

3 points by are 6137 days ago | link

I would rather have immutable strings + unification of symbols and strings.

- Any string could have an associated value, like symbols today.

- "foo", 'foo and (quote foo) would be the same object (you would allow Lisp-style prepend-quoting of non-whitespace strings for convenience).

- foo, (unquote "foo") or (unquote 'foo) would then be used to evaluate, so even non-whitespace strings like "bar baz" could act as symbols (but with less convenience, of course, since you would have to use the unquote form to get them evaluated).

- Since such a unified string/symbol would also act as a perfectly good key/value pair, a simple list of such strings will in effect act as a string-keyed hashtable (since duplicate strings in the list would be the same immutable key), and can be used wherever you need symbol tables (e.g. for lexical environments). In fact, string-keyed hash tables would be a subtype of any-sort-of-key hashtables, and probably used much more.

-----

2 points by absz 6137 days ago | link

Right now, you can do (= |x y| 3) to get at oddly-named symbols, or

  arc> (eval `(= ,(coerce "x y" 'sym) 42))
  42
  arc> |x y|
  42
. And by (unquote "foo"), do you mean (eval "foo")? Or do you mean `,"foo"? The latter makes more sense here.

At any rate, I'm not convinced that this is actually a benefit. Strings and symbols are logically distinct things. Strings are used when you want to know what they say, symbols when you want to know what they are. Unifying them doesn't seem to add anything, and you lose mutability (which, though unfunctional, can be quite useful).

-----

3 points by are 6137 days ago | link

Good feedback.

> Strings and symbols are logically distinct things. Strings are used when you want to know what they say, symbols when you want to know what they are.

Fine. But this breaks down anyway when you force people to use (immutable) symbols instead of strings for efficient allocation. When using symbols as keys in hashtables, you do not "want to know what they are", you "want to know what they say".

And unification would possibly have good consequences for simplifying macros and code-as-data (especially if characters are also just strings of length 1). Most code fragments would then literally be strings (most notable exceptions would be numeric literals, list literals and the like).

-----

2 points by absz 6136 days ago | link

Actually, in a hash table, I usually don't care what the key says, any more than I care about the name of the variable used to store an integer. I care about it for code readability, but I'm usually not concerned about getting a rogue key (where I do care what it says). In that case, I would either use string keys or (coerce input 'sym).

I'm not convinced that characters being strings of length one is a good idea... it seems like the "character" is actually a useful concept. But I don't have a huge opinion about this.

Code fragments would still be lists, actually: most code involves at least one function application, and that's a list structure. Only the degenerate case of 'var would be a string.

-----

1 point by are 6136 days ago | link

> Actually, in a hash table, I usually don't care what the key says, any more than I care about the name of the variable used to store an integer.

That's fine again, but my point is just that by using symbols as keys in hashtables, you never care about the value part of that symbol (you just need an immutable key); you're not using the symbol "as intended", for value storage.

> most code involves at least one function application, and that's a list structure.

Yep. But in the case where that function application does not contain another function application (or list literal) in any of its argument positions, we would, with my proposal, be talking about a list of strings, which could then again be seen as a string-keyed hash table...

-----

1 point by absz 6136 days ago | link

Symbols are not "intended" for value storage, symbols happen to be used for value storage. Symbols have exactly the same properties as, say, named constants in C, and thus fit in the same niche. They also have the properties of variable names, and so fit in that niche too. Symbols are a generally useful datatype, and they are no more intended for just "value storage" than cons cells are intended for just list construction.

A list of strings is still a list, which is not what you said; right now, it's a list of symbols, and I don't see the benefit of a list of strings over a list of symbols.

-----

3 points by almkglor 6136 days ago | link

Really, I think most people are confused by the boundary between interface and implementation.

It's possible to have a mutable string interface built on an immutable string implementation. Just add indirection! We can get the best of both worlds: immutability of actual strings optimizes comparison of strings, while the pseudo-mutable strings allow you to pass data across functions by a method other than returning a value (i.e. mutating the string).

-----

1 point by absz 6136 days ago | link

That's a good point. However, it leaves open the question of what "a new string" creates. One can build either one on top of something else (e.g. immutable strings on top of symbols [though one could argue that that's what symbols are, I suppose]), so the real question (it seems to me) is what the default should be.

-----

3 points by almkglor 6136 days ago | link

This is where "code is spec" breaks down, again ^^; \/

I suppose if the user uses symbol syntax, it's an immutable string, while if the user uses "string syntax", it's a mutable string. Interface, anyone? ^^

edit: typical lisps implement symbols as references to mutable strings; some newer ones implement mutable strings as references to immutable strings, with symbols referring also to immutable strings.

-----

3 points by absz 6136 days ago | link

This isn't so much code is spec, though: Arc only has mutable strings and symbols. You could consider symbols immutable strings, but they exist to fill a different niche.[1] If mutable and immutable strings were created, then the code-spec would have to deal with this; I think it would be capable of doing so.

I'm not so concerned with how Lisps represent symbols and (mutable) strings as long as (1) my strings can be modified, and (2) comparing symbols takes constant time. If it's the Lisp interpreter protecting the string-representing-the-symbol, so be it; that doesn't affect me as a Lisp programmer.

[1]: Although if I recall, Ruby 2.0 will make its Symbols "frozen" (immutabilified) Strings, thus allowing things like regex matching on Symbols. This might be an interesting approach...

-----

11 points by nex3 6138 days ago | link

I'm very fond of the defcall functionality I added in Anarki - the ability to specify in-Arc the semantics for function calls on arbitrary types. So for example:

  (defcall cons (l i)
    (if (is i 0) (car l)
      ((cdr l) (- i 1))))
It would also work for arbitrary user-defined (via annotate) types as well, of course.

-----

6 points by kens1 6138 days ago | link

I'd like improvements in basic usability. This includes filling in missing functionality (e.g. trig, socket connections, regular expressions) and/or an escape to Scheme to do these. Also non-brokenness on Linux, Windows, etc. Also ability to simply make a non-REPL Arc script. Picking up the Anarki stable fixes would probably cover most of this.

On the trivial side: w/bars must die.

One question: why is currying so popular? I don't see the point, so I guess I'm in blub-land with respect to currying.

-----

7 points by nex3 6138 days ago | link

The post by raymyers (http://www.arclanguage.org/item?id=3997) gives some very cool examples of the power of bracket-style currying. In addition to cutting down the number of tokens in the common [foo bar _] case of bracket-notation, it allows much nicer wrapping of variadic functions - or just functions for which you want to leave more than one argument open. Compare [* 2] to (fn args (apply * 2 args)), for instance.

-----

7 points by pau 6138 days ago | link

Well, thanks for asking! I personally would add:

  A foreing function interface
Sure I could use the MzScheme for this but I would do it in a "non-standard" way, e.g. it would be my personal hack. But right now, I would jump into programming a web application in Arc if I had an easy (and "official") way to interface SQLite3...

-----

4 points by stefano 6138 days ago | link

Arc really needs a FFI. This would make possible using graphical libraries such as GTK+. I know Arc is web-oriented, but for some type of applications a conventional GUI is simply better.

-----

3 points by sacado 6136 days ago | link

As a matter of fact, I'd love Arc to not be only web-oriented. Anyway, what will the web be like in a hundred years ?

-----

12 points by tel 6138 days ago | link

Any chance we can get some official word on concerns about namespace clashing?

   An official solution for crowded namespaces (modules, packages, conventions, &c.)

-----

8 points by nex3 6138 days ago | link

We'd also be interested in hearing that you don't think it's as significant an issue as we're making it out to be, if that's your opinion.

-----

1 point by treef 6137 days ago | link

i second that!

-----

6 points by almkglor 6138 days ago | link

A wiki, in arc. Name it Arkani and save it in wiki-arc.arc for further confusion ^^ or not, I'm planning to build this myself

A pg-approved way of creating objects whose assignment methods can be changed, preferably one which requires a relatively simple attachment of functions to functions (cref. "Create your own collection in Arc*" and "a (possibly not very important) bug in annotate"). We could make it ad-hoc and force everyone to use (redef sref...) and stuff, or we could make it a pg-defined convention.

-----

5 points by nex3 6138 days ago | link

Yes. defset for annotated objects would be excellent, although I'm skeptical that the optimal way to do this is attaching functions to objects. I prefer a solution closer to how defset works now: some sort of table associating types to setters.

-----

5 points by almkglor 6138 days ago | link

Hmm. In any case I think our disagreement here has more to do with how we view types. It appears that you have the view of "type" as similar to non-abstract classes in C++: Each class defines a set of static functions for accessing class objects. Applying a function to the object dispatches according to the type of the object. An object of another class cannot be used as an object for a different class.

On the other hand, I view "type" as similar to abstract base classes in C++ (or type classes in Haskell). That is, a "type" defines what the object's interface is. The type defines a set of virtual functions which actual implementations of the type must have. So a 'table type must provide a 'keys virtual function, and a '= virtual setterfunction. Applying a function to the object dispatches according to the actual object, instead of the object's proclaimed type.

-----

4 points by nex3 6138 days ago | link

That sounds about right. My thought process was more along the lines of CLOS-style generic functions, but I think these are roughly equivalent to a more generalized notion of C++'s instance methods.

As vehemently as I may argue, I'm not actually entirely convinced that this way is best. I come from a Ruby tradition, which is deeply based on the message-passing object model, which ends up behaving a lot like type classes. I've seen this end up working very nicely in practice, facilitating duck typing and allowing all sorts of cool tricks via encapsulation.

At the same time, though, CLOS is supposed to be very excellent. And the one thing, more than any other, that appeals to me about a generic-function style object system is that it can be implemented in pure Arc. It doesn't require any more axioms than the very-simple type, rep, and annotate (née tag).

The message-passing/type-class model, on the other hand, requires what seems to me to be an incredibly radical change to the core of the language: built-in per-object tables. This just strikes me as fundamentally un-Lispy. In a sense, it eclipses lists as the fundamental data type - they're not much more than tables with "car" and "cdr" keys.

While this may be interesting territory to explore, it's being explored in other languages - Lua and Javascript both explicitly regard hash tables in the same that Lisp regards lists.

Also, from a more practical sense, I'm not convinced that the message-passing/type-class model offers anything that generic functions don't. The main benefit that I've seen is duck typing - the ability of a function to rely on its input implementing to a given interface (in this case, having functions work properly on it), rather than being a given type.

But I think this works just as well whether or not the core functions are implemented with a type-class-style system or a generic-function-style system.

Consider, for example, Ruby's favorite duck type: enumerable objects. In Ruby, every object that implements an each method that calls a lambda on each element can be declared "Enumerable," and get various methods like map for free. This can be done using attachments like so:

  (let (foo bar baz) ("foo" "bar" "baz")
    (add-attachments
      'each (fn (f)
              (f foo)
              (f bar)
              (f baz))
      nil))

  (def each (obj f)
    ((get-attachment 'each c) f))
or using generic functions like so:

  (= foo (annotate 'mytype '((foo "foo")
                      (bar "bar")
                      (baz "baz"))))

  (redef each (obj f)
    (if (~isa obj 'mytype) (old obj f)
        (do
          (= obj (rep obj))
          (f (car obj))
          (f (cadr obj))
          (f (cadr:cdr obj)))))
However, in either case, map can be defined as simply

  (def map (obj f)
    (let l nil
      (each obj [push (f _) l])))
The point of all this being that duck typing (or whatever you want to call the versatility granted by assuming a type implements an interface rather than specifically checking its type) is independent of whether or not the interface is implemented by attaching functions to objects or by defining generic methods.

-----

1 point by sacado 6138 days ago | link

I'd tend to agree with your vision, almkglor. It is more generic than the other way around : the former can be modelized with the latter, but the opposite is not true. I think Arc should remain generic and thus leave us the choice.

-----

2 points by nex3 6138 days ago | link

The problem is that Arc is not currently generic enough to allow both models. Only generic functions can be implemented with the primitives we're given - the core language has to be modified to allow arbitrary attachments.

-----

2 points by almkglor 6138 days ago | link

Attaching a setterfunction to an object allows us to create encapsulating modules which can have specific module variables modified:

  (with (var1 42
         fn1 nil)
    (def fn1 (x)
      (prn var1 ": " x))
    (add-attachments
      '= (fn (v s)
           (case s
             var1 (= var1 v)
                  (err:string "Cannot set module variable: " s)))
      'keys (fn () (list 'var1 'fn1)
      (fn (s)
        (case s
          fn1 fn1
          var1 var1)))))
Note that the above does not even care about generating its own type, because it's really a one-of table.

Also, making use of lexical environment closures severely reduces the amount of code necessary for accessor functions:

  ;Using attachments -
  (def bidir-table ()
    " Creates a bidirectional table.  Works like a normal
      table but returns keys when queried with values.
      See also [[table]] "
    (with (k-to-v (table)
           v-to-k (table))
      (add-attachments
        '= (afn (v k)
             ; determine if delete or assign
             (aif
               ; insert new pair
               v
                 (do
                   ; delete any existing pairs first
                   (self nil k)
                   (self nil v)
                   ; add it
                   ;  no point assigning this to v-to-k
                   ;  if v==k, since k-to-v will return
                   ;  that mapping first
                   (if (isnt k v) (= (v-to-k v) k))
                   (= (k-to-v k) v))
               ; deleted k
               (k-to-v k)
                 (= (v-to-k it) nil
                    (k-to-v k) nil)
               ; deleted v
               (v-to-k k)
                 (= (k-to-v it) nil
                    (v-to-k k) nil)))
        ; Only return items which were assigned as
        ; keys, so that 'ontable doesn't go over
        ; pairings twice.
        'keys (fn () (keys k-to-v))
        (annotate 'table
          (fn (k) (or (k-to-v k) (v-to-k k)))))))

  ;Using defset-type and defcall:
  (define bidir-table ()
    " Creates a bidirectional table.  Works like a normal
      table but returns keys when queried with values.
      See also [[table]] "
    (annotate 'bidir-table (list (table) (table))))

  (defcall bidir-table (c k)
    (let (k-to-v v-to-k) (rep c)
      (or (k-to-v k) (v-to-k k))))

  (defset-type bidir-table (c v k)
    (let (k-to-v v-to-k) (rep c)
      ((afn ()
             ; determine if delete or assign
             (aif
               ; insert new pair
               v
                 (do
                   ; delete any existing pairs first
                   (self nil k)
                   (self nil v)
                   ; add it
                   ;  no point assigning this to v-to-k
                   ;  if v==k, since k-to-v will return
                   ;  that mapping first
                   (if (isnt k v) (= (v-to-k v) k))
                   (= (k-to-v k) v))
               ; deleted k
               (k-to-v k)
                 (= (v-to-k it) nil
                    (k-to-v k) nil)
               ; deleted v
               (v-to-k k)
                 (= (k-to-v it) nil
                    (v-to-k k) nil)))))

-----

5 points by sacado 6138 days ago | link

A wiki would be nice too. And I don't find currying that useful.

As for arrays, what do you think of Lua's system for arrays ? In Lua, there is no explicit array (or lists), only hash tables. However, tables can be used efficiently as arrays : a table actually consists in two fields, one for numerical indices, the other one for "regular" keys.

As long as you use numerical keys, data is actually stored in an array, so there is no performance (or memory) penalty, and since this is only an implementation issue, there is no need to add explicitly vector manipulation functions. The bonus is that, if you use a very sparse array (e.g., only positions 1, 10 and 1000000, which would lead to 999997 unused positions), you get back to the hash system. It is a little slower than separating hashes / arrays, but it saves axioms.

-----

3 points by tokipin 6138 days ago | link

i love Lua's tables. after using them, other languages feel abstruse in that department, with all these alternatives that are in my mind conceptually and functionally the same thing: (key,value) pairs

[edit]it should be noted that Lua is fast, like the fastest scripting language fast

-----

2 points by nex3 6138 days ago | link

I'm not much of a fan of Lua's array system... I'd rather give programmers explicit control of their data structures.

-----

4 points by mec 6138 days ago | link

For quick prototyping I can see how Lua's tables would be extremely useful. I've been trying to think of a way to combine lisp macros with Lua tables to see how that turns out.

-----

4 points by nex3 6138 days ago | link

For currying, I'm very fond of the sort proposed by cchooper and implemented by almkglor (http://www.arclanguage.org/item?id=3972, http://www.arclanguage.org/item?id=3973). See http://www.arclanguage.org/item?id=3997 for examples of why it's awesome.

-----

7 points by sacado 6139 days ago | link

Less opaque error messages ! This is so painfull to have a message with no line. This is particullary boring since messages are somewhat cryptic as they can be the consequence of an unwanted macro-expansion...

-----

4 points by almkglor 6138 days ago | link

Well, I was thinking that attachments would help by allowing you to attach arbitrary data to arbitrary objects ^^. e.g. line numbers to symbols and lists, so your own custom built macros can report errors at linenumbers.

-----

1 point by treef 6137 days ago | link

that is a neat idea ... some thing to add to my list of good ideas :)

-----

15 points by lojic 6136 days ago | link

Regular expressions

-----

4 points by lojic 6133 days ago | link

pg, what do you think of adding regular expressions to the poll since we have 8 votes here? Not sure if folks would come back to vote the poll, but it's worth a shot.

-----

4 points by croach 6107 days ago | link

I'd love to see regular expressions in Arc, but just like my comment on infix notation, I don't think it needs to be part of the core language. A module system is already in the list of candidates and I believe with a good module system, regular expressions could be added to the language by an outside and benevolent hacker or by the core team (i.e., PG and RM) as an included module rather than as part of the Arc language itself. By the same token, I would love to see of the code now (e.g., the app.arc, srv.arc, and html.arc) refactored into modules once the system is complete.

-----

2 points by almkglor 6106 days ago | link

As an aside, both me and raymyers have written basic module systems.

The hardest part (which we haven't built yet) is macros, both private in the module, and macros defined by the module for use by external code/other modules.

-----

7 points by treef 6135 days ago | link

I would also like to hear what pg has to say about the doc strings we are using the the Anarki repo.

-----

8 points by serhei 6132 days ago | link

A profiler! You promised a profiler:

http://paulgraham.com/ilc03.html

And an FFI, I guess.

-----

2 points by lojic 6111 days ago | link

Pretty late comment, but I'd really like a nice interface to a RDBMS. PostgreSQL would be my first choice, but beggars can't be choosers.

If this can be done nicely enough with a FFI, then consider this another vote for that.

-----

5 points by eds 6138 days ago | link

First-class macros? Anyone?

If not the macros than more ergonomic error messages.

-----

3 points by tokipin 6138 days ago | link

as a lisp noob i'm curious what specific sorts of things you could do with first-class macros. perhaps swapping out macros for a given macro call? say in one pass of a program (function) a particular macro call is expanded by macro1, and in the second pass the macro call is expanded by macro2

-----

6 points by kens1 6138 days ago | link

The problem I ran into today was:

  arc> (apply or '(nil t nil))
  Error: "Function call on inappropriate object #3(tagged mac #<procedure>) (nil t nil)"
I assume that first-class macros would let me do this.

-----

4 points by nex3 6138 days ago | link

There are folks here who know more about this than I do, but I think first-class macros would be very useful for creating a pure-Arc module system.

-----

2 points by eds 6138 days ago | link

I'm not exactly an expert on this, but I think it would allow you to put a macro literal in functional position. Right now:

  arc> and
  #3(tagged mac #<procedure>)
  arc> (eval (list and 1 nil 2))
  Error: "Bad object in expression #3(tagged mac #<procedure>)"
So if you had theoretical first-class macros, you could do stuff like use backquote to protect a macro you use from being overridden. (This wouldn't be necessary if we had a module system, but that might require first-class macros itself.) So in the (contrived) example below, foo works fine because prs is a function, but bar fails because foo is a macro.

EDIT: Actually, function literals in functional position only works on Anarki. It might be nice to have that fix in the official version.

  arc> (mac foo args `(,prs ,@args))
  #3(tagged mac #<procedure>)
  arc> (mac bar args `(,foo ,@(keep [isa _ 'sym] args)))
  #3(tagged mac #<procedure>)
I think first-class macros might be useful in making infix math expansion occur at compile time rather than run time... but I'm not completely sure on that one.

-----

4 points by Jesin 6136 days ago | link

Yes, please. First class macros and the ability to use macros and functions (rather than just their names or definitions) in functional position would be great, and macro names would not have to shadow variable names gobally anymore. In my opinion that is one of the biggest problems with Arc.

-----

2 points by sacado 6136 days ago | link

Oh, yes. That one drove me crazy a few times.

-----

1 point by nex3 6138 days ago | link

fns in functional position work fine for me in arc2... can you give an example where they die?

-----

3 points by eds 6138 days ago | link

  arc> (eval (list + 3 4))
  Error: "Bad object in expression #<procedure:...mming\\Arc\\ac.scm:602:9>"

  arc> (mac foo args `(,+ ,@args))
  #3(tagged mac #<procedure>)
  arc> (foo 3 4)
  Error: "Bad object in expression #<procedure:...mming\\Arc\\ac.scm:602:9>"

-----

1 point by nex3 6138 days ago | link

Oh, I thought you meant function literals as in calls to fn. But calling functions from lists... yeah, that would be great to have.

-----

7 points by hkBst 6134 days ago | link

latest mzscheme (that would be 372 now) as the default hosting environment

-----

3 points by Jesin 6136 days ago | link

Seems to me that currying would be an unnecessary hassle. Where would you actually use it?

-----

4 points by offby1 6138 days ago | link

a way to open a client socket

-----

1 point by map 6133 days ago | link

Some string-handling functions. I couldn't locate a way to find a string in another string.

-----