Arc Forumnew | comments | leaders | submit | rkts's commentslogin
1 point by rkts 6266 days ago | link | parent | on: Help needed with macro

You can write a tail-recursive version of tokipin's code pretty easily. Personally though I think this could be an addition to the "examples of LOOP" thread from a while back.

  (defun wrandf (xs weights)
    (loop with r = (random (apply #'+ weights))
          for x in xs
          for w in weights
          for cw = w then (+ cw w)
          when (> cw r) return x))

  ; assumes pair
  (defmacro wrand (&rest args)
    `(funcall
       (wrandf
         (list ,@(mapcar (lambda (x) `(lambda () ,(cadr x))) (pair args)))
         (list ,@(mapcar #'car (pair args))))))

-----

2 points by rkts 6307 days ago | link | parent | on: Tagged unions on Anarki

I definitely think this is a good idea. We've discussed this before, of course:

http://arclanguage.org/item?id=3471

The major difference between my version and yours is that you require each constructor to belong to one "union" type, whereas in my version each constructor forms its own type. The latter approach seems more Arc-like to me. It's simpler, and in a dynamic language I don't think you should be forced to stuff constructors into fixed categories. (I'm not saying that such categories are useless, just that variant tags shouldn't be inextricably tied to them.)

You may want to look at OCaml's polymorphic variants, which are closer to what I had in mind for Arc:

http://caml.inria.fr/pub/docs/manual-ocaml/manual006.html#ht...

The auto-binding of names to fields seems cool, but I'd need to practice it to see if it was a good strategy overall. You've already managed to shadow car and cdr, which could cause a lot of code to fail mysteriously (think macroexpansions, e.g. each). Also, if the user binds the names explicitly, you get destructuring for free.

Sorry if this is incoherent; I've been up all night.

-----

1 point by absz 6307 days ago | link

I remember that discussion; in fact, I had a response, http://arclanguage.org/item?id=3518, in which I proposed this (and I might have been working on this at the time).

The clearest advantage of distinct types is in things like binary trees:

  (tunion binary-tree
    branch (left  binary-tree?)
           (right binary-tree?)
    leaf   (value object))
If you just had

  (vtype branch left right)
  (vtype leaf   value)
you can't quickly tell what's a binary tree and what isn't in the same way, and you lose the invariant that a branch's left and right are both binary trees. At the same time, something more like vtype would remove

  (tunion my-type
    my-type (foo pred)
            (bar int?))
. Actually, if you had something like

  (mac vtype (type . arguments)
    `(tunion ,type ,type ,@arguments))
you could do this:

  (vtype variant1 (x pred))
  (vtype variant2 (a int?) (b object))
  
  (tcase var
    (variant1
      variant1 (do-something var))
    (variant2
      variant2 (do-something-else var))
    else
      (fail))
And you should be able to write a macro that will convert something nicer to that form, e.g.

  (vcase var
    variant1 (do-something var)
    variant2 (do-something-else var)
             (fail))
. I'll work on that and push it to Anarki.

I'm not convinced on the auto-binding; I implemented it that way because the resulting syntax was nice. It might make sense to have an option to work without auto-binding of names, but maybe it won't actually be an issue in practice. car and cdr are a bit atypical because (I hope) you won't be redefining a list type :)

-----

1 point by absz 6306 days ago | link

See http://arclanguage.org/item?id=7395: is this like what you were talking about?

-----

1 point by rkts 6305 days ago | link

Yep. I wouldn't call it elegant, but it works.

I feel like there ought to be a better way to do this--something that's less complex but handles more cases. In particular I'd like to be able to alternate OO and pattern-matching styles on the same datatypes. However, I haven't been able to come up with a solution that really satisfies me.

One other thing: wouldn't it be better to raise an error when an invariant is violated, instead of returning nil?

-----

1 point by absz 6305 days ago | link

You can use OO structure-access semantics with tagged unions: (tu 'slot-name) and (= (tu 'slot-name) new-value). That's not the interesting part of OO, of course :)

I thought about returning an error, but Arc seems to be a little calmer about such things: (is (car nil) (cdr nil) nil), for instance. Of course, (car 'sym) errors. ::shrug:: I'm not sure about this, and it might change.

-----

3 points by rkts 6305 days ago | link

The purpose of a type system is to help find errors by failing early instead of letting bugs propagate through a system and show up in mysterious ways. Right now, you're actually making debugging harder because you're just wrapping bugs inside other bugs. Instead of figuring out why his binary tree turned into a cheeseburger, a programmer now has to determine (a) why his binary tree is nil (or why mutating it seems to have no effect), (b) which invariant he violated, and (c) what error caused the invariant to be violated.

Regarding (car nil) and (cdr nil), these return nil because it's sometimes convenient. For instance, here's a function that collects every other item in a list:

  (def foo (xs)
    (if xs
      (cons car.xs (foo cddr.xs))))
If (cdr nil) raised an error, we would have to check the cdr before we could recur:

  (def foo (xs)
    (if xs
      (cons car.xs (if cdr.xs (foo cddr.xs)))))
Not a huge difference, obviously, but a difference nevertheless. I can't see any comparable reason to return nil in your case.

-----

1 point by absz 6304 days ago | link

Fair enough, you've convinced me.

EDIT: Creation, reading (defcall), and writing (sref) all give errors if invariants are violated/nonexistent keys are accessed. Is there anything I missed?

-----

1 point by rkts 6320 days ago | link | parent | on: Rethinking macros

Well, we can cut down on the parentheses in the parameter list. This looks ok to me:

  (defho while ([test] . [f])
    ...)

  (defho awhen (x . [f it])
    ...)
As to w/hof, it may be easier to implement, but it seems kind of hard to use.

-----

1 point by almkglor 6320 days ago | link

> As to w/hof, it may be easier to implement, but it seems kind of hard to use.

Then perhaps you can define 'defho in terms of 'mac and 'w/hof, maybe? The Arc solution: throw more macros at it.

Regarding (x . [f it]) :

In canonical arc2, [f it] is (fn (_) (f it)), so (x . [f it]) becomes (x fn (_) (f it)) . In Anarki, [f it] is (make-br-fn (f it)), so (x . [f it]) becomes (x make-br-fn (f it))

This makes the use of [] problematical.

'w/hof is trivially implementable, 'defho much less so due to the syntax.

w/hof is also more general, at the expense of being more talkative.

In fact it might be better to do things this way:

  (mac while (cond . body)
    (w/hof (cond (fn () ,cond)
            body (fn () ,@body))
      ((afn ()
         (when (cond)
           (body)
           (self))))))
This allows a few shortcuts in anaphora:

  (mac aif (cond then (o else))
    (w/hof (cond ,cond
            then (fn (it) ,then)
            else (fn () ,else))
      (if cond
          (then cond)
          (else))))

-----

1 point by rkts 6319 days ago | link

> 'w/hof is trivially implementable, 'defho much less so due to the syntax.

Oh. I haven't bothered to learn how to hack the syntax in Arc, so I'll take your word on that.

-----

1 point by rkts 6320 days ago | link | parent | on: Rethinking macros

The point is that it makes a large class of macros easier to define. Really, compare my examples with the definitions in arc.arc; the former are much shorter and easier to understand than the latter.

My examples are from the core language, obviously, but the suggestion would benefit anyone wanting to extend the language with their own macros.

Placing a parameter inside brackets just says that the argument should be expanded into an fn. The thing after the parameter name is the parameter list that the fn should have. As to the asterisk, I don't know how to explain that better than I already have.

-----

1 point by almkglor 6320 days ago | link

Well, it would be a good addition to Anarki if you can actually implement it, and can prove your point that it's easier to use than using macros - obviously to do that, you can't just rewrite existing code, you have to write new code that uses your version of macros and see if it does indeed work better.

Further, using arc.arc as a basis is pointless; the reason those macros are there is because it's a waste of time to rewrite them. Most of the macros I've written in Arc depend on new syntax, not just rearranging expressions: look at p-m: and w/html , which I doubt are possible in ordinary HOF style.

So yes: while it certainly looks interesting, it doesn't seem to leverage the "common enough" theme quite enough.

Also, I somehow feel that what you really want are hygienic macros, which would look much more similarly to what you're doing.

-----

1 point by rkts 6320 days ago | link

Yes, hygienic macros could work too, provided we devise a good way to hack anaphora onto them. Another possible solution would be to just have a very concise syntax for function literals. Either way, I think that the rampant use of (unhygienic) macros where they are not necessary is a problem and needs to be addressed somehow. Especially since Arc is a Lisp-1.

-----

2 points by almkglor 6320 days ago | link

> hack anaphora onto them

Why not just leave unhygienic macros for the anaphora?

> Another possible solution would be to just have a very concise syntax for function literals.

Bingo. cref the discussion on currying some months back.

-----

1 point by rkts 6320 days ago | link

Because unhygienic macros are a pain in the ass. Surely I'm not the only one who thinks this?

-----

1 point by applepie 6319 days ago | link

Maybe you'd like them more if you called them "macros" instead of "unhygienic macros" ;)

No, really, macros, being essentially compilers, give you enough power to build everything you'd ever want into Lisp, including "hygienic" macros, and even to build facilities to write hof-like things in less painful ways.

Maybe they're a pain in the ass if you don't "go meta", in the same way computers are a pain in the ass if you don't build OSes and compilers first.

-----

1 point by stefano 6319 days ago | link

The real point in favor of unhygienic macros is that they are less constraining and, personally, I find them easier to write and read than hygienic macros. I don't find a bad idea to have both hygienic macros and unhygienic macros.

-----

1 point by rkts 6319 days ago | link

Sigh...

Obviously I like unhygienic macros when they are necessary. The problem is, I keep writing higher-order functions and getting tired of typing the "fn" over and over, and then I have to convert the function to a macro, making it twice as long and hard to read. Hygienic macros help, but they still are not as easy to write as plain functions.

I know I'm not the only person who has this problem, but maybe I'm the only one who realizes I have this problem. I see people on the Internet raving about the AMAZING POWER of macros, and most of their examples are just higher-order functions with some small cosmetic changes. Most of the macros in Arc are of the same kind.

I'm not denying that macros are powerful. I just think there is a gross inefficiency in using them where you shouldn't have to, just because of minor syntactic concerns.

I wanted to solve this with a short, clean syntax for function literals, but I haven't been able to come up with one and neither, apparently, has anyone else. So instead I decided to try something that would generate macros out of HOFs.

I thought this would be evident from my post, but apparently it wasn't.

-----

1 point by shader 6319 days ago | link

Maybe we could use { args | body } ? I don't think the braces are taken.

Now, maybe that's a bad idea; instead, we could redefine the brackets, so that a pipe divides args and body, and if it doesn't have a pipe, it defaults to binding _ ? I don't know how hard that would be, or some variation on the concept, but it would be a bit shorter than (fn (args) (body)), if you don't want to type that all the time.

And how exactly does 'w/hof work, as defined so far? And if it's "standard" now, why not just implement it?

-----

3 points by almkglor 6319 days ago | link

Since Anarki defines [ ... ] as (make-br-fn ...), it's actually possible to have the syntax:

  [params || body]
Which would be:

  (make-br-fn (params || body))
(The double bar is needed because a single | is reserved for weird symbols)

By simply redefining make-br-fn, you can redefine the syntax of [ ... ] to an extent

-----

1 point by shader 6319 days ago | link

I wondered if redefining [...] might be possible. It seems to me that the new double bar syntax is practically a super-set of the old one: if it doesn't have the double bar, just treat it like the old bracket function and define _ to be the argument; otherwise use the argument list provided. Including an empty list, I would hope.

Any word on how hard that would be?

-----

1 point by almkglor 6319 days ago | link

  (let old (rep make-br-fn)
    (= make-br-fn
       (annotate 'mac
         (fn (l)
           (if (some '|| l)
               (do
                 your-stuff)
               (old l))))))
Be careful of supersets: someone's code might unexpectedly break ^^

-----

1 point by shader 6319 days ago | link

Isn't that to be expected in an evolving open source language :)

Do you think || is the best choice, or something else?

-----

2 points by rkts 6319 days ago | link

I think it's a bad choice, personally. I'm not crazy about the single pipe either, but || is awful.

Tangent: this may be a dumb question, but do we really need the pipe character for symbols? I know I've never used it. Why not disallow spaces (and the like) in symbols, and free the pipe for new syntax?

-----

2 points by shader 6319 days ago | link

If you don't like the pipe, then recommend something :)

Other possibilities, in no particular order:

  [ # ]
  [ - ]
  [ = ]
  [ -> ]
  [ : ]
  [ => ]
  [ > ]
  [ ~ ]
  [ % ]
  [ ! ]
  [ $ ]
  [ ^ ]
  [ & ]
  [ * ]
  [ @ ]
  [ + ]
  [ | ]
  [ || ]
  [ ? ]
Most of those are either bad looking or already taken. Anything stand out as a good / ok / not bad choice?

-----

2 points by almkglor 6318 days ago | link

:, =>, and -> don't look bad.

# can't be redefined

Let's try some mockups:

  [ a b c :
    (/ (+ (- b) (sqrt (+ (* b b) (* 4 a c))))
       (* 2 a))]

  [: (thunk this)]

  [ a b c ->
    (/ (+ (- b) (sqrt (+ (* b b) (* 4 a c))))
       (* 2 a))]

  [-> (thunk this)]

  [ a b c =>
    (/ (+ (- b) (sqrt (+ (* b b) (* 4 a c))))
       (* 2 a))]

  [=> (thunk this)]

-----

1 point by shader 6304 days ago | link

So, did we ever make a decision about this? Does someone who knows more than I do about this want to implement it?

Also, is there a way to compose or nest these lambda shortcuts? Or would that make this almost impossible to implement?

-----

1 point by almkglor 6304 days ago | link

Nesting doesn't seem impossible: the reader, I think, will handle nesting as:

  [foo [bar]]

  (make-br-fn (foo (make-br-fn (bar))))
As for implementation, it's easy:

  (given ; this gives us access to the old
         ; implementation of [] syntax; it
         ; is used when we don't find the
         ; separator
         old (rep make-br-fn)
         ; use a variable to easily change
         ; the separator
         separator ': ;for experimentation
    (= make-br-fn
       ; a macro is just a function that has
       ; been tagged (or annotated) with the
       ; symbol 'mac
       (annotate 'mac
         ; the reader reads [...] as
         ; (make-br-fn (...))
         (fn (rest)
               ; find the separator
           (if (some separator rest)
               ; note the use of the s-variant givens
               ; the "s" at the end of the name of givens
               ; means that the variables are specifically
               ; bound in order, and that succeeding variables
               ; may refer to earlier ones
               (givens ; scans through the list, returning
                       ; an index for use with split
                       ; (no built-in function does this)
                       scan
                       (fn (l)
                         ((afn (l i)
                            (if (caris l separator)
                                i
                                (self (cdr l) (+ i 1))))
                          l 0))
                       ; now do the scan
                       i (scan rest)
                       ; this part destructures a two-element
                       ; list
                       (params body)
                         ; used to get around a bug in split
                         (if (isnt i 0)
                             (split rest i)
                             (list nil rest))
                 ; it just becomes an ordinary function
                              ; body includes the separator,
                              ; so we also cut it out
                 `(fn ,params ,@(cut body 1)))
               ; pass it to the old version of make-br-fn
               ; if a separator was not found
               (old rest))))))
