Arc Forumnew | comments | leaders | submitlogin
Share your useful functions/macros
7 points by waterhouse 4864 days ago | 39 comments
I've had this thread in my head for a long time, and I guess I might as well post it now. (It'd probably be good to have more like it every once in a while.)

In this thread, I'll submit comments describing functions/macros I find generally useful, and I suggest that others do the same. For each item, I'll try to communicate a) what it does, b) why I find it useful, and c) enough of a formal description that readers can implement it without trouble. (Obviously, working code will cover (c), but I have at least one idea that requires platform-specific code. And note that "platform-specific" can refer to Arc implementations as well as operating systems.)

In doing this, I hope to give everyone, and perhaps gain from you, tools to make programming more pleasant--in Arc, but also in any other language (like other Lisps) that supports these items. I also expect this thread to be useful for core language designers to look over: "general-purpose functions/macros that we find useful in day-to-day coding" is pretty much a description of what should go into a core language.

And now I'll try not to run afoul of the Arc Forum's probably existent protections against people submitting too fast (as I've prepared nine comments that I think I'm ready to submit all at once)...



6 points by waterhouse 4864 days ago | link

Function: (cp) : copy-paste. Runs the code in your clipboard. Write code in your favorite text editor, select it, copy it, then go to the REPL and type in (cp) instead of pasting it in. Useful when a) your terminal doesn't like it when you paste in multilined input or b) you don't want to clutter up your REPL interactions. Example implementation and usage:

  (def cp () (map eval (readall:clipboard)))

  ;Now I redefine a couple of functions (e.g. to add or remove printlining)
  ; and copy the new definitions, then use (cp) at the REPL.
  arc> (cp)
  *** redefining int-nthroot
  *** redefining cint-nthroot
  *** redefining cint-omega
  (#<procedure: int-nthroot> #<procedure: cint-nthroot> #<procedure: cint-omega>)
Obviously, the above requires a (clipboard) function, which is useful in its own right:

Function: (clipboard) : Returns the clipboard as a string. Useful when you want to give your program a string from somewhere (maybe capture it with (= x (clipboard)) and then use it), and your alternatives are a) pasting it in literally (which requires escaping), b) pasting to a file and reading it in, and c) trying to call (readline) a bunch of times or something. Also useful in implementing (cp).

(clipboard) probably takes a platform-specific system call. On Mac OS X, the shell command "pbpaste" prints out the clipboard, so I have it defined as:

  (def clipboard () (tostring:system "pbpaste"))
