Arc Forumnew | comments | leaders | submitlogin
1 point by akkartik 4748 days ago | link | parent | on: Why should zero be truthy?

Thanks a bunch for the python links, especially the last one. They were most illuminating.

I think the error is that None, False and True were ever 'constants' rather than literals.

Update: ah, this is fixed in python 3.

2 points by rocketnia 4748 days ago | link | parent | on: Why should zero be truthy?

Oh, very nice! ^_^ I remember using this a few times, but yours looks much better:

  (aif (pos f seq)
    <operate on seq.it>)
2 points by akkartik 4748 days ago | link | parent | on: Why should zero be truthy?

Hmm, so how is one expected to check for list membership in arc? Ah, this would seem to be the canonical idiom:

  (aif (mem f seq)
    <operate on car.it>)
2 points by rocketnia 4748 days ago | link | parent | on: Why should zero be truthy?

"pos seems a reasonable case for keeping 0 truth-y"

While I personally like 0 being truthy, I don't see this as a convincing reason.

I'd treat 'pos exactly the same way as 'find. They're even conceptually similar, one finding the key and the other finding the value. For 'find, the value we find might be falsy, so truthiness isn't enough to distinguish success from failure. The same might as well be true for 'pos.

---

"But you're right, I might yet make empty strings and tables false-y."

What if the table is mutable? That's an interesting can of worms. :)

JavaScript has 7 falsy values, all of which are immutable. If we know something's always falsy, we also know it encodes a maximum of ~2.8 bits of information--and usually much less than that. It takes unusual effort to design a program that uses all 7 of those values as distinct cases of a single variable.

This means if we have a variant of Arc's (and ...) or (all ...) that short-circuits when it finds a truthy value, we don't usually have to worry about skipping over valuable information in the falsy values.

If every mutable table is falsy as long as it's empty, then a falsy value can encode some valuable information that a practical program would care about, namely the reference to a particular mutable table.

---

"(True, False = 0, 1 :( That's the ugliest thing I've ever seen python allow. At least throw a warning, python! Better no booleans than this monstrosity.)"

There's some rationale here:

http://www.python.org/dev/peps/pep-0285/

http://docs.python.org/2/whatsnew/2.3.html

http://www.python.org/download/releases/2.2.1/NEWS

The PEP describes the design and rationale of introducing booleans to Python this way. Version 2.3 implements this. Version 2.2.1 preemptively implements bool(), True, and False to simplify backporting from 2.3.

Notably, the variable names "True" and "False" were chosen to be similar to the variable name "None", and all three of these are just variables, not reserved words.

Later, version 2.4 made it an error to assign to None:

http://docs.python.org/2/whatsnew/2.4.html

From what fallintothis says, apparently the same change hasn't been made for True and False.

1 point by akkartik 4748 days ago | link | parent | on: Why should zero be truthy?

Yeah, I'm just minimizing how much I need to change, picking my poison between hard-coded symbols and extra cell types.
2 points by rocketnia 4748 days ago | link | parent | on: Why should zero be truthy?

"I was trying to avoid an extra hard-coded symbol"

Speaking of which, why are you making false and nil count as symbols at all?

I suppose it gives them an external representation without coining a new syntax like #f.

2 points by akkartik 4749 days ago | link | parent | on: Why should zero be truthy?

Thanks for trying it out, and for the comments! Yeah it's gotten slow :(

I hadn't realized how close to python I've gotten. Seems right given how the whitespace and keyword args are inspired by it. On rosetta code I found a cheap way to get syntax highlighting was to tag my wart snippets with lang python :)

I've been using 1 as the default truth value, and it's not assignable either. I was trying to avoid an extra hard-coded symbol, but now that I've added false perhaps I should also add true.. I'm not averse to going whole-hog on a boolean type, I'd just like to see a concrete use case that would benefit from them. pos seems a reasonable case for keeping 0 truth-y, and the fact that lists include the empty list seems a reasonable case so far to keep nil false-y. But you're right, I might yet make empty strings and tables false-y.