Edit: tested. Also reveals a bug in split: (split valid_list 0) == (split valid_list 1)

  (= foo [ i :
           [ : i]])

  ((foo 42))
edit2: p.s. probably not really easy much after all^^. As a suggestion, (help "stuff") is good at finding stuff.

edit3: added comments

-----

1 point by shader 6304 days ago | link

Hmm. It doesn't seem to work with the older version. If I try ([+ _ 10] 3) it complains: "reference to undefined identifier: ___"

It used to complain "#<procedure>: expects 1 argument, given 3: + _ 10", but something seems to have changed between updates :)

-----

1 point by almkglor 6304 days ago | link

Have you tried restarting Arc and then repasting the code?

Probably some dirt left from older versions ^^

-----

1 point by shader 6318 days ago | link

I agree, those aren't bad.

I think that out of those, : makes the most sense. They all make logical sense with arg lists, but : looks the best without any.

-----

1 point by almkglor 6318 days ago | link

> Tangent: this may be a dumb question, but do we really need the pipe character for symbols? I know I've never used it. Why not disallow spaces (and the like) in symbols, and free the pipe for new syntax?

Wanna start implementing a reader for Arc?

-----

1 point by rkts 6318 days ago | link

Looks like this is configurable in MzScheme. Do

  (read-accept-bar-quote #f)

-----

1 point by rkts 6319 days ago | link

How would you write a function with no arguments?

-----

1 point by shader 6319 days ago | link

leave the part before the pipe empty, I suppose

  {| body}
it might need a space between the brace and the pipe, but I don't know

-----

1 point by shader 6320 days ago | link

Pardon my ignorance, but what is anaphora as it relates to macros?

-----

2 points by almkglor 6320 days ago | link

It's a macro which automagically binds a name. For example, 'afn:

  (afn ()
    (your-code))
  =>
  (let self nil
    (= self
      (fn ()
        (your-code))))
Or aif:

  (aif x
    (your-code))
  =>
  (let it x
    (if it
      (your-code)))

-----

4 points by rkts 6424 days ago | link | parent | on: Can you help with shortening this code?

  (n-of n (readb stream))

-----

1 point by CatDancer 6424 days ago | link

There we go!

-----

2 points by rkts 6430 days ago | link | parent | on: keep and rem at the same time ?

This is a rare case where I think the tail-recursive solution is more readable:

  (def partition (f xs)
    (if (alist xs)
      (let f (testify f)
        ((afn (xs a b)
           (if (no xs)
             (list (rev a) (rev b))
             (f (car xs))
               (self (cdr xs) (cons (car xs) a) b)
               (self (cdr xs) a (cons (car xs) b))))
         xs nil nil))
      (map [coerce _ 'string] (partition f (coerce xs 'cons)))))