(In fact, since I use "pbpaste" at the terminal, I find the name 'pbpaste more mnemonic.) It seems that on Linux, you can install a utility named 'xclip', and 'xclip -o' outputs the clipboard. I don't know about Windows. Alternatively, if you're using the Racket GUI libraries, there's some platform-independent way to get the clipboard string. And, for completeness, I'll mention pbcopy, which isn't as useful:

Function: (pbcopy x) : Coerces x to a string and sets the clipboard to that string. Named after the MacOSX utility. Useful mainly so I can add two spaces to each line when pasting code into this forum:

  arc> (pbcopy:tostring:map [prn "  " _] (lines:pbpaste))
  nil
Implementation: To avoid issues with escaping and stuff, what you probably want to do is write the string to a temporary file, then do "cat tmpfile | pbcopy" (where "pbcopy" might be replaced with "xclip" on Linux and whatever on Windows).

  (def pbcopy (s)
    (let f (tmpfile)
      (w/outfile gf f (disp s gf))
      (system:string "cat " f " | pbcopy")
      rmfile.f))

  (def tmpfile ()
    (let u (string "/tmp/arctmp" (rand-string 10))
      (if (file-exists u)
          (tmpfile)
          u)))

-----

5 points by akkartik 4864 days ago | link

This thread's making me uncomfortably aware of how many tricks I - and we as a community - have forgotten about. The HN-style forum interface isn't ideal. Perhaps we need a repository of tips like http://vim.wikia.com/wiki/Vim_Tips_Wiki

I remember seeing tricks for cons counting, profiling (http://arclanguage.org/item?id=11556, another story about forgetting the existence of a key tool), tracing, visualizing macroexpansion.. the list goes on. Lots of stuff in the arc2 branches never made it to arc3. I'm certain there's gems there.

-

I posted a few at http://arclanguage.org/item?id=11111

REPL tricks at http://arclanguage.org/item?id=11103

Then there's defgeneric/defmethod. I'm still proud of the writeup on how I rediscovered defgeneric for myself: http://arclanguage.org/item?id=11779

And serialize/unserialize/persisted: http://arclanguage.org/item?id=11865

I like these one-liners:

  (= be is)
  (= neither nor)
  (= redef =)
Here's a counter-intuitive one:

  (= const =)
I'm using 'const' as just documentation - it's up to the programmer to not change the variable.

I don't want to use init (http://arclanguage.org/item?id=11103) because I do want the value to be updated when I reload the file. init is for global state in data structures which shouldn't be reset when I reload the file, and const is for global parameters that influence the behavior of my app. Those I often do want to update on reload.

-

Another recent macro encapsulates a common pattern for initialization code:

  (mac firsttime(place . body)
    `(unless ,place
       ,@body
       (set ,place)))
-

A workhorse of readwarp: pick a random feed from a set and check if it satisfies certain properties (e.g. has an unread item, is recent, is well-cleaned, etc., etc.)

  (mac findg(generator test)
    (w/uniq (ans count)
      `(ret ,ans ,generator
         (let ,count 0
           (until (or (,test ,ans) (> (++ ,count) 10))
              (= ,ans ,generator))
           (unless (,test ,ans)
             (= ,ans nil))))))
The generator expression often uses randpos:

  (def randpos(l)
    (when l
      (l (rand:len l))))
As I've said before, suggestions for better names are especially welcome.

-----

2 points by fallintothis 4864 days ago | link

And then, exploding forth with a dramatic surplus of conceit and utter lack of humility, came ... ME!

cons counting

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

tracing

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

visualizing macroexpansion

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

the list goes on

http://arclanguage.org/submitted?id=fallintothis

(Okay, that last one was excessive, but I thought it was funny. I'm just glad anyone likes things I write. :P)

P.S.: Gah! I hadn't realized rand-elt would break on empty sequences before. Good catch. Should probably use (unless (empty ...) ...), in case you want a random string character.

-----

2 points by shader 4860 days ago | link

Yeah, I've noticed that a lot of arc ideas have been posted as code in comments, instead of linked to on a source control site. This makes it so that the code is more likely to get lost, and harder to use in the first place.

To be honest, this is what I think people should be using the 'lib section of Anarki for; posting code that they think is useful. That way it's available for anyone that wants it. If you post code on the forum, post it somewhere else so that people can find it again.

Maybe people don't like using Anarki for that purpose, either because they're worried about dirtying the codebase, or because they just don't like using git. In that case, maybe we should make a server for arc hacks? Like an HN style arc-app that allows submission of code and comments, but with a search engine and an API for pulling code remotely. Thoughts? Maybe this should me moved to another thread.

-----

1 point by evanrmurphy 4860 days ago | link

> maybe we should make a server for arc hacks? Like an HN style arc-app that allows submission of code and comments, but with a search engine and an API for pulling code remotely. Thoughts? Maybe this should me moved to another thread.

Maybe aw's idea for a new code site could fill this role. Have you seen that recent thread? -> http://arclanguage.org/item?id=12920

-----

1 point by shader 4859 days ago | link

Possibly. At the time I wrote that I hadn't seen that yet, though I had seen previous ideas about an example system, etc.

Certainly, the ideas are related. I suppose any discussion should be on the other thread ;)

-----

1 point by garply 4864 days ago | link

I've got an identical "rand-pos" in my code. Any opinions on which is more correct? I never know when to hyphenate.

-----

1 point by akkartik 4863 days ago | link

I think that's up to our discretion. I don't have a hyphen because it seems like a frequent token in readwarp.

-----

4 points by waterhouse 4864 days ago | link

Macros: (fromfile f . body), (tofile f . body), (ontofile f . body)

'fromfile executes body in a context where stdin is an input-port that reads the contents of the file described by the string f; it closes the port afterwards. 'tofile is similar, but instead binds stdout so that it writes to the file f. 'ontofile is similar to 'tofile, except that output is appended to the file f.

These macros, while not strictly necessary given the existence of 'w/infile, 'w/outfile, and 'w/appendfile (which are implemented almost identically), make it nicer to read from and write to files. There are several macros in Arc that come in pairs, one binding a variable and the other supplying a usually anaphoric default variable name. Examples: 'rfn and 'afn, 'each and 'on, 'iflet and 'aif (although these aren't quite identical). I think this is a good pattern.