(True, False = 0, 1 :( That's the ugliest thing I've ever seen python allow. At least throw a warning, python! Better no booleans than this monstrosity.)

2 points by fallintothis 4749 days ago | link | parent | on: Why should zero be truthy?

Technically, my first thought was that something was broken. Hitting C-d as soon as I got the prompt:

  $ time ./wart
  ready! type in an expression, then hit enter twice. ctrl-d exits.
  => nil

  real    0m29.200s
  user    0m27.602s
  sys     0m0.000s
Anyway, I was going to test to see if you had Arc's t; but it doesn't look like it:

  (if t 'hi 'bye)
  020lookup.cc:28 no binding for t
  => bye
Note that it's trivial to add:

  (<- t 't)
  => t
  (if t 'hi 'bye)
  => hi
The reason I thought to try this was because I initially balked at maintaining false and nil at the same time with the same truth values. Then I thought of t, and suddenly the pieces clicked together: at least in part, it seems like you just want a Python-like system anyway.

Once I got the landscape laid out in my head, I started objecting to it less, because I could make sense of it. You're most of the way there:

- false is a separate, canonical false value.

- t (if you chose to have it) is a separate, canonical truth value.

- nil is an empty list, but empty lists are false.

Compare to Python's True, False, and []. The major differences being:

1. No first class boolean type. In wart, this produces more of a disconnect between t and false. t (i.e., 't) is just a normal symbol whose truth value is incidental. But false is a special, unassignable keyword.

  (<- false 'hi)
  => hi
  false
  => false
Python lacks symbols (you can't just say True = 'True), so this disconnect between symbolic value and keyword doesn't exist. There is still, however, a different sort of disconnect in Python because the "first class" boolean type gets contaminated by the int type:

  >>> False < 10
  True
  >>> 867 + True
  868
  >>> isinstance(True, int)
  True
  >>> isinstance(0, bool)
  False
  >>> True, False = 0, 1
  >>> if True: print "hi"
  ...
  >>> if False: print "hi"
  ...
  hi
2. You don't take Python's next logical leap. Since you already make the empty list false, other values become fair game, such as the thread's original idea (make 0 false), the empty string, other empty data structures, etc. But like I said before, I make do in such systems. Keeping nil falsy is really just your prerogative, if you want to avoid calls to empty? that much. ;)
2 points by akkartik 4750 days ago | link | parent | on: Why should zero be truthy?

Ok, I'm now experimenting with a new keyword in wart called false.

a) There's still no boolean type. The type of false is symbol. (The type of nil has always been nil; maybe I'll now make it list.)

b) if treats both nil and false as false-y values.

c) nil and false are not equal.

d) Comparison operators now short-circuit on false BUT NOT nil.

I can mostly use either in place of the other. But I'm trying to be disciplined about returning false from predicates and nil from functions returning lists.

Wart now has four hard-coded symbols: nil, object, caller_scope and false.[1]

Thoughts? It was surprisingly painless to make all my tests pass. Can anybody think of bugs with this kinda-unconventional framework? If you want to try it out:

  $ git clone http://github.com/akkartik/wart
  # Optionally "git checkout 0ff47b6bce" if I later revert this experiment.
  $ cd wart
  $ ./wart
  ready! type in an expression, then hit enter twice. ctrl-d exits.
[1] fn is just a sym with a value:

  let foo fn (foo () 34)
  => (object function {sig, body})
2 points by akkartik 4750 days ago | link | parent | on: Why should zero be truthy?

I just ran into a case where I wished the empty list wasn't the same as the false value. When implementing infix in wart (http://arclanguage.org/item?id=16775) I said: "Range comparisons are convenient as long as they return the last arg on success (because of left-associativity) and pass nils through."

  (a < b < c)
  => (< (< a b) c)   ; watch out if b is nil

  (< nil x)          ; should always return nil
But there's one situation where you want (< nil x) to not be nil: when x is a list and you want to perform lexicographic ordering (https://en.wikipedia.org/wiki/Lexicographical_order; http://rosettacode.org/wiki/Order_two_numerical_lists).

This problem wouldn't occur if there was a first-class boolean type; then I could use False or something for chaining comparisons.


Er, I knew it was weird for me to say "'err is defined to be Racket's 'error," but I just realized, that factoid was in the original post of this thread. :-p

The culprit is that 'err is defined to be Racket's 'error. It looks like every single use case of 'error is discouraged for one reason or another in the Racket reference:

http://docs.racket-lang.org/reference/exns.html#%28def._%28%...

- (error sym) creates a message string by concatenating "error: " with the string form of sym. Use this form sparingly.

- (error msg v ...) creates a message string by concatenating msg with string versions of the vs (as produced by the current error value conversion handler; see error-value->string-handler). A space is inserted before each v. Use this form sparingly, because it does not conform well to Racket’s error message conventions; consider raise-arguments-error, instead.

- (error src frmat v ...) creates a message string equivalent to the string created by

  (format (string-append "~s: " frmat) src v ...)
