Arc Forumnew | comments | leaders | submitlogin
1 point by rocketnia 4791 days ago | link | parent

Yeah, t should be rebindable, right? :)


2 points by aw 4791 days ago | link

http://awwx.ws/localt0

-----

1 point by rocketnia 4791 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 4791 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 4791 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 4791 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 4791 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 4791 days ago | link

Exactly! nil too. :P

-----

2 points by shader 4791 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 4790 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 4790 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 4791 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 4791 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 4791 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 4790 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 4790 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 4791 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.

-----