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))))))
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:
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.
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:
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
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 :)
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?
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.
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:
EDIT: Creation, reading (defcall), and writing (sref) all give errors if invariants are violated/nonexistent keys are accessed. Is there anything I missed?
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.
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.
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.
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.
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.
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.
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?
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.
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?
[ 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)]
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.
> 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?
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))))
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...
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.
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 _)
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>)"
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.
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.
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.
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.
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.
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.
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?
<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. :)
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.
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.
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.
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.