Arc 3.1‎ > ‎

Known Bugs and Gotchas

  • Mutating lists sometimes fails.
Fixed by waterhouse: Racket lists properly mutated.
The fix has been pushed to Anarki.
The bug is not present in ar or in Arc runtimes not based on Racket.

  • srv throttles all connections when Arc is run from behind a reverse proxy server
In Hacker News an HTTP connection made from a browser to port 80 talks directly to the Arc server.  When there are too many requests coming too quickly from a particular IP address the abusive-ip function in srv.arc throttles (slows down) responding to connections from that IP address.

Another common configuration is to set up another web server such as nginx or Apache as a reverse proxy, where the browser connects to the front end web server on port 80 (or port 443 for https connections), and the front end server forwards (perhaps some of) the connections to the Arc server.  The front end server can perform lower level utility tasks such as SSL, virtual domain hosting, allowing multiple web applications to run on one domain name, or serving static content.

However, when connections to srv.arc are coming from the reverse proxy server then all requests are coming from the same IP address (such as localhost, when Arc and the reverse proxy server are running on the same computer), which srv.arc quickly throttles and the server runs slowly for everyone.

A quick fix is to disable abusive-ip:

(def abusive-ip (ip) nil)

  • Macros take effect at compile time
Because macros look a lot like functions, people often extrapolate and expect that macros will work like functions in ways that they turn out not to.  An example of when this can be a "gotcha" is that redefining a macro doesn't affect code that has already used it:

arc> (mac foo () 33)
#(tagged mac #<procedure: foo>)
arc> (def bar () (foo))
#<procedure: bar>
arc> (bar)
arc> (mac foo () 44)
*** redefining foo
#(tagged mac #<procedure: foo>)
arc> (bar)

If foo had been a function, then bar would use the most recently defined foo because functions are called at runtime.  However macros are invoked when code is compiled, not when it is run, so the foo that is used by bar is the foo that was in effect at the time that bar was defined, not when it is called.

(The upshot of this is that if you change a macro, then you also need to reload all the code that uses the macro for the change to take effect).

Another example is how apply doesn't work on macros:

(in 'a 'b 'c 'd 'a)     -> t
(apply in '(a b c d a)) -> error
    apply works at runtime, not at compile time, which is why it works on functions but not on macros.

    Note that if you implement Arc as an interpreter than you don't have to have these restrictions because an interpreter doesn't need to have a separate compilation phase (though it may may turn out to be slow to reapply macros on every evaluation of an expression).

    • List splicing is broken in nested quasiquotes
    Quasiquotes are commonly used in macros, and people often would like to use nested quasiquotes to write macro defining macros.  Historically Common Lisp has supported the ,@ list splicing syntax in nested quasiquotes, while this has been broken in Scheme.  Since Arc 3.1 uses Racket's quasiquote implementation it inherits the bug from Racket.

    Common Lisp:
    > (eval ``(+ 1 ,,@(list 2 3) 4))
    (+ 1 2 3 4)

    Arc 3.1:
    arc> (eval ``(+ 1 ,,@(list 2 3) 4))
    (+ 1 2 4)

    Alan Bawden's Quasiquotation in Lisp paper [PDF]

    • Some Arc expressions can generate Racket lists
    This Arc expression

    arc> ((fn args args) 'a 'b)
    (a b)

    looks like it's producing an Arc list (terminated with a nil), but is in fact creating a Racket list:

    arc> (cdr (cdr '(a b)))
    arc> (cdr (cdr ((fn args args) 'a 'b)))

    Because Arc functions such as no, iso, and so on commonly treat Racket lists the same as Arc lists, the difference often isn't noticeable:

    arc> (iso '(a b) ((fn args args) 'a 'b))

    However there are some cases where it does make a difference.  Tables check their keys for equality using Racket's equal?, not Arc's iso, so using a Racket list as a table key is not the same as using an Arc list:

    arc> (let h (table)
           (set (h '(a b)))
           (h ((fn args args) 'a 'b)))

    in fact a table can contain the "same" list twice as a key, once as an Arc list and once as a Racket list:

    arc> (let h (table)
           (set (h '(a b)))
           (set (h ((fn args args) 'a 'b)))
           (keys h))
    ((a b) (a b))

    • Complex setforms are run inside of an atomic
    Suppose that you want to read from an input stream where you may need to wait for the characters to be available, such as from a terminal or network connection.  This works fine: 

    (= chars (allchars s))

    the current thread will pause waiting for the characters to be read, and the other threads in the program will continue in parallel.  But now imagine that you want to store the characters in a more complex location:

    (= result!chars (allchars s))

    At this point you'll likely hang your web server until the input is available.  The reason is that complex setforms such as the second example are automatically run inside of an atomic block.  While this can help with getting only one thread to make a change to a data structure at a time, it does also mean that any other thread using atomic will block if the set operation takes a long time.

    • readline includes the newline in empty lines
    Take this input



    readline will read this as two lines:

    arc> (fromstring "one\n\nthree\n" (drain (readline)))
    ("one" "\nthree")

    A fix is available in readline1 and in ar, though those versions also add the functionality of accepting lines with CR-LF endings.

    • the REPL reads only up to the character that completes the input expression
    arc> (readline) ;Fee fi fo fum
    " ;Fee fi fo fum"

    • Global macro names take precedence over local lexical variables
    When a local lexical variable has the same name as a global function or other value, identifiers with that name refer to the enclosing lexical variable, not the global variable:

    arc> (let map + (map 2 3))

    However, when the global value is a macro, then the identifier refers to the global macro, not the local lexical variable:

    arc> (let obj + (obj 2 3))
    #hash((2 . 3))

    This is reversed in ar, so that an identifier being a local lexical variable takes precedence over it being a macro.


    • The whilet utility doesn't support destructuring like iflet and whenlet do
    arc> (= my-alist '((a 1) (b 2) (c 3) (d 4)))
    ((a 1) (b 2) (c 3) (d 4))
      (whilet (k v)
        (prn k " " v))
    Error: "Function call on inappropriate object a (1)"
      (whilet entry
        (prn entry.0 " " entry.1))
    b 2
    c 3
    d 4