Arc Forumnew | comments | leaders | submitlogin
4 points by akkartik 2163 days ago | link | parent

Immediate reaction: that's going a bit too far. Racket has `#<void>`, and its absence is one reason I prefer Arc to Racket.

Will the empty list be false-y? I kinda like that it is.

Both these ideas help make programs terser in Arc. Here's a definition that uses both:

    (def map1 (f xs)
    "Returns a list containing the result of function 'f' applied to every element of 'xs'."
      (if xs
        (cons (f car.xs)
              (map1 f cdr.xs))))
I like keyword arguments too, but I don't follow what your preceding proposals have to do with it.

You know what language has perfect interop with Racket? Racket ^_^



2 points by shawn 2163 days ago | link

cdr basically ends up being:

  (define (xcdr x)
    (if (pair? x) (cdr x) ar-nil))
and ar-nil is falsy. So your example will work unmodified.

... oh. And now that I check, you're right about void:

  arc> (seval '(if (void) 1 2))
  1
I foolishly assumed that (void) in racket is falsy. But it's truthy. That rules out using racket's (void). `(null ? 1 : 2)` gives 2 in JS, and `if nil then 1 else 2 end` gives 2 in Lua, so it's surprising that `(if (void) 1 2)` gives 1 in Racket.

For what it's worth, in an experimental version, using #f for ar-nil and #t for ar-t worked. It's a bit of a strange idea, but it helps interop significantly due to being able to pass arc predicates right into racket.

It'd be better for me to show a working prototype with ar-nil set to #f rather than try to argue in favor of it here. But to your original question: yes, anything other than |nil| would be great, since that gets rid of the majority of interop headaches.

One thing that might be worth pointing out: The lack of void means it's back to the old question of "how do you express {"a": false, "b": []} in arc?" Choosing between #f and () for nil implies a choice between forcing {"a": false} or {"b": []} to be the only possible hash table structures, since one of them would be excluded from hash tables. But that might be a tangent.

Yes, the keyword section was poorly explained. My comment should have been prefixed with "some thoughts on arc, in case it's helpful" rather than "here is a proposal." And then I should have taken that comment and put it in a different thread, since keyword arguments are unrelated to the question of nil becoming (). I was mostly just excited for the possibility of leveraging more racket now that denil/niltree might be cut soon.

-----

4 points by krapp 2163 days ago | link

But why should an empty list be falsy? An empty list can be as valid a form of list as a non-empty one. It also seems to me that an empty list shouldn't be nil, since to me, nil should mean "undefined", and an empty list is well defined as an empty list.

Would disambiguation here really make Arc programs less terse? Is that a decision that should be enforced by the language or left to the author?

-----

3 points by akkartik 2163 days ago | link

In my example above, making an empty list truthy would cause this change:

    (def map1 (f xs)
    "Returns a list containing the result of function 'f' applied to every element of 'xs'."
   -  (if xs
   +  (if (~empty? xs)
        (cons (f car.xs)
              (map1 f cdr.xs))))
We can argue how important this is, but disambiguation does definitely make Arc programs less terse.

-----

3 points by shawn 2163 days ago | link

Two points.

- the assumption baked into this argument is that cdr of an empty list returns an empty list. Switching nil to #f and letting empty list be truthy avoids this problem.

- Good names are important. ~empty? isn't really a fair characterization. Lumen uses (some? xs). There is also another way: Update `no` to be (or (is x nil) (empty x)), and then use (~no xs).

-----

2 points by akkartik 2163 days ago | link

First option makes sense.

Second option, part a: I actually find `~empty?` clearer than `some` in this case. Also `some` means something different in Arc.

Second option, part b: wait, then `(if xs ...)` would sometimes not do the opposite of `(if (no xs) ...)`!

-----

2 points by rocketnia 2162 days ago | link

For what it's worth, my approach here is pattern-matching. In Lathe Comforts for Racket I implement a macro `expect` which expands to Racket's `match` like so:

  (expect subject pattern else
    then)
  ->
  (match subject
    [pattern then]
    [_ else])
If Arc came with a similar pattern-matching DSL and `expect`, we could write this:

  (def map1 (f xs)
    (expect xs (cons x xs) ()
      (cons (f x) (map1 f xs))))
The line "expect xs (cons x xs) ()" conveys "If xs isn't a cons cell, finish with an empty list. Otherwise, proceed with x and xs bound to its car and cdr."

-----

1 point by i4cu 2163 days ago | link

I agree; an empty list is a value. And when you consider interop with other langs, they will infer it to be some object too, where predicates will see it as a value not the lack of one.

-----

2 points by hjek 2163 days ago | link

I actually like `#<void>`, because it makes more of a distinction between pure and impure functions.

I read a good blog post[0] recently on how not distinguishing makes it difficult to guess the behaviour of simple and short code snippets (in JavaScript, but the same could apply to Arc).

[0]: https://medium.com/@winwardo/the-principle-of-least-astonish...

-----

1 point by akkartik 2162 days ago | link

There's definitely a tension between being a concise language and being a safe language. Arc doesn't try to help newcomers avoid simple mistakes. It gives them enough rope to hang themselves, like with unhygienic macros. That's partly why I stopped using Arc to teach my students programming (http://akkartik.name/post/mu).

-----