Arc Forumnew | comments | leaders | submitlogin
~ applied to variables
2 points by garply 3218 days ago | 3 comments
Today I was coding and unthinkingly wrote "~foo" as a test of a variable foo being nil. I stared at the code for a long time before I realized that I wanted "no.foo".

What do you guys think about extending "~" to mean applying "no" to non-functions? Other languages have things like "!" which are much more concise than "no.", and the overload feels natural to me.

"~" is already an abbreviation for "no:", run-time performance issues aside, are there stylistic reasons why it shouldn't also be an abbreviation for "no."?



5 points by rntz 3217 days ago | link

That's a bad idea. Not because it is intrinsically wrong, but because it leads to a bad habit, namely, using an operator whose meaning depends upon the type of the thing you are applying it to.[1] In this case, you're making the meaning of ~ change on non-function objects. Suppose you're writing a function 'foo, one of whose arguments is 'x. Then:

a) foo might call ~x on an argument. Then you want ~x to mean what it does currently. But x need not be a function -- it could be a hashtable, since hashtables can act as lookup-functions, or a list. So suddenly ~x is simply incorrect.

b) foo might not care whether x is a function or not; but then you can't use ~x on it, because then if you pass in a function for x to foo, it will behave incorrectly. But if you accustom yourself to using ~x to mean (no x) when x isn't a function, you might accidentally write ~x when you really do mean no.x.

Admittedly, both of these can be avoided through careful programming, but the point of ssyntax is as a convenient abbreviation for common operations, to avoid even the minimal cost of parsing parentheses and a function name when using them. This totally defeats the purpose.

[1] Note that this is different from, but can overlap with, polymorphism, where the implementation of the operator you are applying depends upon the type of the object. The meaning of an operation and its implementation can be separated; an example polymorphic operation which has the same meaning is addition: addition of integers, of floating-point numbers, and of vectors of integers are all implemented differently, but mean the same thing. An example of meaning-non-preserving polymorphism is the use of the left-shift operation on a stream in C++ to print an object to it.

-----

1 point by garply 3217 days ago | link

Thanks for taking the time to dissect the downsides of what I'm increasingly convinced is a bad idea. I agree with your reasoning, particularly with regard to the scenarios involving hashtables and lists.

Nonetheless, variable negation is something I imagine we all encounter with relative frequency, what do you guys think of a negation symbol equivalent to "no." (i.e., ¬)?

Since "!" is used elsewhere, "^" strikes me as a natural choice, probably because I associate it with set complements for POSIX regular expressions.

-----

3 points by fallintothis 3218 days ago | link

It'd be easy, since ac.scm just changes ~foo into (complement foo) (except (~foo x) is changed to (no (foo x)), but that doesn't affect us here). Just change complement.

  (mac complement (f)
    (w/uniq (gf args)
      `(let ,gf ,f
         (if (isa ,gf 'fn)
             (fn ,args (no (apply ,gf ,args)))
             (no ,gf)))))

  arc> (= foo "bar")
  "bar"
  arc> ~foo
  nil
  arc> (keep ~even '(1 2 3))
  (1 3)
  arc> ~nil
  t
  arc> ~
  #<procedure: no>
But I don't think I've ever made that slip. Function complementation isn't the same as Boolean-not, so overloading the visually distinctive ~foo based on context rubs me the wrong way. I'd probably get used to it, though.

Edit:

Another thought occurs. It could produce some weird bugs. E.g., you might mistakenly pass a variable to a keep that complements, which will work, but keeps t or nil instead of using a function.

  arc> (= foo "bar")
  "bar"
  arc> (keep ~ssyntax '(a.b a:b nil a b c))
  (nil a b c)
  arc> (keep ~foo '(a.b a:b nil a b c))
  (nil)
  arc> (keep ~nil '(a.b a:b nil a b c))
  nil
It doesn't seem so bad, but when you have it buried in some definition like

  (def foo (bar baz)
    (...  (keep ~bar baz) ...))
it might be hard to find.

-----