When possible, use functions such as raise-argument-error, instead, which construct messages that follow Racket’s error message conventions.

The reason to use 'write rather than 'display is precisely so that things can be pasted into the repl. All you should have to do is 'read and 'eval the entire error string.

But there's definitely something busted about this. In anarki:

  arc> (= x "abc")
  arc> (err x)
  Error: "abc"           ; ok, looks good
  arc> (= x 'abc)
  arc> (err x)
  Error: "error: abc"    ; whaa..
There's also a long-standing issue that's bothered me before:

  arc> (= x "abc")
  arc> (err x " shouldn't be a string")
  Error: "abc \" shouldn't be a string\""
You're right, that's pretty ghastly. It should print:

  Error: "abc" shouldn't be a string
or:

  Error: "\"abc\" shouldn't be a string"
I'm still mulling what the culprit is here, but I don't think it's write vs display.

> To play devil's advocate (and/or nihilist), why do you need that? Error messages in Arc are already universally this way, so it's expected behavior that the messages look crappy. :P

Because I'm not satisfied with crappy behavior. It makes readability worse, and debugging harder.

I'll take a look to see if I can catch it better; reading over my code, some of it does seem to be convoluted, if working. I've never quite been happy with how the failure messages are calculated; if that changes, I can easily change how we handle printing the errors.

Other than the double-quote issue, I don't recall other big reasons to use to-readable-string. I'd certainly be a fan of removing code, if I can make it work. It would bring my macro:function ratio below 1:1 again, which would be awesome. :)


To play devil's advocate (and/or nihilist), why do you need that? Error messages in Arc are already universally this way, so it's expected behavior that the messages look crappy. :P

On a more constructive note, is there some part of your code (or could there be, with some rewriting) where you could specifically catch the exception whose message you want to display nicely? Seems like you could probably work out some way to wrap something like:

    arc> (on-err (fn (c)
                    (pr "Error: ")
                    (prn (details c)))
                 (fn () (err "a \"b\" c")))
    Error: a "b" c