'tofile and 'ontofile are especially useful when you want to write to a file using 'pr, 'prn, 'prs, etc., none of which take an optional output-port argument; alternatives would be rebinding stdout using 'w/stdout, or collecting output with 'tostring and 'disp-ing it all at once (which may not work as a substitute if it's important that output be generated incrementally).

Credit for these names goes to fallintothis: http://arclanguage.org/item?id=12272

  (mac fromfile (f . body)
    (w/uniq gf
      `(w/infile ,gf ,f
         (w/stdin ,gf
           ,@body))))
  (mac tofile (f . body)
    (w/uniq gf
      `(w/outfile ,gf ,f
         (w/stdout ,gf
           ,@body))))
  (mac ontofile (f . body)
    (w/uniq gf
      `(w/appendfile ,gf ,f
         (w/stdout ,gf
           ,@body))))

-----

1 point by akkartik 4864 days ago | link

I was calling ontofile w/prfile. Thanks for the better name and all the context.

-----

4 points by waterhouse 4864 days ago | link

Function: (accumulate comb f xs init next done) : A function useful for expressing many accumulation patterns: mapping, summation, products, and others. You use next to iterate over xs until xs is done, and the results are mapped by f and comb-ined one by one with init. For example:

  (map f xs) = (rev:accumulate cons f:car xs nil cdr no)
  (factorial n) = (accumulate * idfn 1 1 inc [> _ n])
  (rev xs) = (accumulate cons car xs nil cdr no)
  (range a b) = (rev:accumulate cons idfn a nil inc [> _ b])
  (len xs) = (accumulate + [idfn 1] xs 0 cdr no)
  (sum f a b) = (accumulate + f a 0 inc [> _ b])
  (sumlist f xs) = (accumulate + f:car xs 0 cdr no)
  (mapn f a b) = (rev:accumulate cons f a nil inc [> _ b])
I use this a fair amount. See also: http://arclanguage.org/item?id=10791

  (def accumulate (comb f xs init next done)
    ((afn (xs total)
       (if (done xs)
           total
           (self (next xs) (comb (f xs) total))))
     xs init))

-----

3 points by waterhouse 4864 days ago | link

Macro: (as type x) : Macroexpands to (coerce x 'type). Leads to the more pleasant form:

  (as string
      (big long
           (hairy gigantic)
           (immense and complicated (function))))
instead of:

  (coerce (big long
               (hairy gigantic
               (immense and complicated (function)))
          'string)
In the first case, you can hear the coder thinking, "Now I want to coerce this thing into a string; this thing is big and long and hairy and [...]." In the second case, the coder thinks, "Now I want to coerce this thing into a string; I write coerce, and now I'll have to remember to write 'string; now this thing is big and long and hairy and [...]; and now what was that I was remembering? I see a coerce up there with the unbalanced parenthesis... oh, yeah, 'string." It's just a little bit easier on the mind.

  (mac as (type x)
    `(coerce ,x ',type))
Credit to aw for creating this: http://arclanguage.org/item?id=10752

(Incidentally, aw's thread doesn't show up in aw's "submissions" list. Weird.)

-----

1 point by rocketnia 4864 days ago | link

If I may:

  (string:big long
    (hairy gigantic)
    (immense and complicated (function)))
No macro is needed, and there's less indentation and fewer parentheses. You can also improve on the 'as macro by allowing for (as (type expr)) in addition to (as type expr). That lets you cut down on indentation and parentheses just as much:

  (as:string:big long
    (hairy gigantic)
    (immense and complicated (function)))

-----

2 points by akkartik 4864 days ago | link

as is still useful for coercing to other types.

-----

1 point by rocketnia 4864 days ago | link

Sure is, but I'm just a bit of an un-fan of 'coerce. I've already mentioned unary functions being more convenient with a:b ssyntax. On top of that, there's oftentimes more than one obvious way to get one type of value from another, and while you could use custom types to represent more coercion targets, like (rep:coerce x 'assoc-list), I think that's a step in the wrong direction, just 'cause I don't know where the benefit is.

-----

1 point by akkartik 4863 days ago | link

Ah I see. But I don't follow what you mean by unary a:b.

-----

1 point by rocketnia 4863 days ago | link

The ssyntax expressions (a:b ...), a.b, and a!b all pass only one argument to a, so ssyntax (currently) makes one-argument functions and macros more convenient than other kinds.

-----

1 point by akkartik 4862 days ago | link

Ah, that makes sense. And why is it bad to have more than one way to convert one type into another?

-----

1 point by rocketnia 4862 days ago | link

Eh? XD There's nothing wrong with that. My point was that it doesn't make a whole lot of sense to designate just one conversion for each target type when there are lots of options.

(Note from the future: What follows doesn't really continue that response. I just started randomly considering a few more angles. ^_^ )

Most of the time, [coerce _ 'string], [coerce _ 'sym], and so forth just end up being more verbose ways of saying things like [string _] and [sym _]. Almost all the uses of 'coerce in Arc 3.1 and Anarki are like that, where they hard code the target type, so that it's just like the name is coerce-cons or coerce-string but without any synergy with ssyntax. There are two exceptions:

  (def sort (test seq)
    (if (alist seq)
        (mergesort test (copy seq))
        (coerce (mergesort test (coerce seq 'cons)) (type seq))))

  (def inc (x (o n 1))
    (coerce (+ (coerce x 'int) n) (type x)))
These functions have the special property that they can be used with all sorts of types as long as 'coerce is capable of translating back and forth. If you want to make a custom type that's compatible with them, you only need to replace or extend 'coerce to do it, rather than, well, replacing 'sort or 'inc. :-p This technique was sort of rediscovered here recently: http://arclanguage.org/item?id=12340

Now that I think about this, my position has softened even more, but I'm still a bit stubborn. Even if 'coerce is only used as a way to convert something back from the type a calculation needs to manipulate it in, there's still an arbitrary choice being made between multiple behaviors. If some generic string utility wants to convert '(#\[ #\1 #\]) to "[1]" and back, but some JSON utility wants to convert '(1) to "[1]" and back, then (coerce "[1]" 'cons) needs to have two meanings at once.

Of course, if it comes to that, it's easy to just make the JSON utility convert from '(1) to (annotate 'json-encoded "[1]") and back instead. I think I have no escape from that. XD Any corner case I come up with is going to be at least as you're-not-gonna-need-it as 'coerce is. ^_-

Nevertheless, while I was arguing with myself, I came up with an experimental type-independent (and therefore 'coerce free) way to model back-and-forth transformations. It's a pretty obvious design, but I somehow managed to make it complicated and arbitrary in certain ways. :-p Here it is in case anyone's interested:

  (def encode-int (unencoded)
    (list `(type ,type.unencoded) int.unencoded))
  
  (def decode-int (recovery-notes encoded)
    (let fail (fn () (err "Unexpected 'decode-int case!"))
      (case do.recovery-notes.0 type
        (case do.recovery-notes.1
          string  string.encoded
          int     encoded
                  (do.fail))
        (do.fail))))
  
  (= encoding-int (annotate 'encoding
                    ; This is more hackable than
                    ; (list encode-int decode-int).
                    (list (fn args (apply encode-int args))
                          (fn args (apply decode-int args))))
  
  (def fn-through (encoding unencoded body)
    (withs ((encode decode) rep.encoding
            (recovery-notes encoded) do.encode.unencoded)
      (do.decode recovery-notes do.body.encoded)))
  
  (mac through (encoding var . body)
    `(fn-through ,encoding ,var (fn (,var) ,body)))
  
  (def inc (x (o n 1))
    (through encoding-int x
      (+ x n)))
(Hmm, is this related to http://en.wikibooks.org/wiki/Haskell/Monad_transformers#lift? >.> )

-----

2 points by akkartik 4862 days ago | link

"it doesn't make a whole lot of sense to designate just one conversion for each target type when there are lots of options."

Ah, now I follow. But often there's one kind that applies far more often than alternatives. And it's good for coerce to give it. For other type combinations it's good to know that coerce is complete, and that it'll give some sort of conversion. But you're right, I don't know if this ever makes code shorter. Perhaps this is yet another example of programmer's OCD, that insidious need to have things be aesthetic even when they don't matter.

I just started randomly considering a few more angles. ^_^

I'm starting to realize that's the reason I have a hard time following what you write :) If I may make a suggestion, make a list. I find your long comments are often too terse. Often they contain enough stuff for three or four distinct postings or comments. But the transitions are abrupt, perhaps because you're trying not to be even more long-winded. If you gave each idea its own post or comment it would have more room to breathe, and I'd be able to return to each one at my leisure. Feel free to post new submissions with substantial ideas. We hardly have enough submissions here.

Like your code idea at the end of the comment, and the suggestion that it's like monad transformers. I'd love to see a post elaborating on it. I'd love even more to see a yegge-sized elaboration of all your thoughts on coerce and what 'synergy with ssyntax' means. Perhaps they haven't settled down yet, but keep it in mind when they do :)

Sometimes I feel I would spend an hour with any one of your ideas if you'd spent just a few extra minutes with it.

Hopefully this is constructive feedback. Perhaps it's just me having poor comprehension; you should ask for others' opinions.

-----

2 points by rocketnia 4862 days ago | link

I have a hard time following what you write :)

Thanks for telling me that. ^^;

My usual process is that I'll take a few hours to write a comment, then I'll run out of time and decide to come back later to finish it up, and then I'll never go back to that draft (but I might start from scratch on the same topic). Occasionally I'll finish something in time to post it.

Recently, I have less time than I'm used to, so I've been posting things even if they're rough, in case someone gets value out of it anyway. I guess this makes my posts into scatterings of half-ideas, based on things I've been thinking to myself but only now had a reasonable excuse to mention. (Usually I shoot for a few organized, mostly full ideas, which also have very good excuses to be mentioned. :-p )

Thanks to your suggestion, I'll try harder to talk about things even when there isn't an excuse. ^_^

-----

1 point by akkartik 4864 days ago | link

(Incidentally, aw's thread doesn't show up in aw's "submissions" list. Weird.)

I noticed this as well as I was trying to recall some old ideas. For some reason people's submitted pages here don't get a more link like they do on HN. I emailed PG a request.

-----

2 points by waterhouse 4864 days ago | link

Macro: (xloop var-vals . body) : Extremely useful macro to create and use a local recursive function named 'next. var-vals is a list of alternating variables and initial values (in the style of the built-in Arc 'with macro), and body is the body of the function (which probably contains calls to 'next). To illustrate, we shall calculate the factorial of n:

  (xloop (i 1 total 1)
    (if (> i n)
        total
        (next (+ i 1) (* total i)))))
This macroexpands to:

  ((rfn next (i total)
     (if (> i n)
         total
         (next (+ i 1) (* total i))))
   1 1)
This is my go-to when I want to write a function that needs looping or general recursion. I use this macro all the time:

  $ grep -c xloop a general-poly.arc
  a:38
  general-poly.arc:18
Credit for xloop's current form goes to aw: http://awwx.ws/xloop0 That link also contains an implementation, which I will reproduce here for convenience (though, for consistency, I have renamed the first argument to var-vals, which I hope doesn't irritate aw):

  (mac xloop (var-vals . body)
    (let w (pair var-vals)
      `((rfn next ,(map car w) ,@body) ,@(map cadr w))))

-----

4 points by rocketnia 4864 days ago | link

My 'xloop at http://github.com/rocketnia/lathe has a more convenient interface at the expense of overall simplicity. It supports the usual syntax, but it also lets you leave off the parentheses as long as the things you're binding to are non-nil, non-ssyntax symbols, like most variable names are.

  (xloop i 1 total 1
    (if (> i n)
      total
      (next (+ i 1) (* total i))))
The restriction on the bindings makes it possible to figure out where the body starts.

-----

1 point by aw 4864 days ago | link

which I hope doesn't irritate aw

Gosh no! Most everything I write is based on code I've seen elsewhere; with either what I hope will be an incremental improvement or else with some minor change to make it fit a personal preference. For me to complain because other people then share their own improvements would be rather foolish... :)

-----

2 points by waterhouse 4864 days ago | link

Function: (mapn f a b . xs) : "map n". Primary usage: (mapn f a b) = (map f (range a b)). Extra argument pairs will yield nested applications of 'mapn, with f applied to a kind of product of all ranges. The implementation and a demonstration will make this clearer:

  (def mapn (f a b . xs)
    (if no.xs
        (map f (range a b))
        (mapn [apply mapn (fn args (apply f _ args))
                     xs]
              a b)))

  arc> (mapn square 1 10)
  (1 4 9 16 25 36 49 64 81 100)
  arc> (mapn list 1 3 20 22)
  (((1 20) (1 21) (1 22)) ((2 20) (2 21) (2 22)) ((3 20) (3 21) (3 22)))
'mapn is very nice for testing out a function on a numerical range, especially repeatedly. Also, given the 'grid function, we can construct two-dimensional tables of a function extremely easily:

  arc> (grid:mapn * 1 7 1 7)
  1  2  3  4  5  6  7
  2  4  6  8 10 12 14
  3  6  9 12 15 18 21
  4  8 12 16 20 24 28
  5 10 15 20 25 30 35
  6 12 18 24 30 36 42
  7 14 21 28 35 42 49
  nil
  arc> (grid:mapn choose 0 7 0 7)
  1 0  0  0  0  0 0 0
  1 1  0  0  0  0 0 0
  1 2  1  0  0  0 0 0
  1 3  3  1  0  0 0 0
  1 4  6  4  1  0 0 0
  1 5 10 10  5  1 0 0
  1 6 15 20 15  6 1 0
  1 7 21 35 35 21 7 1
  nil

-----

1 point by waterhouse 4864 days ago | link

Here are a bunch of printing functions, which I frequently use in combination.

Function: (prsn . args) : Prints the elements of args separated by spaces, then prints a newline. Extremely handy for debugging: add in (prsn a b c) when you want to see the state of a,b,c. Equivalent to the built-in prs, except this also prints a newline.

  (def prsn args (apply prs args) (prn))
Function: (pad x wanted-len (o side 'left) (o pad-char #\space)) : Coerces x to a string, then adds enough pad-char characters on the left or right side to make x be wanted-len long. Returns the resulting string. If x is longer than wanted-len, this will throw an error.

Very handy for printing out a table of data, especially in conjunction with prsn. Also good for, e.g., printing out dates which you want to be constant-length. Look at this beautiful table:

  arc> (for i 6 15 (prsn (pad i 2) (pad fib.i 3) (pad (fib:* 2 i) 6)))
   6   8    144
   7  13    377
   8  21    987
   9  34   2584
  10  55   6765
  11  89  17711
  12 144  46368
  13 233 121393
  14 377 317811
  15 610 832040
  nil

  (def pad (x wanted-len (o side 'left) (o pad-char #\space))
    (zap string x)
    (let padding (newstring (- wanted-len len.x) pad-char)
      (case side
        left (string padding x)
        right (string x padding))))
Of course, in that demonstration, I found those values 3 and 6 by experiment and having 'pad throw errors until I increased wanted-len enough. This may suck for you; therefore, here is a function to print out a whole grid:

Function: (grid xses (o strict nil)) : Prints out xses, which should be a list of lists, in a grid; that is, it pads each element enough that all elements in each column are the same width. If strict is t, then all columns will be the same width. Works even if lists are different length. Note that my definition uses 'pad and 'prsn. Illustration with Pascal's triangle, first without strict, then with:

  arc> (grid:accum a (for n 0 10 (a:accum b (for k 0 n (b:choose n k)))))
  1
  1  1
  1  2  1
  1  3  3   1
  1  4  6   4   1
  1  5 10  10   5   1
  1  6 15  20  15   6   1
  1  7 21  35  35  21   7   1
  1  8 28  56  70  56  28   8  1
  1  9 36  84 126 126  84  36  9  1
  1 10 45 120 210 252 210 120 45 10 1
  nil
  arc> (grid (accum a (for n 0 10 (a:accum b (for k 0 n (b:choose n k))))) t)
    1
    1   1
    1   2   1
    1   3   3   1
    1   4   6   4   1
    1   5  10  10   5   1
    1   6  15  20  15   6   1
    1   7  21  35  35  21   7   1
    1   8  28  56  70  56  28   8   1
    1   9  36  84 126 126  84  36   9   1
    1  10  45 120 210 252 210 120  45  10   1
  nil
I love this. Implementation:

  (def grid (xses (o strict nil)) ;dense lines of code for brevity
    (let lens (map [best > (map (fn (xs) (len:string:car:nthcdr _ xs)) xses)]
                   (range 0 (dec:best > (map len xses))))
      (when strict (let u (best > lens) (= lens (n-of len.lens u))))
      (each xs xses (apply prsn (map pad xs lens)))))

-----

4 points by rocketnia 4864 days ago | link

Macro: (thunk body). It's simple enough. Just like the square bracket syntax covers one-argument functions, this can cover zero-argument functions.

  (fn () (err "Ack! You called me!"))
  (thunk:err "Ack! You called me!")
When the function body is long, this helps reduce indentation and parentheses. (That's sort of a hobby of mine, I guess. :-p )

-----

3 points by evanrmurphy 4849 days ago | link

> Macro: (thunk body). It's simple enough. Just like the square bracket syntax covers one-argument functions, this can cover zero-argument functions.

A side effect of making all function arguments optional [1] is that the square bracket syntax can then cover zero-argument functions too, so we get this additional (shorter) way to express your thunk example:

  [err "Ack! You called me!"]
---

[1] http://arclanguage.org/item?id=13030

-----

1 point by akkartik 4849 days ago | link

Nice. So you can use [] if it saves you a set of parens, and thunk to sidestep the implicit function call:

  (thunk 34)

-----

1 point by rocketnia 4849 days ago | link

Actually, I don't use 'thunk when it doesn't save me parens. Both "thunk" and "fn ()" take up the same number of characters (albeit not the same number of tokens), and I figure Arc programmers will have to look up what 'thunk means more often than they look up 'fn. :-p

Incidentally, it actually does save parens in that case:

  thunk.34

-----

1 point by akkartik 4864 days ago | link

Like do but without a set of parens. Nice. Wish I could define do in terms of thunk, but it's too early in arc.arc.

-----

1 point by waterhouse 4864 days ago | link

Macro: (aps x) : Returns a list of all bound Arc symbols for which x (coerced to a string) is a substring of the symbol (coerced to a string). Come to think of it, I'll make it sort them, too. Extremely useful if you wonder what precisely a function was named, or whether certain functions exist.

  arc> aps.map
  (deep-map map map-chooses map-more map-subperms map1 mapn mappend maptable s-map s-map1)
Implementing this requires access to the underlying Racket (or something similar in non-Racket Arc implementations). If, as in Anarki, ($ expr) causes expr to pass straight through the Arc compiler into underlying Racket, then the following implementation will work:

  (def arc-namespace ()
    (map [sym:cut string._ 1]
         (keep [is string._.0 #\_]
               ($.namespace-mapped-symbols))))
  (def apropos-fn (x)
    (sort < (map sym (keep [findsubseq x _]
                           (map string (arc-namespace))))))
  (mac aps (x) `(apropos-fn (string ',x)))
See also: http://arclanguage.org/item?id=11468

For hacking ac.scm to make '$ work, see http://arclanguage.org/item?id=8719 and use '$ instead of 'mz.

-----

1 point by fallintothis 4864 days ago | link

I remember that thread! Good stuff.

Though it's more limited, you might even just use sig in vanilla Arc.

  (mac apropos (name)
    (list 'quote
           (sort (compare < string)
                 (keep [findsubseq (string name) (string _)] (keys sig)))))

-----

1 point by waterhouse 4864 days ago | link

Function: (count-up xs) : Returns a list of lists of the form (x n), where x is an element of xs and n is the number of times x occurs in xs. This list is sorted to present most frequent items first (otherwise it would be sorted pseudorandomly).

  arc> (count-up "banana")
  ((#\a 3) (#\n 2) (#\b 1))
  arc> (factor 720)
  (2 2 2 2 3 3 5)
  arc> count-up.that
  ((2 4) (3 2) (5 1))
Useful for, well, counting things up; I've used this to count word and letter frequencies, calculate the totient function, create a couple of histograms, compute the number of distinct permutations of a list, help print out a number's prime factorization, test the randomness of supposedly random functions, and do some other things.

  (def count-up (xs)
    (let u (table)
      (each x xs
        (++ (u x 0)))
      (sort (compare > cadr) (tablist u))))

-----

2 points by fallintothis 4864 days ago | link

Ah, that's a fun one. Could use counts to get a table in vanilla Arc, except that it hard-codes recursion on cdrs, so it won't work for strings. From arc.arc:

  (def counts (seq (o c (table)))
    (if (no seq)
        c
        (do (++ (c (car seq) 0))
            (counts (cdr seq) c))))
It's also easy to not notice the built-in sortable from srv.arc:

  (def sortable (ht (o f >))
    (let res nil
      (maptable (fn kv
                  (insort (compare f cadr) kv res))
                ht)
      res))
So,

  (= count-up (compose sortable counts [coerce _ 'cons]))
until counts gets fixed to use each, at which point it's just

  (= count-up sortable:counts)

-----

1 point by akkartik 4864 days ago | link

Ah, getting rid of the optional arg lets me define counts using defgeneric!

I've updated my repo with your suggestions: https://github.com/akkartik/arc/commit/a5b3c6d6bf616f51bc4ab...

-----

1 point by akkartik 4864 days ago | link

Hey, I call this freq. Nice to meetcha.

-----

2 points by aw 4864 days ago | link

My favorite debug print macro:

  (mac erp (x)
    (w/uniq (gx)
      `(let ,gx ,x
         (w/stdout (stderr)
           (write ',x)
           (disp ": ")
           (write ,gx)
           (disp #\newline))
         ,gx)))

  arc> (+ 3 (erp (/ 4 2)) 5)
  (/ 4 2): 2
  10

-----