Also, how about generalizing accum to multiple lists?

  (mac accums (accfns . body)
    (let gaccs (map [uniq] accfns)
      `(withs ,(mappend (fn (gacc accfn)
                          (list gacc 'nil accfn `[push _ ,gacc]))
                        gaccs accfns)
         ,@body
         (list ,@(map [list 'rev _] gaccs)))))

  (def partition (f xs)
    (if (alist xs)
      (let f (testify f)
        (accums (a b) (each x xs ((if (f x) a b) x))))
      (map [coerce _ 'string] (partition f (coerce xs 'cons)))))

-----

3 points by almkglor 6430 days ago | link

True, although the final (rev a) (rev b) loses some of the speed benefit in the tail-recursion. If we didn't care about order (which happens quite often) we can drop the (rev ...) things. Otherwise, we can use a modulo-cons form, but this exceedingly complicated.

accums seems good... I suggest pushing it on nex-3's arc-wiki.

What I would like to see is a proper order-preserving accum. Here's a try at a "reasonably efficient" implementation:

  (mac accumf (accfn . body)
    " Collects or accumulates the values given to all calls to `accfn' within
      `body' and returns a list of those values.  The returned list
      is in the same order as calls to `accfn'.
      See also [[accum]] [[summing]] "
    (w/uniq (hd tl)
      `(let (,hd ,tl ,accfn) nil
            (= ,accfn
              (fn (s)
                (if ,hd
                    (do (= ,hd (cons s nil)) (= ,tl ,hd))
                    (do (= (cdr ,tl) (cons s nil)) (= ,tl (cdr ,tl))))))
            ,@body
            hd))))

-----

2 points by sacado 6430 days ago | link

Thanks for your answers ! I think that might be an interesting functionnality to add in the core functions. I used it a few times, in quite different contexts. Partitioning a list from a given criteria looks like a frequent action...

-----

4 points by rkts 6432 days ago | link | parent | on: ~Tilde and macros

Why do we even have the tilde syntax? Isn't ~f the same thing as no:f?

-----

8 points by pg 6431 days ago | link

Yes, ~f means the same as no:f. So far I like the brief and distinctive look of negated functions thus: ~foo. But since potential symbol-syntax chars are rare, I might use ~ for something else one day if negated fns turn out not to be too common. There are currently only 9 in news.arc (1410 LOC).

Edit: Looks like there could be 4 more. Most of news.arc was written before ssyntax, so it's not maximally up to date.

-----

3 points by absz 6432 days ago | link

(1) Technically, it's the same as (complement f), which I believe is roughly equivalent.

(2) Because it's nicer-looking, especially with things like keep.

-----

4 points by aston 6432 days ago | link

But, unlike ~ and complement, no:litmatch works.

-----

8 points by pg 6431 days ago | link

no:litmatch only works in functional position, because (no:litmatch ...) is transformed in ac into (no (litmatch ...)).

To add to the confusion, it's a bug that ~litmatch doesn't work in functional position. There's a comment reminding me to fix that in ac.scm.

-----

1 point by parenthesis 6431 days ago | link

Wouldn't it make more sense for (~foo ...) to expand into (no (foo ...)) ?

-----

2 points by absz 6431 days ago | link

Not really, because that would make (keep ~foo lst) meaningless, and that's an important use of ~.

-----

2 points by cooldude127 6432 days ago | link

complement only works with functions. behind the scenes, it uses apply to call the original function. at first glance, i don't really see a reason that it couldn't just do the same as (compose no _)

-----

2 points by eds 6432 days ago | link

I know compose works on macros... but I'm not sure you are straight on, because compose uses apply as well. And the macro expansions look exactly the same.

  arc> (macex '(compose no litmatch))
  (fn gs1639 (no (apply litmatch gs1639)))
  arc> (macex '(complement litmatch))
  (fn gs1641 (no (apply litmatch gs1641)))
  arc> ((compose no litmatch) "a" "aston")
  nil
  arc> ((complement litmatch) "a" "aston")
  Error: "vector-ref: expects type <non-negative exact integer> as 2nd argument,
  given: \"a\"; other arguments were: #3(tagged mac #<procedure>)"
So what is going on here?

-----

3 points by cooldude127 6432 days ago | link

except calls to compose are optimized away in the interpreter. they are treated specially. look in the `ac' function in ac.scm. i'm guessing this is the reason.

-----

1 point by absz 6432 days ago | link

Ah! That must be the problem, yes. Good catch!

Now, what to do about the other case... there's still no reason it should bug out, it seems to me. Or rather, it should be implementable in such a way that it shouldn't.

-----

2 points by cooldude127 6432 days ago | link

mainly, we need an apply that works for macros. why would that not be possible, i wonder.

-----

5 points by pg 6431 days ago | link

What you're thinking of can be done, but only by calling eval explicitly. I.e. what you could do thus with or if it were a function:

  (apply or args)
you have to do thus with the real or, which is a macro:

  (eval (cons or args))
This is not surprising, because macros live in the world of expressions, not values.

-----

3 points by aston 6432 days ago | link

What you mean is, we need macros to be first class.

-----

1 point by absz 6432 days ago | link

Yes, we do. But if I recall (and I can't test, because the git "wiki" broke for later mzschemes again), apply does work for macros, except it evaluates all the arguments first, which breaks things like and. Someone should test that, though; as I noted, I can't.

EDIT: No, sorry. It doesn't work. My bad.

-----

4 points by nex3 6431 days ago | link

Just as a note, the wiki is fixed now.

-----

1 point by absz 6431 days ago | link

I saw, that's how I was able to test. Thank you for setting it up, by the way! And if you fixed it, thank you for that; if someone else did, thank them for that.

-----

3 points by nex3 6431 days ago | link

You're welcome :-). It would be wfarr who fixed this issue, though.

-----

1 point by cooldude127 6432 days ago | link

well, yeah. basically.

-----

1 point by absz 6432 days ago | link

Compose does something very similar behind the scenes as well, in fact. So there's no reason it should work either.

-----

1 point by rkts 6432 days ago | link | parent | on: Suggestion: constructors

As I said, there are plenty of half-baked solutions to this problem, and mem is an example; it distinguishes two possibilities by returning either a cons or nil. What I'm looking for is a general abstraction that obviates the need for such kludges.

-----

5 points by kennytilton 6432 days ago | link

You say "kludge", I (and apparently pg) say "you do not have a use case, this is an imagined problem" and one should not build languages around imagined problems, you end up with Java. I asked for a use case and you have not supplied one. I am not saying one does not exist, I am saying any discussion of a "fix" is meaningless until we have a target.

Consider again the very similar case of Arc tables being unable to differentiate "key not found" from "key associated with nil". I was halfway thru a brilliant retort to pg on this with an example from my own real live working code (where I used CL's ability to differentiate the cases) when I realized Doh! I totally did not need the capability (and man was I happy I realized that before trying to school pg <g>).

The danger in posting a use case is that all the ones you offer will turn out to be better handled some other way. Then you will know why your bloated fix is not in Arc.

-----

4 points by rkts 6432 days ago | link

I knew someone was going to ask for a use case, which is why I gave an example that (I thought) was so basic as not to require one: find a list of a given length. If you can't solve such a simple problem simply, that indicates to me a fundamental weakness.

Besides the kludginess of mem, consider how easy it is to screw up with find. How do you verify that a list doesn't contain any nils? Without a type system to enforce such invariants you get the worst kind of bugs: those that are manifest only once in a blue moon and present no obvious diagnosis.

That similar problems arise with other uses of nil suggests to me that we're missing an abstraction.

Let me turn this around. Is there any code that would become more verbose with my suggestion? You may think it only helps in weird edge cases (which I dispute), but what do we lose by incorporating it?

-----

4 points by kennytilton 6432 days ago | link

<cough> No, the /code/ you offered was looking for a list with less than a given length with the understanding that nils might be in the list. The test for a list without nils is:

   (all idfn lst)
Is it a kluge to state the test positively? Actually, negative tests are understood to be harder on the cognitive system to get right.

Meanwhile, are you sure (member nil lst) is a kluge? It sure looks readable, and it sure does work. What exactly bothers you about that?

Meanwhile, a use case means your are not just running code samples you typed in at the repl, it means you are trying to solve a problem using a computer and something came up. So here we are after iterating over the members of the library and we built up a list of overdue books per member -- no, that would not have nils, that would have an assoc entry (bob . nil). Hmmm... can you help me here, I cannot think of one.

You have not responded to the very similar example of tables. Correct, one cannot distinguish the absence of a key from a key added with the value nil. A use case is not a couple of lines of code proving that, a use case is a lookup on drivers license number during a traffic stop and...?

My point you may have missed is that without a use case not perfectly well expressed with either of the two solutions I offered above, the fundamental weakness is in offering a theoretical functional gap in a language as if it were a gap in a language.

Recall that pg is already on record as wanting things that are problems in practice, that this is a hacker's language, not a strait jacket language like C++ or Java with all sorts of theoretical angst over untyped variables.

The problem, btw, of putting an air bag in the trunk of a car is the added weight. :)

-----

1 point by byronsalty 6431 days ago | link

In practice I imagine one would just use a known symbol like 'removed mid list/tree instead of blindly using a nil. But this is much less fun.

-----

2 points by kennytilton 6429 days ago | link

Puzzled. Do you mean the nils would arise because non-nil entries would be changed to nil as they were (somehow) processed? Just delete the cons:

   (= (cdr prior-kill-me) (cdr kill-me))
instead of:

   (= (car kill-me) nil)
Otherwise you are forever iterating over the now irrelevant cons,

-----

2 points by rkts 6443 days ago | link | parent | on: Binary Search Trees in Arc

> The haskell version is doing some balancing.

Balancing that causes remove to be O(n). Probably not a good idea.

-----

2 points by raymyers 6443 days ago | link

A more balanced tree will speed up 'find' and 'insert'. If those actions are performed far more often than 'remove', it would indeed be a good idea. Without knowing the use case, I cannot say which I would prefer.

-----

2 points by rkts 6443 days ago | link | parent | on: Binary Search Trees in Arc

I would argue that relying on overloaded comparison functions is a bad idea. Not all things have a 'natural ordering,' and even for those that do you might want to override it at some point.

-----

2 points by raymyers 6443 days ago | link

I think reasonable people can differ on this. Passing comparators as arguments is an approach more typical of dynamically-typed languages (e.g. CL and Scheme 'sort'). In a statically-typed language, it is more common to use a comparison operator that specializes on type (e.g. Haskell Prelude 'sort'). If a set of objects don't have a 'natural ordering', they might not belong in a Binary Search Tree in the first place.

-----

3 points by pg 6443 days ago | link

What do you do if you want to maintain a list of strings sorted according to their length?

-----

4 points by raymyers 6443 days ago | link

If I wanted to keep using the Ord type-class for comparison, I might use a container type, like this:

    data Elem = Elem String deriving (Show, Eq)
    instance Ord Elem where
        Elem x < Elem y = length x < length y
    
    tree1 = insert (Elem "Some string") Empty
    tree2 = remove (Elem "Some string") tree1
If I really wanted to pass in the comparison function, I would have to change my code slightly. See: "A More Faithful Translation" (http://ray.codezen.org/wiki/doku.php?id=binary-search-tree).

-----

0 points by gregwebs 6443 days ago | link

You have just seen a bunch of code that gets the job done, whereas there is still an argument about whether the lisp code should be passed a comparison function. If you are going to make this argument you should show some actual code, because it is obvious from looking at this example which way is better.

-----

2 points by pg 6443 days ago | link

You have just seen a bunch of code that gets the job done,

It seems to me more that you have redefined the job to be what the code does.

-----

3 points by raymyers 6442 days ago | link

Or maybe we just interpreted the job as "a non-destructive binary search tree". Sorry if we misread the syllabus :)

-----

3 points by gregwebs 6442 days ago | link

Sorry, that comment doesn't sound nice and is dumb because I didn't realize what the previous commenter was getting at.

-----

More