Even with a macro:

    (mac w/nice-errors exprs
      `(on-err [do (pr "Error: ") (prn (details _))]
               (fn () ,@exprs)))
It's a bit of hack, but if you need to subvert the language's defaults in an implementation-conforming way, them's the breaks.

Side note: paging through your source code, I notice you have a few functions to support to-readable-string. I believe it could be greatly simplified, because write's job is already to print out values in ways that read can parse back in. Thus, save for the single-quote stuff, I think you could boil it all down to tostring:write. Play around with it and see if it's what you want:

    arc> (tostring:write (list 'a "b" (obj c 'd)))
    "(a \"b\" #hash((c . d)))"

Ugh, that's awful. I want to print out error messages that, in part, can be copied and pasted into the REPL. Right now, since double quotes are escaped, I'm printing single quotes, which obviously don't work. (https://bitbucket.org/zck/unit-test.arc/issue/40/make-error-...)

I guess this is a reason to switch to Anarki. But even that doesn't help too much given that I want unit-test.arc to work with arc3.1.


This is actually due to the code that handles errors in the REPL. Notice that it uses write to display the exception message instead of display:

    (define (tl2)
      (display "arc> ")
      (on-err (lambda (c) 
                (set! last-condition* c)
                (display "Error: ")
                (write (exn-message c))
                (newline)
                (tl2))
        ...))
Never known why, myself...
3 points by akkartik 4754 days ago | link | parent | on: mzscheme dies

Yeah we've discussed this before: http://arclanguage.org/item?id=16178
4 points by rocketnia 4755 days ago | link | parent | on: mzscheme dies

In order to really define "backward compatible," you'd have to define Arc in a way that's implementation-independent. In Arc, the code is the spec, so as soon as the code changes, compatibility becomes subjective.

For instance, suppose Anarki defines a new utility and uses it to simplify the implementation of 10 other utilities. (It does this in a few places.) Now suppose my Arc 3.1 code has defined a utility with exactly the same name, and running this code on Anarki causes those other 10 utilities to misbehave, thus wrecking my program. This is a case where Anarki isn't compatible with Arc 3.1, but since it's so easy for me to choose a different name for my utility, it's hardly even worth mentioning. Pretty much any substantial update to Arc would break it in exactly the same way.

There's only one difference between Arc 3.1 and Anarki that's ever gotten in my way, and that's the way Anarki has revamped the [...] syntax to support multi-argument functions. When I say [do t] or [do `(eval ',_)], Anarki treats these as 0-arity functions, and when I say [let (a . b) _ ...], Anarki chokes when trying to search the dotted list for any underscored variables. Once again, this is the kind of change that's pretty easy to work around, and I can't really say Anarki is worse for having this extra functionality.

I'd say Arc platforms are not really portable with each other, in the sense that not all code that works on one platform will work on another. However, I've found it pretty easy to develop my code so it'll work on multiple Arc platforms at the same time.

1 point by lark 4755 days ago | link | parent | on: mzscheme dies

Does this mean Anarki is not backward compatible with Arc?
1 point by akkartik 4757 days ago | link | parent | on: A follow-up on the pipe operator

"I look forward to you figuring out what kind of indentation you prefer here. :)"

Here's my current thinking: https://github.com/akkartik/wart/blob/aec0af2676/082macex.wa...

"these syntaxes actually have a special tendency to let sugar accumulate, driving them away from the ideal "tiny expressions" case."

Definitely. The wart example you gave is indeed legal, but the actual _ expression is still simple, even though the chain is long.

It seems nested expressions often work fine because each macro only wraps its arg and leaves the rest to the inside.

  (zap (map car._ _) (list list.1 list.2)
  => (zap (fn(_) (map car._ _)) ..)
  => (zap (fn(_) (map (fn(_) car._) _)) ..)
But that's less readable than when you have the [] delimiting scopes.

Some other concrete examples I came up with of the problems you pointed out:

  (zap (cons? & (fn(_) ..)) ..)  # broken by nested scope
  (zap (cons? & (fn(x) (+ '_ car.x)) ..)  # broken by quoting
2 points by rocketnia 4757 days ago | link | parent | on: A follow-up on the pipe operator

"I can only defend myself against the wart section :) In wart the pipe operator can only take two args and is intended to be used in infix. I use a non-infix transform for more args, and prefix mode in general"

Oh, so you're pursuing both options at once. I look forward to you figuring out what kind of indentation you prefer here. :) My "considerations about wart" section was only wishy-washy anyway.

---

"For the rest, my weak defense is that functionize and the _ syntax is only intended for tiny expressions."

In Penknife, when I used the a'b operator as sugar for (b a), I found I ended up with a few really long lines of a.b.c'd'e.f, so it kinda suffered from its own success. ^_^ My a'b is the same as your (a -> b._), and it exactly corresponds to your no-underscore special case, (a -> b), so I expect you to have the same issue.

I suspect these syntaxes actually have a special tendency to let sugar accumulate, driving them away from the ideal "tiny expressions" case. Specifically, they make it possible to inject new code without breaking apart the surrounding sugar first:

  a.b.f.c.d                  # before refactoring
  a.b."foo".c.d              # illegal
  a.b -> (_ "foo") -> _.c.d  # legal? (not quite the example you gave)
  a.b'[itfn:it s.foo].c.d    # Penknife code of similar generality
Fortunately for wart, its infix operators allow whitespace in between, which possibly means you can write these long expressions on multiple lines. (That wasn't the case in Penknife.)
1 point by akkartik 4757 days ago | link | parent | on: A follow-up on the pipe operator

It's out, and I'm not putting it back or anything, but FWIW:

  (mac functionize (expr)
    (if (and (treemem '_ ssexpand.expr)
             (~caris expr 'make-br-fn)
             (~caris expr 'fn))
      `(fn(_) ,expr)
      expr))

  arc> (= x '((1) (2) (3)))
  arc> (zap [map [car _] _] x)
  arc> x
  (1 2 3)
This idea was in the wart version from the start (https://github.com/akkartik/wart/commit/7e5d620b28#diff-1) but I didn't mention it for simplicity, and then totally forgot to carry it over into anarki.

It's still pretty hacky though, and has more in common with C's macros than with lisp's.

1 point by akkartik 4757 days ago | link | parent | on: A follow-up on the pipe operator

Ok, done reading now, and you're right, I'll revert it.

I can only defend myself against the wart section :) In wart the pipe operator can only take two args and is intended to be used in infix. I use a non-infix transform for more args, and for prefix mode in general: https://github.com/akkartik/wart/commit/ec0f9a38b8

My weak defense for the rest: functionize and the _ syntax was only intended for tiny expressions.

1 point by akkartik 4757 days ago | link | parent | on: A follow-up on the pipe operator

Just spotted your comment while I'm working on something else. Haven't digested it all, but judging from the first five words -- feel free to revert! It was definitely intended as an experiment, and I'm not attached to it. I may well do so myself later today if you don't get to it first.
4 points by rocketnia 4758 days ago | link | parent | on: A follow-up on the pipe operator

This is a horrible change. I didn't respond to the original post because of "Thumper's rule," and I couldn't rebut thaddeus in time to stop you. :(

-

== Problems with functionize in general ==

The functionize-based utilities are discontinuous about the way they detect underscores: The body can use _ three times, or two times, or one time, but as soon as it uses _ zero times, it means something completely different. Thanks to this, I can break several layers of code by making a single local edit. But will I? Yes:

The 'treemem function detects occurrences of _ without regard for quoting or local scopes. So if I use an _ to activate one functionize-based utility, then I'll accidentally activate all the other functionize-based utilities which surround that one. If I want to avoid refactoring several layers of code each time I edit, I pretty much have two options:

- I can avoid putting an _ anywhere in my code, in which case this functionize feature won't be very useful to me.

- I can make sure to activate each and every functionize utility as soon as I use it, in which case they would have been better off as 'let variants. For instance, I might settle on the idiom (zap (do '_ ...) foo), but it would be more convenient to say (zaplet orig foo ...).

-

== Problems in Arc ==

Your Anarki commit is one of those things that is "guaranteed to break all your code." Personally, I like using the pattern (zap [map [...] _] args), which now breaks since the _ activates zap's automatic function wrapper. It seems you would want me to write (zap (map [...] _) args) instead, but for compatibility with Rainbow, Jarc, etc., I think I'll define a macro (itfn ...) and write (zap (itfn:map (itfn:...) it) args). Effectively, I'll be recreating the [...] functionality in the way I like.

Meanwhile, Arc already covers a lot of the functionality of Clojure's -> and ->> operators using (aand ...). If you still miss -> or ->>, I recommend just implementing an 'aand variant that doesn't short-circuit on nil. Call it 'ado or something.

Here's how to use 'aand/'ado with the examples from that thread you linked to (http://arclanguage.org/item?id=18044):

  (-> (range) [map [* _ _] _] [filter even? _] [take 10 _] [reduce + _])
  (-> 5 [+ _ 3] [/ 2 _] [- 1 _])
  
  (ado (range) (map [* _ _] it) (filter even? it) (take 10 it) (reduce + it))
  (aand 5 (+ it 3) (/ 2 it) (- 1 it))
-

== Considerations in Wart ==

Clojure's -> and ->> operators are not infix, and that might be good for their indentation. I'm not really sure.

  (-> (...)
    (...)
    (...
      ...)
    (...))
  
  (...) ->
    (...) ->
    (...
      ...) ->
    (...)
  
  (...)
    -> (...)
    -> (...
         ...)
    -> (...)
On the other hand, I like that you can potentially use infix to combine things like -> and ->> into one chain:

  (...)
    -> (...)
    ->> (...
          ...)
    -> (...)
2 points by akkartik 4758 days ago | link | parent | on: A follow-up on the pipe operator

Done! https://github.com/arclanguage/anarki/commit/afdfdf929a

I've also extracted the _ handling into a new function called (ugh) functionize. And I've fixed zap to show how you can make other functions "_ aware".

But like I said, this isn't as valuable in arc since you already have the [.. _ ..] syntax. All it permits is more widespread ssyntax use. You can say _.x instead of [_ x], and you can chain ssyntax around obstacles.

I found the idea interesting because it asks the question: do we need [.. _ ..] syntax, or can we eliminate the need for this reader macro just with a few simple changes to the functions where we most often use it?

2 points by thaddeus 4758 days ago | link | parent | on: A follow-up on the pipe operator

I like it. Can this be added into anarki?
2 points by thaddeus 4760 days ago | link | parent | on: Why should zero be truthy?

> But I'm also happy if we don't have booleans at all, because then "Is this nil?" can just be a special case of "Is this a list?"

However, you do lose the ability to cleanly interop with other languages or transforms.

2 points by rocketnia 4760 days ago | link | parent | on: Why should zero be truthy?

Well, this ended up leading in different directions than I expected, so I'll be more specific about my opinions here.

I like the idea of the main (if ...) semanics being just another equality check or dynamic type check: "Is this nil?" If falsiness overlaps with multiple other dynamic types, then we end up having confusing crosshatching where one extension wants to do X with any falsy value and another extension wants to do Y with any list.

Secondarily, I also see some benefit in distinguishing between () and #f, because then it's possible to dispatch on whether something is a list or a boolean. But I'm also happy if we don't have booleans at all, because then "Is this nil?" can just be a special case of "Is this a list?"

More