Arc Forumnew | comments | leaders | submit | shader's commentslogin

Here's a more functional version of require that works in the current version of arc:

  (mac require (n (o typ))
    `(if (and ,typ ,n (no:isa ,n ,typ))
           (err:string "parameter "
                       ',n
                       " must be of type "
                       ,typ)
         (no ,n)
           (err:string "parameter " ',n " is required")))

-----

1 point by shader 5357 days ago | link

Interesting, there is a bug (feature?) in arc that lets you shadow t in a function, even though you normally can't rebind it. Should this be fixed?

-----

1 point by Pauan 5357 days ago | link

Why? `t` is a nice and short variable name, and I would expect it to work fine in function arguments. Hm...

  ((fn (t) t) 10)         -> 10
  ((fn (quote) quote) 10) -> 10
  ((fn (nil) nil) 10)     -> nil
Seems nil doesn't like being shadowed. Works fine in my interpreter, though: they all return 10.

-----

1 point by rocketnia 5357 days ago | link

"Seems nil doesn't like being shadowed."

That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10), which destructures the first 0 elements from 10, treating it as a degenerate cons list.

I actually use this behavior in practice, 'cause it's useful to have at least some parameter syntax that doesn't bind any variables, rather than just using a conventional name like "ignored" or "_". Most of the time I use it in a much-too-big 'withs block, where I abuse one of the bindings for sanity-checking or other side effects. ^^ I wouldn't worry about it too much, though.

On the other hand, nil's behavior makes it more verbose to determine whether something's usable as a local variable; you have to check (and x (isa x 'sym) (~ssyntax x)). In Lathe, I define 'anormalsym to do just that. (It might be more accurate to change it to (and (isa x 'sym) (~in x nil t 'o) (~ssyntax x)).)

Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:

  arc> (let a.b 4 a.b)
  Error: "reference to undefined identifier: _a"
  arc> (let a.b 4 ($ a.b))
  4
That could be a bug.

Personally, I'd like for ssyntax-burdened names in parameter lists to use user-defined parameter syntaxes, like custom forms of destructuring and whatnot. And then, just for axiomatic simplicity's sake, I'd like things like 'o to be implemented using the same system, using ssyntax-burdened names like ".o".

-----

1 point by Pauan 5357 days ago | link

"That's because ((fn (nil) nil) 10) is supposed to be the same as ((fn (()) nil) 10)"

nil is such a weird value... it's false, a symbol, and an empty list all at once. I actually had to do some hacky stuff to get () and '() and nil to behave properly.

"Speaking of (~ssyntax x), ssyntax is usable for local variable names, but naturally, you can only get the values from the Racket side:"

I plan to implement ssyntax at the reader level in my interpreter, so that shouldn't be an issue.

-----

1 point by evanrmurphy 5357 days ago | link

"nil is such a weird value... it's false, a symbol, and an empty list all at once."

Perhaps nil should serve as the base case for every type, rather than just some of them. In Arc, it already does this for booleans, lists and strings (""), but it could be extended to support tables (#hash()), numbers (0), symbols (||) etc. Then it would have more consistency as a concept.

-----

2 points by shader 5357 days ago | link

I've run into issues with nil the symbol vs nil the value. It's caused some issues when I wanted to use arc's sym type to represent variable names in a class compiler project I'm working on. If the variable's name is nil, all kinds of things stop working, and I've had to resort to calling some scheme functions directly to get proper handling of symbols.

I wish that 'nil and nil could be kept somewhat separate, with the first being just a symbol, and the second being equivalent to the base value for all data structures. Otherwise the symbol type is broken and inconsistent, since you cannot accurately (or at least completely) represent code as data.

-----

2 points by Pauan 5356 days ago | link

My interpreter treats 'nil and nil as separate. Not sure if that's a good idea, but I'd rather wait and see if it causes problems before changing it:

  (is 'nil nil) -> nil
This does raise one interesting question, though... obviously 'nil is a sym, but if (is nil ()) is t, then shouldn't (type nil) return 'cons?

-----

2 points by waterhouse 5355 days ago | link

() is not a cons cell, and while (car ()) is well-defined (to be nil), you can't set the car or cdr of (). It is definitely not a cons.

On the other hand, (listp nil) should be true. In fact, its precise definition should probably be (and is, or is equivalent to) this:

  (def listp (x)
    (or (is x nil)
        (and (acons x)
             (listp (cdr x)))))

-----

1 point by Pauan 5354 days ago | link

However, I can easily define nil so that it's type is 'cons, but it would throw an error if you try to assign to the car or cdr. That may break code that assumes that 'cons != nil though.

Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy? Probably. Especially since nil is a singleton and you can't create more nil's, so it would be a global change.

Right now, nil does have a type of 'sym, but it seems weird to treat it mostly like an empty cons cell, but not have it's type be 'cons. So I figured I could play around with it and try giving it a type of 'cons and see how badly it breaks stuff.

-----

2 points by evanrmurphy 5354 days ago | link

"Actually, I could represent nil as an actual 'cons cell, so that assigning to the car or cdr would work. Crazy?"

That's a bit crazy. :)

PicoLisp does something reminiscent. Every one of its data structures (numbers, symbols, nil and conses) is implemented using the low-level cons cell structure (i.e. a pair of machine words). [1] They talk about nil's representation fulfilling its dual nature as both a symbol whose value is nil and a list whose car and cdr are nil; both the symbol predicate and the list predicate return true when applied to nil:

  : (sym? NIL)
  -> T
  : (lst? NIL)   
  -> T
I'm not sure that they let nil's car and cdr be assignable though, because "NIL is a special symbol which exists exactly once in the whole system." [2]

--

[1] http://software-lab.de/doc/ref.html#vm

[2] http://software-lab.de/doc/ref.html#nilSym

--

Update: Oops, I just noticed a lot of this comment could be considered redundant with the grandparent comment by waterhouse. Sorry for that.

-----

1 point by rocketnia 5357 days ago | link

My code has a lot of [string:or _ "nil"] to handle that kind of stuff. ^_^ I've just kinda accepted it ever since http://arclanguage.org/item?id=10793.

I do 'string lists into strings a lot, but that would continue to work if 'nil and '() were separate values.

-----

1 point by aw 5357 days ago | link

If the variable's name is nil, all kinds of things stop working

Can you give an example? Are you trying to use nil as a variable name in Arc, or simply to have a data structure representing variables where some of those variables are named nil?

-----

1 point by shader 5355 days ago | link

I'm creating a compiler for a class, and I've been representing variable names in the ast with symbols. I could use strings instead, and may end up doing so, but symbols seemed a more elegant solution.

-----

1 point by Pauan 5356 days ago | link

So nil would be an ubertype that basically just means "empty"? One issue with that is (is 0 nil) would be t... I think it's useful to not always treat 0 as the same as nil.

Not sure about empty tables, though... maybe it would be fine to treat those as nil. We already treat an empty list as nil, after all.

Actually, what you're suggesting is very similar to how languages like JavaScript and Python do it, with 0 null undefined NaN "" etc. being falsy. So in JS, you can do this:

  var foo = 0;
  if (foo) {} // will not be evaluated

  foo = "";
  if (foo) {} // will not be evaluated

  foo = NaN;
  if (foo) {} // will not be evaluated
On the other hand, both those languages support actual Booleans, and they treat an empty array as true.

-----

1 point by rocketnia 5357 days ago | link

Yeah, t should be rebindable, right? :)

-----

2 points by aw 5357 days ago | link

http://awwx.ws/localt0

-----

1 point by rocketnia 5357 days ago | link

I mean making 't an everyday global variable, like fallintothis does at http://arclanguage.org/item?id=11711.

As far as prior work goes, I think this topic is the last time "can't rebind t" came up: http://arclanguage.org/item?id=13080 It's mainly just me linking back to those other two pages, but it's also a pretty good summary of my own opinion of the issues involved.

-----

2 points by aw 5357 days ago | link

In my runtime project, nil and t are ordinary global variables... if someone turns out to want the rebind protection feature, I'll add it as a compiler extension (which other people could then choose to apply, or not, as they wished).

-----

1 point by Pauan 5357 days ago | link

What I would do is make it print a warning, but still allow it. Something like, "hey, you! it's a bad idea to rebind nil; you'll probably break everything! use (= nil ()) to fix the mess you probably made"

-----

1 point by aw 5357 days ago | link

Printing a warning is a good idea. Whether rebinding nil could be fixed with "(= nil ())" is an interesting question, you might (or might not, I haven't tried it) find that rebinding nil breaks Arc so badly that = no longer works... :-)

I see a potential for a contest here: how to permanently break Arc (with "permanently" meaning you can't recover by typing something at the REPL), in the most interesting way, using the fewest characters. (Non-interesting being, for example, going into an infinite loop so that you don't return to the REPL).

-----

1 point by Pauan 5357 days ago | link

Quite possibly. It should work in my interpreter, though. Actually, () and nil are two different values, but they're special-cased to be eq to each other. It was the only way I found to make one print as "nil" and the other print as "()" From an external point of view, though, they should seem the same.

Also, if = doesn't work, assign probably would:

  (assign nil '())
I just tested it in my interpreter:

  (assign nil 'foo) -> foo
  nil               -> foo
  'nil              -> nil
  ()                -> ()
  '()               -> nil
  (assign nil '())  -> nil
  (is nil ())       -> t
  
So yes, it should be possible to recover, even after overwriting nil (at least in my interpreter. I don't know about MzScheme)

-----

1 point by Pauan 5357 days ago | link

Exactly! nil too. :P

-----

2 points by shader 5357 days ago | link

Hmm... the issue with that is that you might start having to quote nil or t whenever you want to actually mean nil or t, instead of just typing them in normally.

I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil. Maybe doing destructuring on rest args would help solve that problem in most cases?

-----

1 point by Pauan 5356 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil, or was left empty and defaults to nil."

I too have sometimes wished for that in JavaScript, but let me tell you a little story. I was writing a syntax highlighter, and got it working fine in Chrome and Firefox 3.5, but there was a bug in Firefox 3.0.

You see, I was using this bit of code here:

  output.push(text.slice(curr.index[1], next && next.index[0]));
If `next` doesn't exist, it will pass the value `undefined` to the `slice` method. In JS, if you don't pass an argument, it defaults to `undefined`, so this is supposed to behave like as if I hadn't passed in the argument at all.

But in Firefox 3.0, the slice method behaves differently depending on whether you pass it `undefined`, or don't pass it any arguments. So, I had to use this instead:

  if (!next) {
      output.push(text.slice(curr.index[1]));
  } else {
      output.push(text.slice(curr.index[1], next.index[0]));
  }
This was (thankfully) fixed in 3.5. The moral of the story: most of the time it doesn't matter whether the caller passed nil, or didn't pass anything. You can treat the two situations as the same.

Consider this hypothetical example in Arc:

  (your-func 5 (and x y z))
If x, y, or z are non-nil, it will be passed in as usual. On the other hand, if any of them are nil, it will be like as if you had used (your-func 5 nil).

By behaving differently when nil is passed in vs. not passing in an argument, you might cause the above example to break. Or perhaps it would work, but the behavior would be subtly different... introducing bugs.

By having different behavior depending on whether an argument is passed or not, you force callers to do this, instead:

  (iflet val (and x y z)
    (your-func 5 val)
    (your-func 5))
Note the redundancy. In fact, this is even more important in Arc (compared to JavaScript) because you can use any expression, such as (if), a macro call, etc.

So... let me ask: what situations do you really need to know whether the caller actually passed in nil, or didn't pass anything at all?

-----

1 point by rocketnia 5356 days ago | link

Great point. In fact, I don't check whether an optional argument was passed very often, and the times I do, I usually expect to regret it at some point, for exactly that reason. ^_^

-----

1 point by rocketnia 5357 days ago | link

"I do wish there was an easier way to tell whether or not a value was provided as nil"

I share this sentiment. One thing we could do is have a space of hidden-from-the-programmer variables which tell you whether other variables have been bound. They can be accessed using a macro:

  (= given-prefix* (string (uniq) "-given-"))
  (mac given (var)
    ; NOTE: I don't think this will work properly for nil, but nil is
    ; never a local variable name anyway.
    (sym:+ given-prefix* var))
The implementation of argument lists would need to be aware of 'given-prefix* and bind the prefixed variables at the same time as the regular ones.

---

"Maybe doing destructuring on rest args would help solve that problem in most cases?"

What do you mean by that?

-----

2 points by shader 5357 days ago | link

Well, if you use a rest arg for all optional values, and then use some form of destructuring bind on that list to extract your optional arguments, then you can tell whether or not they were passed in or merely defaulted to nil by just searching the arg list.

  (def test args
    (if (assoc 'c args)
          (pr "c was passed")
        (pr "c was not passed")))

-----

1 point by rocketnia 5357 days ago | link

I still don't follow. We can already manage the argument list manually, but in most of the suggestions here, we can only do it if we don't destructure it in the signature (unless we use more complicated kinds of destructuring).

  ; Current options:
  
  (def test args
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (let missing list.nil  ; This is just something unique.
    (def test ((o c missing))
      (pr:if (is c missing)
        "c was not passed"
        "c was passed")))
  
  
  ; Some hypothetical options and non-options:
  
  (def test (& (c))
    (pr "no way to tell if c was passed"))
  
  (let missing list.nil
    (def test (& (c))
      (pr "still no way to tell if c was passed")))
  
  (def test (& args)
    (let ((o c)) args
      (pr:if (len> args 0)
        "c was passed"
        "c was not passed")))
  
  (def test (& (&both args (c)))  ; Destructure twice.
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))
  
  (def test ((o c nil c-passed))
    (pr:if c-passed
      "c was passed"
      "c was not passed"))
  
  (def test ((o c))
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (c)  ; Parameter lists are just destructuring.
    (pr:if given.c
      "c was passed"
      "c was not passed"))
  
  (def test (&both args (c))
    (pr:if (len> args 0)
      "c was passed"
      "c was not passed"))

-----

1 point by Pauan 5356 days ago | link

Brilliant! In fact, you could write a macro that would do that for you:

  (mac defreq (name args . body)
    `(w/uniq gen
       (def ,name ,(map (fn (x) `(o ,x gen)) args)
         ,@(map (fn (x) `(if (is ,x gen) (err:string "parameter " ',x " is required"))) args)
         ,@body)))

  (defreq foo (x y) (+ x y))
  (foo)     -> x is required
  (foo 1)   -> y is required
  (foo 1 2) -> 3
It probably breaks with rest arguments, but I think you could get those working too.

-----

1 point by Pauan 5356 days ago | link

Or this version, which is even better:

  (mac defreq (name vars . body)
    (if (isa vars 'cons)
          (let exp (len vars)
            `(def ,name args
               (let giv (len args)
                 (if (< giv ,exp)
                       (err:string "expected " ,exp " arguments (" giv " given)")
                     (apply (fn ,vars ,@body) args)))))
        `(def ,name ,vars ,@body)))


  (defreq foo (x y) (+ x y))
  (foo)     -> error: expected 2 arguments (0 given)
  (foo 1)   -> error: expected 2 arguments (1 given)
  (foo 1 2) -> 3
  
  (defreq foo args args)
  (foo)     -> ()
  (foo 1)   -> (1)
  (foo 1 2) -> (1 2)
  
It fails on functions that take required and rest args, though:

  (defreq foo (x y . args) (list x y args)) -> error
Err... right, you were talking about detecting if an argument was nil or not given... but I realized that the same technique could be used to write a version of def that implements required arguments even in a language where every argument is optional.

-----

1 point by Pauan 5357 days ago | link

Only if you actually rebind them. It's like using `quote` as a variable name: you can do it, but most people won't because that's silly. I just think it's nice to allow it, on the off chance it's actually useful. It just feels weird to arbitrarily say "you can't rebind nil and t" but allow rebinding of everything else.

-----

1 point by Pauan 5357 days ago | link

Using err:string is good, but why the unnecessary `and` check? Am I missing something?

-----

1 point by shader 5357 days ago | link

The and is required to make sure that a type was provided, otherwise it will always fail the type check. Also, if you leave n out of the and clause, it will still pass if the required type is sym. Maybe that should be fixed.

-----

1 point by Pauan 5357 days ago | link

Hm... yes, you're right. Odd, I remember it working fine when I tested it earlier. This should work correctly:

  (mac require (n (o t))
    `(if (no ,n)                  (err:string "parameter " ',n " is required")
         (and ,t (no:isa ,n ,t))  (err:string "parameter " ',n " must be of type " ,t)))

-----

1 point by shader 5396 days ago | link | parent | on: Anarki $ escape to scheme bug?

Not necessarily interpretation.

It is theoretically possible to do a form of JIT compilation, where you compile the normal function (presuming function calls), but keep the source around and dynamically compile and link in branches of code for when the argument is a macro.

Basically, you'd have a jump table based on the argument. If it was a function, you'd jump to the function version. If it was a macro, you'd look it up in a hash table to see if it had been used before. If not, compile the function again and add the new one to the hash table.

Obviously, this wouldn't be as fast as a normal function call, since you'd have to do runtime checking of one of the arguments, but if you were careful the common case could be quite fast, with only the macro calls being slow and only new macro calls taking more than a small amount of time.

Of course, this is only one possible implementation. It is true that any language that supports macros could not be entirely statically compiled, but then again as far as I know any higher-order functional language requires runtime compilation as well. It partly depends on how you define "compilation."

-----

1 point by rocketnia 5396 days ago | link

It is true that any language that supports macros could not be entirely statically compiled

Actually, in a language with no side effects, I don't think there's anything keeping run time level code from being used at compile time, as long as there's already enough information to compile it. Who would ever know? ;) This means you have the most unique benefits of macros, that of being able to program them in the same language and environment as the rest of the program.

This is the basis behind Blade, an extensible statically compiled language I was making before I realized the lack of side effects made it really hard for me to write code for. :-p

-----

1 point by evanrmurphy 5396 days ago | link

> before I realized the lack of side effects made it really hard for me to write code for. :-p

So that's why you stopped working on Blade. I was curious! ^_^ Can you speak more about your realization that side effect-free programming is impractical? I've sometimes found myself tempted by pure FP, you see.

-----

2 points by waterhouse 5394 days ago | link

[PRO TIP: Click "link" (or "parent") on this comment, so you can view it (mostly) by itself rather than squashed against the right side of the page.]

For me, when I was writing my spider solitaire program, I had a couple of global variables, the-deck and the-piles, which represented the state of the game. the-deck was a list of randomly permuted cards, which were integers from 0 to 51 (or 0-25 with two suits, or 0-12 with one suit). the-piles was a list of piles, which were lists of cards (the zeroth element was the top of the pile), and the cards were of the form (list card revealed?), where "card" = 0-51 and "revealed" = t/nil. (Yes, a card is an integer in the deck, but a list of an integer and t/nil on the board. This does not need to be changed.)

To flip over the top card of the nth pile, I went:

  (= (cadar the-piles.n) t)
To deal a card (face up) to top of each pile from the deck, I said:

  (forlen i the-piles
    (push (list pop.the-deck t) the-piles.i))
To move a chain of n cards from (the top of) one pile to another, I went:

  (let (a b) (split the-piles.i n)
    (= the-piles.i b
       the-piles.j (join a the-piles.j)))
Also, I created a global variable called "prev-states", and every move the user performed would

  (push (deepcopy:list the-deck the-piles) prev-states)
, and I had an (undo) function that would

  (let (a b) pop.prev-states
    (= the-deck a the-piles b))
. Now, what would you do in a "pure functional programming" language? If there were no assignment whatsoever, not even to global variables, I'd probably effectively implement global state by making every function take an extra argument representing global state, and shifting code around in other ways. If you could assign to global variables but all data structures were immutable (I think Clojure is like that, correct me if I'm wrong), then I'd have to keep rebinding the entire "the-piles" list when all I wanted to do was alter one or two piles. Revealing the top card of the nth pile would look something like this:

  (= the-piles
     (join (take (- n 1) the-piles)
           (list:cons (list (car the-piles.n) t) (cdr the-piles.n))
           (drop n the-piles)))
Dealing wouldn't be too horrible:

  (= the-piles
     (map [cons pop.the-deck _] the-piles))
(Note that "push" and "pop" are legal because they just do global assignment; they don't alter any data structures). And then moving a chain of n cards from i to j would look like this:

  (= the-piles
     ; ... oh god I have to know whether i>j
     ;(join (take (dec:min i j) the-piles)
     ;      ... egad I may as well just say (if (< i j) ...
     ;(if (< i j)
     ;    (join (take dec.i the-piles)
     ;          (list:drop n the-piles.i)
     ;          (cut the-piles i dec.j)
     ;          (list:join (take n the-piles.i) the-piles.j)
     ;          (drop j the-piles))
     ;    (join ... egad the repetition is intolerable
     ; hmm, this'll work
     (with (new-i (list:drop n the-piles.i)
            new-j (list:join (take n the-piles.i) the-piles.j))
       (if (< i j)
           (join (take dec.i the-piles)
                 new-i
                 (cut the-piles i dec.j)
                 new-j
                 (drop j the-piles))
           (join (take dec.j the-piles)
                 new-j
                 (cut the-piles j dec.i)
                 new-i
                 (drop i the-piles)))))
On the plus side, I would no longer need to use "deepcopy" when creating a backup of the game state.

  (push (list the-deck the-piles) prev-states)
Which is pretty much the only benefit I'd get from outlawing modification of data structures, which I assume is what "pure functional programming" would mean. Don't even talk to me about how this would look if I had to simulate global variables...

Functional programming is a useful tactic in some cases. For example, I use a "chain-length" function, which might, say, take the list (7♥ 8♥ 9♣) and return 2, the number of revealed cards of consecutive rank of the same suit. I first thought about making it take "i" as an argument, referring to the ith pile in the-piles, but I made it take the list "xs" instead, and that allowed me to use it for other purposes: e.g. determining whether a move of n cards from pile i to j will create a longer single-suited chain. (Like, moving that 7♥ 8♥ chain onto a 9♥. I have a couple of AI-assistance procedures that look for moves satisfying conditions like that.) The expression is:

  (is (+ n chain-length:the-piles.j)
      (chain-length:join (take n the-piles.i) the-piles.j))
Had I written "chain-length" to refer just to the ith pile in the global "the-piles" variable, I'd... basically have to rewrite most of it. So factoring out things into individual functions (which, btw, is, I think, in the category of things called "functional programming", but isn't usually the same as making things side-effect-free) is frequently a good thing--it makes the program easier to write, to read, to understand, and to maintain.

But the goal, the reason you'd want to do it, is not because it's called "functional programming"--it's to make the program easier to write/read/understand/maintain. And if something called "functional programming" makes it harder to do those things, then I would advocate throwing it out. I think preventing all side effects, or all side effects except those on global variables, falls firmly into the latter category; I think my example demonstrates this.

-----

3 points by rocketnia 5393 days ago | link

Your examples can be simplified quite a bit. ^_^

  ; Move a chain of n cards from i to j.
  (zap [let (taken leftovers) (split _.i n)
         (copy _ i leftovers j (join taken _.j))]
       the-piles)
  
  
  ; Reveal the top card of the nth pile.
  
  (def copdate (orig . kvs)
    (apply copy orig (mappend [list _.0 (_.1:orig _.0)] pair.kvs)))
  
  (zap [copdate _ n [cons (list _.0 t) cdr._]] the-piles)
In principle I agree, disabling the programmer's ability to use mutation without reason is just frustrating. But I don't expect an example to help show this; most examples we could come up with would probably have easy-in-hindsight solutions like this. I think the point is that mutation gives us more easy ways to do things, so that we can quickly experiment and stuff.

-----

2 points by akkartik 5390 days ago | link

  ; Reveal the top card of the nth pile.
  
  (def copdate (orig . kvs)
    (apply copy orig (mappend [list _.0 (_.1:orig _.0)] pair.kvs)))
  
  (zap [copdate _ n [cons (list _.0 t) cdr._]] the-piles)
How the heck is this 'simplified' compared to:

  (set:cadar the-piles.n)
? :)

-----

1 point by rocketnia 5389 days ago | link

I see you want to push the example to its limit a bit. ^_^ Well, I assume this hypothetical language would be stocked with utilities that made up for what its pureness missed out on. How believable are these?

  (def zipdate (func subject . indices)
    (iflet (first . rest) indices
      (copdate subject first [apply zipdate func _ rest])
      func.subject))
  
  (def zet (subject . args)
    (apply zipdate [do t] subject args))
  
  (zap zet the-piles n 0 1)

-----

1 point by evanrmurphy 5389 days ago | link

Was he calling it a simplification of one of waterhouse's more complex snippets, such as the one below?

   (= the-piles
       (join (take (- n 1) the-piles)
             (list:cons (list (car the-piles.n) t) (cdr the-piles.n))
             (drop n the-piles)))

-----

1 point by rocketnia 5389 days ago | link

That's definitely what I meant, and I was going to just say so, but whan I saw the ":)" I realized akkartik probably knew that and meant something like "that may not be 'oh god' complicated, but it's still not nearly as convenient as the mutating code." I don't know if that's a correct interpretation, but it made it more interesting to respond. ^_^

-----

1 point by akkartik 5389 days ago | link

No I didn't think about it nearly that much, just misunderstood which snippet you were responding to :/

-----

1 point by rocketnia 5389 days ago | link

Oh, then I'm glad that's resolved now. XD;;

-----

1 point by evanrmurphy 5394 days ago | link

Yeah, I like the way you handle the-deck and the-piles. And I like this style of programming in general (i.e. a few global variables to keep state, functions corresponding to verbs in the problem space that manipulate the globals in an intuitive way). You've reminded me of how preventing all side effects could get awkward by ruling out this style of programming entirely.

To clarify, I'm not mostly interested in eliminating side effects because of some obsession with purity, but rather for performance sake. I've been pursuing so many lang design choices like fexprs/first-class macros that are supposed to really cripple performance, that I'm starting to worry my hobby implementations aren't even going to run. I'd heard that Haskell and Clojure get a big performance boost from enforced immutability/no side effects because it guarantees that most operations can safely be run concurrently, so I've just considered it an option.

I'm a real novice when it comes to optimization, and I'm certainly interested in techniques that could help me get decent performance without having to sacrifice mutability. TCO, strategic memoization, compiler hints? Any tips on this front could be really useful to me.

-----

2 points by akkartik 5394 days ago | link

Yeah there's a bunch of haskell papers on this. Among other things, purity and lazy eval allow them to do crazy loop-fusion transformations: http://stackoverflow.com/questions/578063/what-is-haskells-s...

I did compiler work in a past life, but it was C compilers where you have to be crazy conservative in your transformations.

-----

2 points by rocketnia 5395 days ago | link

Aww, you know I can't resist talking about my hobby horses. XD

Blade is written in Groovy right now, and to make partial evaluation possible, everything is done in continuation-passing style. Partial calculations' continuations are held in stasis until the definitions they're waiting for become available. I guess it's related to cooperative multithreading and single-assignment variables, but I'm kinda playing it by ear.

Back when my work on Blade was petering off, the main reason was that Groovy's syntax wasn't very friendly for continuation-passing style. I really missed macros, and I wasn't in the mood for writing a complicated Groovy AST transformation. I wanted to just port it to Arc or something, but I had trouble writing Arc code itself without getting hung up in all the abstraction leaks (particularly unhygienic macros) and absent features (particularly OO-style type-based dispatch and weak references).

Meanwhile, thanks to ranting about Blade here, I realized it wouldn't be a good REPL language. Single-assignment has gotta paint people into corners at a REPL, and the alternative would be to compile the whole application over and over, using some meta-runtime to orchestrate the compiles. But what runtime?

Also, I began to suspect Bllade's design itself would result in something ugly to use and possibly even unusable. A declaration's behavior would depend on definitions, and a definition's value would be what the declararions said it was, and naturally there'd be deadlocks. The deadlocks were easy to avoid in toy examples, but I didn't know what limitations and gotchas Blade would develop once it matured. (I still wonder about that.)

So a plan fell into place: I could pursue a language that had similar high hopes for extensibility as Blade, but in a REPL-friendly form. That language's evaluation model would be much more familiar to me, and it would be closer to any of the languages I might choose to implement it in, so I'd probably be able to implement it faster and more confidently. Then I could use the resulting language--and the lessons I learned from it--to experiment with different variations on CPS and finish Blade.

So I started working on that language, and I called it Penknife. ^^

Altogether, I don't think this was based on any bad experience with pure functional programming. It was more about my expectation of those bad experiences, together with the frustration of writing CPS code in longhand Groovy.

-----

1 point by evanrmurphy 5395 days ago | link

Very interesting!

> Meanwhile, thanks to ranting about Blade here, I realized it wouldn't be a good REPL language. Single-assignment has gotta paint people into corners at a REPL,

> So a plan fell into place: I could pursue a language that had similar high hopes for extensibility as Blade, but in a REPL-friendly form.

So the REPL was a large part of it. I've heard too that single-assignment is problematic for REPLs, but I don't quite understand why. Can't you just use let everywhere you'd use = and def ordinarily?

  ; Scribbling at the REPL

  arc> (= x 5)
  5
  arc> (def add1 (n) (+ n 1))
  #<procedure: add1>
  arc> (add1 x)
  6

  ; Single-assignment version

  arc> (let x 5)
  nil
  arc> (let add1 [+ _ 1])
  nil
  arc> (let x 5
         (let add1 [+ _ 1]
           (add1 x)))
  6
REPLs should provide a convenient way to grab commands from the history anyway, so you can just keep building on your previous let. It is more cumbersome, but you also avoid some of the nasty surprises that come after you've accumulated a complex state in your REPL.

Could this style of REPL'ing address the problems posed to REPLs by single-assignment, or are there larger problems I'm ignoring?

Update: In the particular REPL example I've shown, there's no need for the style transformation since nothing gets redefined. Hopefully you can still gather what I mean from it! ^_^

-----

1 point by rocketnia 5395 days ago | link

Can't you just use let everywhere you'd use = and def ordinarily?

Well, single-assignment can have variables that are introduced without values and then assigned to later. It's a bit of a relaxation of pure FP that (I think) doesn't sacrifice referential transparency. Hmm... actually, a lambda that assigns to a binding outside itself is not referentially transparent, so oh well. :-p

In any case, Blade isn't directly supposed to be a single-assignment or pure sort of language. I just want to view the space of Blade declarations as a completely orderless set, letting the compilation of each one run in parallel semantically, and I still want it to have an intuitive kind of determinism. Inasmuch as I can orderlessly orchestrate side effects in ways I find bearably intuitive, I'll probably add side effects to Blade.

Blade side effects would also be limited by the fact that I want to build up to an IDE that rewinds the compile when you edit. In fact, I'm almost certain my current plan won't work well with that. I expect custom declaration syntaxes to be supported by core-syntax declarations that happen to branch based on the declarations they find by reading the project tree, but then editing any part of a file would invalidate the branching itself and cause recompilation of all the custom-syntax declarations in that file. Seems there'll need to be a more edit-resistant kind of branching. I already expect a bit of IDE cooperation with this--it doesn't have to be as hackish as diffing plain text--but that's where my ideas leave off.

...Actually, I've got a bit of an idea already. XD A declaration that observes a file's contents in order to branch can limit its observation to some method that doesn't rely on the contents of the individual declarations, and then each of the branches can observe just its own part of the file. The core syntax already works like this, essentially splitting the file based on appearances of "\n\n[".

Anyway, you've gotten me thinking and ranting. ^_^

---

Would this style of REPL'ing address the problems posed to it by single-assignment, or are there other problems I've ignored?

The kind of problem I expect to happen is that I'll define something once, I'll define something based on that, and then I'll realize the first thing had a bug, but I won't be able to redefine it as simply as I can in Arc. Once I do enter the correct version, I'll have to paste in all the things that depend on it too.

To draw a connection, it's similar to how when you want to redefine a macro in Arc, you have to re-enter all the code that used the macro in order for it to use the new one, whereas fexprs wouldn't have this problem.

-----

1 point by shader 5403 days ago | link | parent | on: Anarki $ escape to scheme bug?

Yeah, I never use arc's tagging either, because it just isn't useful for anything other than what pg designed it for, namely differentiating between functions and macros.

Hopefully I'll be able to get my hack onto anarki soon so you guys can look at it and tell me if its an improvement at all.

-----

3 points by akkartik 5403 days ago | link

Types are useful in proportion to the number of primitives that can handle them.

That's basically why I built defgeneric; at this point functions len, iso, keep, some, rem, all are all generics, which means they can be extended for new types. (http://github.com/akkartik/arc)

In wart the type system's baked in pretty early on, and I try to use it everywhere I possibly can. (http://github.com/akkartik/wart)

-----

1 point by evanrmurphy 5403 days ago | link

From your description at http://arclanguage.org/item?id=13474, I definitely think it's a step in the right direction. It has already influenced my ideas about how to implement typing in upcoming toy interpreters.

Look forward to seeing your code.

-----

1 point by shader 5404 days ago | link | parent | on: How to help with the Arc runtime project

I'd like it if everything written on the forum was posted in some way to anarki, so that we could track its history and have a central location to go for example code.

One of the advantages of anarki is that everyone has commit access, so you don't need to wait on anyone to give you access or to integrate your code.

-----

3 points by akkartik 5404 days ago | link

Every few months I think of building a webapp that integrates anarki and arc forum, so commits link to posts referring to them, and vice versa..

-----

1 point by shader 5403 days ago | link

Interesting idea, though I'm not sure that all commits should generate a post to the forum, and I don't know how you would go about selectively generating them.

How were you thinking that it would work? Maybe you could do it the other direction: you create a specially formatted post, and all of the code in it gets auto-committed to a certain branch on anarki.

I don't know how you would update commits to mention a post created after they were, and linking from a post to a commit is rather trivial already.

-----

2 points by rocketnia 5403 days ago | link

I don't know if this really solves anything, but GitHub does have support for commenting on commits. So linking to a new Arc Forum post from an old Anarki commit is already as trivial as linking the other way around. The problem is that nobody's looking there (right?).

How about having every Anarki commit be a forum topic after all, but having those topics be hidden on a separate "commit log" page until someone promotes them?

-----

2 points by akkartik 5403 days ago | link

Rather than create forum posts for every single commit, I'd just track the RSS feeds for AF and github.com/nex3/arc, and look for URLs in one that contain URLs in the other.

Now when you go to a commit it might say "referred to at http://arclanguage.org/... "

That's it :) The problem's that it hardly seems worth a new website. Hmm, perhaps I should make it a greasemonkey script running on arclanguage.org that talks to a second server.

It would still be up to committers to describe their change on AF, and to include a link to the commit.

-----

1 point by akkartik 5400 days ago | link

Independent of the webapp I'd like to suggest the best practice of inserting a permalink to a relevant repo at least once during a thread.

Often when I dive into the AF archives I find people talking about variants of arc semantics that it's a lot of trouble to reconstruct. I'm guilty of this as well with wart and whatnot. At the time the context was clear to the participants, but they've since moved on. We could find the context in some nearby thread, but it's hard to locate nearby threads. The only thing that's easy is clicking parent again and again until we get to the top, then doing a search for a github link or something.

-----

1 point by shader 5404 days ago | link | parent | on: Table layouts considered helpful

I don't know that it does, but it's probably better supported by older browsers.

-----

2 points by shader 5404 days ago | link | parent | on: ASK AF: Any progress/usage with telnet?

I don't really know what other support for tcp connect already exists for arc, but I copied (and slightly modified) this from somewhere else on the forum, for use with anarki:

  (def connect (host port) 
    ($ (let-values (((in out) (tcp-connect ,host ,port))) 
      (list in out))))
I created this as well:

  (def disconnect (out)
    ($ (close-output-port ,out)))
I'm using them right now for developing an irc-bot, and they're working pretty well.

-----

1 point by thaddeus 5404 days ago | link

thanks, i'll give it a whirl.

-----

2 points by shader 5404 days ago | link | parent | on: Queue bug investigation

Just wondering, I haven't looked into ar that deeply yet:

How invasive is the change to mpairs really? Is it drastic enough you needed to create most of a new runtime, or is that just because you're like the rest of us and want to experiment ^^

-----

2 points by aw 5404 days ago | link

http://awwx.ws/mpair0 was my attempt to simply modify Arc 3.1's ac.scm to use mpair's. I got enough working to see that using mpair's appeared to fix the queue bug, but my implementation was otherwise quite buggy. (Lots of places where Racket lists appeared where Arc lists should have been, and vice versa; and weird mixes of the two, etc.) I have no doubt that someone cleverer than me could get it to work, but I just kept getting lost trying to figure out my bugs.

So I decided to implement the compiler incrementally, where I could test each step as I went. It would take a lot more work (a bit like getting my jeep stuck in the swamp, and so hauling myself out ten feet at a time with a winch), but it was an approach that I knew I would be successful at.

Then, since I was rolling through a compiler rewrite anyway, I decided to also go ahead and reflect the compiler into Arc to make it more hackable ^_^.

-----

1 point by evanrmurphy 5404 days ago | link

One of the 3 goals listed in the readme is "to make Arc more hackable," so I would guess that mpairs isn't most of it. Reorganizing ac.scm and adding unit tests so that the internals are easier to modify is probably a significant motivation.

Am I off-base here, aw?

-----

2 points by shader 5404 days ago | link | parent | on: Table layouts considered helpful

The people who recommend against tables for layout using separation of content and style as justification are obviously mistaking html for content.

In my opinion, if you can generate the html programmatically, then it is no longer content but rather a result of whatever presentation tools you are using. I understand that tables could result in poor rendering by certain browsers, and possibly confuse accessibility functions for people who can't read, etc. but those should really be alternate concerns. If you're truly worried about code reuse and cluttering your webpage with style embedded in the html "content", use a templating system!

Formatting with tables also has the advantage of displaying properly in older browsers, and achieving things that are extremely difficult to do with the current css model.

-----

2 points by shader 5407 days ago | link | parent | on: inline

I did an experiment where annotate produced a list, of the form ('tagged x type1 type2 ...), making the tagged lists transparent objects to the arc system. In doing so, I extended the type system to include the original type of the object in the list and support more than one type, allowing a simple form of inheritance through coercion. I.e. if you annotated a list as type 'foo, until you defined an actual coercion method for converting 'foo to a cons it would be treated as a normal list, and you wouldn't lose any ability to work with it using existing operations.

The thing that annoys me the most about arc's current type system is that if you tag something, it becomes an unusable vector according to all existing arc code, so you need to re-implement or extend any function that you actually want to support the new type.

-----

2 points by akkartik 5407 days ago | link

Hmm, it sounds like just making it a tagged list instead of a tagged vector wouldn't address your criticism. Any other suggestions?

-----

1 point by evanrmurphy 5406 days ago | link

I think he made the additional change of delegating all functional calls to the "parent" type when they didn't explicitly handle the type at hand. This makes a lot of sense. If you have a list that's tagged 'foo, it should behave exactly like a normal list does until you've specified some diverging behavior for type 'foo.

Do you still have the code for your experiment, shader?

-----

1 point by shader 5404 days ago | link

I do, but it was built on the old anarki master, so I'm going to have to spend a little bit of time updating it.

-----

1 point by akkartik 5406 days ago | link

Ah, I didn't properly read that first para somehow!

-----

2 points by shader 5409 days ago | link | parent | on: Why so proper, alist?

"there might be a way to merge conses and symbols into a single type"

Interesting idea. This might help a lot with implementing lisp in strongly typed languages. I suppose atoms could just be cons cells with nil in their cdr slot. The only problem is then how do you get the actual value out of an atom, and what is it?

-----

2 points by rocketnia 5409 days ago | link

This might help a lot with implementing lisp in strongly typed languages.

Don't most of them have option types or polymorphism of some kind? If you've got a really rigid one, at least you can represent every value the lisp as a structure with one element being the internal dynamic type (represented as an integer if necessary) and at least two child elements of the same structure type and one element of every built-in type you'll ever need to manipulate from the lisp (like numbers and sockets). Then you just do manual checks on the dynamic type to see what to do with the rest. :-p

The only problem is then how do you get the actual value out of an atom, and what is it?

I say the programmer never gets the actual value out of the atom. :-p It's just handled automatically by all the built-in functions. However, this does mean the cons cell representation is completely irrelevant to a high-level programmer.

-----

1 point by evanrmurphy 5409 days ago | link

> I suppose atoms could just be cons cells with nil in their cdr slot.

Could they be annotated conses with symbol in the car and value in the cdr (initialized to nil)? nil itself could then be a cons with the nil symbol in the car and nil in the cdr. This should achieve the cons-symbol duality for nil that's usually desired. (Follow-up question: annotate is an axiom, right?)

Warning: May include sloppy thinking.

-----

More