Arc Forumnew | comments | leaders | submit | fallintothis's commentslogin
1 point by fallintothis 5462 days ago | link | parent | on: Share your useful functions/macros

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)))))

-----


If by "atom" you mean non-conses that aren't tables, strings, or functions:

  $ diff -u old-ac.scm new-ac.scm
  --- old-ac.scm  2010-12-01 10:18:36.961984734 -0800
  +++ new-ac.scm  2010-12-01 10:27:02.058606152 -0800
  @@ -647,7 +647,7 @@
   ;       ((or (number? fn) (symbol? fn)) fn)
   ; another possibility: constant in functional pos means it gets 
   ; passed to the first arg, i.e. ('kids item) means (item 'kids).
  -        (#t (err "Function call on inappropriate object" fn args))))
  +        (#t (ac-niltree (apply list fn (ar-nil-terminate args))))))
   
   (xdef apply (lambda (fn . args)
                  (ar-apply fn (ar-apply-args args))))

  arc> (1 2 3)
  (1 2 3)
  arc> (+ (1 2 3) (4 5 6))
  (1 2 3 4 5 6)
  arc> ('a 'b 'c)
  (a b c)
  arc> (map [1] ('a 'b 'c))
  ((1) (1) (1))
You could, of course, have another conditional (e.g., only do it for numbers instead of numbers, exceptions, quoted symbols, sockets, etc.). The catch-all seems dangerous. The special-casing hardly seems worthwhile. It saves you, what, a quote character for list literals (or otherwise a call to list, which isn't that much to type and makes the code clear that there's a literal list happening there)? Versus the other ideas kicking around in the comments of ac.scm, as seen in the diff.

-----

2 points by hasenj 5464 days ago | link

> a call to list, which isn't that much to type and makes the code clear that there's a literal list happening there

So far most macro-intensive lisp code isn't really clear until you know what the macro is doing, you could see (function arg1 arg2 arg3) inside an expression and you'd think it's a function call, but it could be passed to a macro and that macro would transform it into something else.

The advantage I'm hoping for is simplifying building lists (or trees) without resorting to macros.

Here's an explicit alist

  ((a b) (b c))
which otherwise would be a bit more cumbersome:

  (list (list a b) (list b c))
And this is not the same thing as

  '((a b) (b c))
tryarc:

  arc> (= a "w1" b "w2" c "w3")­
  "w3"
  arc> '((a b) (b c))
  ((a b) (b c))
  arc> (list (list­ a b) (list­ b c))
  (("w1" "w2") ("w2" "w3"))
I have to admit this is all somewhat theoretical at this point. For all I know, this pattern never occurs in lisp programs.

-----

3 points by fallintothis 5463 days ago | link

I have to admit this is all somewhat theoretical at this point. For all I know, this pattern never occurs in lisp programs.

Not enough that ambiguities wouldn't still need to be resolved with list anyway. Most of the time, you're not building up a literal tree of elements that you know will be atoms -- you're using variables. E.g.,

  (def enq (obj q)
    (atomic
      (++ (q 2))
      (if (no (car q))
          (= (cadr q) (= (car q) (list obj))) ; call to list here
          (= (cdr (cadr q)) (list obj)        ; call to list here
             (cadr q)       (cdr (cadr q))))
      (car q)))
You couldn't change (list obj) into just (obj), since (a) Arc doesn't like lexical bindings replacing macros, so (obj) would eval to an empty hash table; (b) even if that was fixed (it's easy to do, but vanilla Arc's still bugged), what if you're enqueuing a function, list, string, or hash table? (obj) would eval either to an error (not enough arguments) or would call your function, which is surely a bug. Thus, you need list.

  (def pair (xs (o f list))
    (if (no xs)
         nil
        (no (cdr xs))
         (list (list (car xs)))      ; calls to list here
        (cons (f (car xs) (cadr xs))
              (pair (cddr xs) f))))
could only be changed to

  (def pair (xs (o f list))
    (if (no xs)
         nil
        (no (cdr xs))
         (list ((car xs)))           ; since ((car xs)) would be a list
        (cons (f (car xs) (cadr xs))
              (pair (cddr xs) f))))
which isn't really clearer, and runs into the same problems if (car xs) happens to be a string, list, function, or hash table.

Even

  (def split (seq pos)
    (list (cut seq 0 pos) (cut seq pos))) ; call to list here
couldn't be changed, since seq should be a list or string.

See also the definitions in arc.arc of insert-sorted, reinsert-sorted, defsets of car/cdr/caar/cadr/cddr, setforms, a lot of macro expansions (like obj, though that's arguably replaceable under the proposed scheme), and commonest. That was my point about list making the presence of literal lists explicit.

Places where you could safely use this scheme aren't really big wins anyways. You get to change

  (def queue () (list nil nil 0))
to

  (def queue () (nil nil 0))
or

  (let record (list (seconds) ip user)
in app.arc to

  (let record ((seconds) ip user)
It's rare you'll have to type out a big enough tree literally (i.e., without variables that will cause the problems we've seen) to make it worthwhile, and it doesn't simplify much code that currently uses list anyway.

-----

1 point by akkartik 5463 days ago | link

What about

  `((,a ,b) (,c ,d))

?

-----

1 point by hasenj 5463 days ago | link

Too cumbersome and somewhat confusing.

Actually I think subconsciously I want to get rid of these symbols when ever possible.

More importantly, can you think of a disadvantage for implicit listing of expressions?

-----

2 points by waterhouse 5463 days ago | link

  ;(pmul-deg m) returns a function that multiplies polynomials
  ; and throws out all terms with degree > m
  ;xs is a list of polynomials
  
  ((pmul-deg 5) (car xs) (cadr xs))
I do not want this to be interpreted as an assoc-list.

-----

1 point by hasenj 5463 days ago | link

Right, and in this case I wouldn't want either.

What I'm proposing is, given a list:

  (x ....)
if x doesn't evaluate to a function, hash table, macro, (or whatever else is allowed as a first element), then as a last resort, we interpret the expression as a plain list

In your example, the first expression evaluates to a function, so the normal rules would apply as usual.

-----

5 points by waterhouse 5463 days ago | link

I see. I'll mention first that I wouldn't find this feature useful myself, and would likely be irritated that certain things didn't turn out to be errors. However, here's something I see as a problem that you can probably appreciate:

(1 2 3). Arc evaluates this expression. The car is a number. Therefore, this is interpreted as the literal list (1 2 3).

((1 2 3) 1). By the above, the car of this expression evaluates to the list (1 2 3). This, applied to the argument 1, should give us the second element (the 1th element with zero-origin indexing) of the list (1 2 3), which is 2.

(((1 2 3) 1) 6). Following the above method of evaluation, the car of this expression evaluates to 2, so we get (2 6), which you say should evaluate to the literal list (2 6). This is, of course, quite different from the list (((1 2 3) 1) 6), which is probably what someone who writes literal lists according to your system would expect. I don't think it's possible for (((1 2 3) 1) 6) to be interpreted as a literal list without throwing the Arc "data structure in functional position is interpreted as a lookup" model out the window.

For that matter, I think would already be pretty weird that, given that xs is (1 2 3), (xs u) will be either an element of xs (if u happens to be a number) or a literal list (if u isn't a number).

So, either you have a "literal lists" rule that works for nesting lists most of the time--just not when one of the lists happens to be length 2 and the cadr is a number--or you have to give up the "xs is a list --> (xs n) is the nth element of xs" rule. Perhaps you could restrict it to constructing lists of constant depth; that would be a consistent, easily understood rule that wouldn't infringe on list lookups, although it still would make (xs n) be a drastically different type of object depending on what n was, and I still wouldn't like it or find it useful.

Pedagogical digression:

You say this about using quasiquote in `((,a ,b) (,c ,d)):

> Too cumbersome and somewhat confusing. Actually I think subconsciously I want to get rid of these symbols when ever possible.

Then I think you are ignorant. My intent is not to insult you; I intend this as a statement of fact, and as implied advice (that you should learn more). Look at what quasiquote allows you to do:

  `((,a ,b) (,c ,d)) ;evaluate keys and values in assoc-list
  `((,a b) (,c d))   ;evaluate keys, not values, in assoc-list
  `((a ,b) (c ,d))   ;evaluate values, not keys, in assoc-list
  `(,(a b) ,(c d))   ;construct list of two function calls
By putting in a quasiquote and adding or not adding commas in various positions, you can specify any arbitrary pattern of evaluation for the list ((a b) (c d)). And I assure you, all of these are useful at times; above, I have merely listed ones that are common enough patterns for them to have names; attempting to define the language so that the compiler will always find the correct pattern and you'll never need to use quasiquote is a bad idea. And don't even try writing more than the most basic macros without quasiquote--it's like working without rlwrap, except that can be somewhat remedied by editing a file and defining (l) to load that file.

(Again, I don't intend to insult or flame you. I do intend to flame this idea, because I think it's bad.)

Philosophical digression:

Note, by the way, that the "data structures in functional position are interpreted as lookups" rule is pretty justifiable from a Lisp point of view. You could implement data structures as functions:

  (def my-cons (a b)
    (fn (x)
      (case x
        car a
        cdr b
        (if (isa x 'int)
            (if (is x 0)
                a
                (b (- x 1)))
            (err "Can't do this.")))))
  (def my-car (x)
    x!car)
  (def my-cdr (x)
    x!cdr)

  arc> (= x (my-cons 1 (my-cons 2 (my-cons 3 nil))))
  #<procedure: my-cons>
  arc> (x 0)
  1
  arc> (my-car x)
  1
  arc> (my-car (my-cdr x))
  2
  arc> (x 1)
  2
  arc> (x 2)
  3
The only issue is printing. Here's an example implementation:

http://pastebin.com/KK1bvv85

It isn't perfect, because that 'print function will apply any function to the symbol 'type, which may cause errors. You need some access to the implementation of primitive operators to do this right. But, of course, the language designer has that power, and could quite plausibly have implemented conses and tables as functions, and given 'pr what it needs. So, it makes sense from a very-basic-Lisp point of view that "(the-list-xs 2)" could be a function call that yields the second element of the-list-xs.

I'll add that numbers and atoms are... atomic. Pure and indivisible, the building blocks that you can make everything else in Lisp with. I'm fine with compound data structures being functions, because they can be implemented with functions as featureful as you want, but I think atoms should be simple, basic pieces.

-----

2 points by rocketnia 5463 days ago | link

I was going to say something almost just like this, so kudos. ^_^ However, I cut myself off 'cause I realized that in someone's mind, 6 could primarily be a function that constructs lists that begin with 6, and only incidentally has useful behavior with '+, '-, etc. :-p To be fair, that's pretty silly, and you have other points that are good regardless.

-----

1 point by hasenj 5463 days ago | link

I agree with the first part.

but:

> `((,a b) (,c d)) ;evaluate keys, not values, in assoc-list > ((a ,b) (c ,d)) ;evaluate values, not keys, in assoc-list

or, or ..

  ((a 'b) (c 'd))
  (('a b) ('c d))
Granted, this uses quote symbols too.

My point was finding ways to lessen the need for ` and ,

If [] wasn't already used for lambdas, I could've suggested using it as a raw-list literal. [1 2 3] wouldn't be that bad.

It's possible to use other symbols, like @[1 2 3] where '@[' is a single token, or @(1 2 3).

> Philosophical digression: (...) You could implement data structures as functions

Saw that in SICP :)

-----

2 points by fallintothis 5462 days ago | link

  ((a 'b) (c 'd))
  (('a b) ('c d))
I just noticed none of your alist examples work with the atoms-imply-lists thing -- unless you ditch list-indexing, like (xs 0). That is, even if a, b, c, and d are all atoms,

  ((a b) (c d))
would not be an explicit alist, since

  (a b) == (list a b)
and

  (c d) == (list c d)
Thus,

  ((a b) (c d)) == ((list a b) (list c d))
which throws an error since (list c d) isn't a proper index (i.e., isn't a number).

Even if you could write alists that way, you'd be restricted to only those with atom cars. Personally, I can't think of the last time I needed a literal alist. If I wanted to write them that much, couldn't use quote, and couldn't bare to use list, I'd probably just do

  (def assoc-list args (pair args))

  arc> (assoc-list 'a 1 'b 2)
  ((a 1) (b 2))
and do away with complicating evaluation rules in such fragile ways.

-----

1 point by rocketnia 5463 days ago | link

If [] wasn't already used for lambdas, I could've suggested using it as a raw-list literal. [1 2 3] wouldn't be that bad.

I don't think odd parentheses like [...] are substantially more convenient than operator applications like (list ...). In fact, when editing in a bare-bones text editor, it's a slight pain to have to figure out whether )))]))]))) is the right combination of brackets. :-p

That doesn't mean it's a bad idea altogether. I think Clojure probably has the best practical use of brackets. It uses [] for literal lists just like what you're talking about, but it also generally uses [] brackets wherever there's no operator-and-body format that needs highlighting and indenting. They're used for argument lists, for instance. I haven't heard of someone setting up an editor to take advantage of that consistency, but I'd be surprised if that wasn't the reason for it. ^_^

-----

2 points by hasenj 5463 days ago | link

> ))]))])))

One of the ideas lurking in my head was a symbol to close all open parenthesis

For example, assuming [] isn't used for anything:

  (def fun (args)
    (a (b (c (d)))))
would be written as:

  (def fun (args)
    (a (b (c (d))]  
where ] would tell the interpreter to close everything. Or maybe just close the nearest open parenthesis that's at the beginning of a line.

Granted, something like (a (b (c (d] looks a bit odd, but this looks less odd:

  (a (b (c (d (e (f (g (h)))]
And you'll be able to insert stuff in the middle without having to remember to balance parenthesis at the end:

  (a (b (c (d (x y) (z (e (f (g (h)))]

-----

1 point by rocketnia 5462 days ago | link

Didn't pg talk about this use of ] in one of the early posts on Arc?

I shy away from it only 'cause it reduces the number of editors which can make sense of the brackets.

-----

1 point by akkartik 5463 days ago | link

(Which is reason against PLT's use of [], but doesn't affect arc's chosen use.)

Incidentally [] has one major advantage over (): it doesn't require pressing the shift key every single time. In my vim and emacs I've swapped the two sets of keys in lisp mode.

-----

2 points by rocketnia 5462 days ago | link

Which is reason against PLT's use of [], but doesn't affect arc's chosen use.

Hmm? I don't provide any reasons against Racket's claim that "Using square brackets in a few key places makes Racket code even more readable." In fact, I think it does aid a bit in readability, but it doesn't help when my goal is to correct sloppy brackets. XD

What I am saying is that Arc's [+ 1 _] syntax is about as convenient as (f- + 1 _) or (f-:+ 1 _). Arc also shares the ))]))) issue, a little. It would be more noticeable if more operators accepted functions as their last argument rather than their first argument.

Incidentally [] has one major advantage over (): it doesn't require pressing the shift key every single time. In my vim and emacs I've swapped the two sets of keys in lisp mode.

You mentioned this a while ago, so I've been using only [] in my languages-in-progress. ^_^ It also helps that I begrudge () and {} for looking too similar to each other. :-p The one thing I'm worried about is that ] and [ might be less distinguishable from each other than ) and ( are.

-----

1 point by akkartik 5462 days ago | link

It would be more noticeable if more operators accepted functions as their last argument rather than their first argument.

Yeah, but they don't. Lisp idiom tends to be to put the values being operated upon last, and with good reason: you want to put last the arg most likely to be a temporary. Otherwise you risk separating function calls from their args. Compare:

  (some-function
     (some-verbose-computation
       ...
       ...)
     arg2 arg3)
with:

  (some-function arg2 arg3
    (some-verbose-computation
      ...))
Since there's this major structural constraint I think any dispatch in lisp should be on the type of the last arg. (http://arclanguage.org/item?id=12646)

-----

1 point by akkartik 5463 days ago | link

Hmm, it would involve reimplementing eval inside arc. So far the arc compiler simply converts arc expressions to scheme expressions. You can't evaluate anything then.

-----

2 points by fallintothis 5463 days ago | link

it would involve reimplementing eval inside arc

Guess I should've mentioned this in my first post. People shouldn't be getting hung up on it. The diff was in this function:

  ; call a function or perform an array ref, hash ref, &c

  ; Non-fn constants in functional position are valuable real estate, so
  ; should figure out the best way to exploit it.  What could (1 foo) or 
  ; ('a foo) mean?  Maybe it should mean currying.

  ; For now the way to make the default val of a hash table be other than
  ; nil is to supply the val when doing the lookup.  Later may also let
  ; defaults be supplied as an arg to table.  To implement this, need: an 
  ; eq table within scheme mapping tables to defaults, and to adapt the 
  ; code in arc.arc that reads and writes tables to read and write their 
  ; default vals with them.  To make compatible with existing written tables, 
  ; just use an atom or 3-elt list to keep the default.

   (define (ar-apply fn args)
     (cond ((procedure? fn) 
            (apply fn args))
           ((pair? fn) 
            (list-ref fn (car args)))
           ((string? fn) 
            (string-ref fn (car args)))
           ((hash-table? fn) 
            (ar-nill (hash-table-get fn 
                                     (car args) 
                                     (if (pair? (cdr args)) (cadr args) #f))))
   ; experiment: means e.g. [1] is a constant fn
   ;       ((or (number? fn) (symbol? fn)) fn)
   ; another possibility: constant in functional pos means it gets 
   ; passed to the first arg, i.e. ('kids item) means (item 'kids).
  -        (#t (err "Function call on inappropriate object" fn args))))
  +        (#t (ac-niltree (apply list fn (ar-nil-terminate args))))))
It works the same as any other list/table/string referencing in Arc. Things that look like function calls are compiled to (ar-apply f args), generally speaking (see ac-call), so this logic happens at runtime. Thus,

  arc> (let f [+ _ 1] (f 5)) ; evals as fn call
  6
  arc> (let xs '(a b c) (xs 0)) ; evals as cons ref
  a
  arc> (let xs "abc" (xs 0)) ; evals as string ref
  #\a
  arc> (let h (obj a 1 b 2) (h 'a)) ; evals as table ref
  1
In standard Arc:

  arc> (let x 'atom (x 5)) ; defaults to #t clause
  Error: "Function call on inappropriate object atom (5)"
With the patch:

  arc> (let x 'atom (x 5)) ; defaults to #t clause
  (atom 5)

-----

1 point by akkartik 5463 days ago | link

Ah, I did notice that. This thread feels like it's been going a long time.

-----

1 point by fallintothis 5473 days ago | link | parent | on: QuickCheck for Arc

I do have one comment though (concerning the wiki): rev is not idempotent.

Egads! How embarrassing. That'll teach me to double-check my abstract algebra. :) Thank you, and fixed.

but bit bucket seems to be down

Yeah, there were notices about some maintenance thing or other. Side note: the /src/ vs. /src thing seems fixed now.

One thing that might be nice to show in your tutorial is how to use it more like a traditional unit test syste[m].

I have plenty to say about that, but I can't type it all right now, so I'll have to get back to you in a little bit. Sorry!

-----

2 points by fallintothis 5474 days ago | link | parent | on: QuickCheck for Arc

Hadn't seen it done yet, so I decided to learn how Haskell's QuickCheck (http://www.cse.chalmers.se/~rjmh/QuickCheck/) works by writing an Arc version.

The idea is that you can automatically test a program by providing a set of properties it should follow. QuickCheck then randomly generates a lot of test cases to see if the properties hold and lets you look at the distribution of the test data. Properties are boolean expressions universally quantified over certain types of arguments. They are written in Arc using the utilities that quick-check.arc defines.

After some technical difficulties, I've ported what was my initial tutorial (which was too long for the forum) to the wiki: http://bitbucket.org/fallintothis/quick-check/wiki/Home

Edit: Ugh. Seems my messing around has broken BitBucket. At the time of this edit, http://bitbucket.org/fallintothis/quick-check/src points to an old version, http://bitbucket.org/fallintothis/quick-check/src/ (with a trailing slash) points to the new one. Sorry about that. http://bitbucket.org/fallintothis/quick-check/src/28760e9413... is the correct version, anyway. At least hg pull and the wiki seem to work.

-----

2 points by fallintothis 5481 days ago | link | parent | on: Arc Conference

1. Well, my answer is some odd amalgamation of the other responses. I've been quite busy, will be for the foreseeable future (on the order of a couple years), and Silicon Valley (a common consensus here) is a little too out of my way for a "dinner party", so I almost certainly wouldn't be showing up. Besides being in the same boat as shader wrt the funds and transportation. But it's an interesting idea, so here it goes.

2. Code for Code. What tools facilitate the actual process of programming, and how are those tools themselves programmed? I don't mean this in too broad a sense, like any ol' API or library for frobnicating your database or querying some widget. It's more like "developer tools", though that doesn't sound very zazzy. In the sense I mean, it covers language implementations and compilers, which are certainly very interesting, but also many more things we use that no one seems to get nearly as excited over. REPL tools, syntax highlighters, debuggers, profilers, code introspection, pretty printing, documentation systems, software testing (randomized à la QuickCheck, unit testing, whatever), type systems, bug tracking, ... the list goes on. It starts getting increasingly tangential (e.g., if we count text editors, do we slippery-slope ourselves into counting entire operating systems?). You probably get the gist, though.

My interest is independent from Arc -- or any language, for that matter. I had to learn Vimscript to make a halfway decent highlighter, for instance. But it's the sort of thing I don't think we hear enough about, since we get used either to having these tools or just doing without. So, how do we make programs for programmers, and what kind should we make? How novel can we get?

3. Moot point per 1, but I could talk about some of the work I've done along the lines of 2.

4. February 31. Everyone will be free that day. ;)

-----

1 point by shader 5481 days ago | link

I agree, Code for Code was pretty much what I was looking for as well. I think I like lisp mainly because I can actually work on metaprogramming topics easily and without straying too far from the code itself. Arc doubly so, since the language definition is so short.

Oh, and I don't have anything scheduled for Feb. 31 either ;)

-----


I find it funny that this one didn't even manage to post a URL. How's that for ROI? :)

-----


pg doesn't think so.

  $ grep "(xdef stdout" ac.scm -A2
  (xdef stdout current-output-port)  ; should be a vars
  (xdef stdin  current-input-port)
  (xdef stderr current-error-port)

-----

7 points by fallintothis 5496 days ago | link | parent | on: (map _!name bleh)

  $ grep "(def get" arc.arc
  (def get (index) [_ index])

  arc> (ssexpand '.a)
  (get a)
  arc> (ssexpand '!b)
  (get (quote b))
  arc> (= bleh (map [obj num _] (range 1 5)))
  (#hash((num . 1)) #hash((num . 2)) #hash((num . 3)) #hash((num . 4)) #hash((num . 5)))
  arc> (map !num bleh)
  (1 2 3 4 5)
  arc> (let b 'num (map .b bleh))
  (1 2 3 4 5)

-----

1 point by akkartik 5496 days ago | link

That explains the cryptic errors about get I've gotten on occasion. Thanks!

-----

1 point by d0m 5496 days ago | link

awesome!

-----

3 points by fallintothis 5499 days ago | link | parent | on: List of characters vs string

I thought it would've been kind of implicit in the perennial "hey, let's make strings lists" idea. Which isn't new, by the way.

Haskell

  Prelude> :type "abc"
  "abc" :: [Char]
Erlang

  1> A="abc".
  "abc"
  2> is_list(A).
  true
Prolog

  ?- is_list("abc").
  true.
What else do all these strings-are-lists languages have in common? They're notoriously slow for string processing. Linked lists use up more memory and simple accesses are O(n) rather than O(1). It's to the point that the Haskell guys wrote the ByteString library to override the standard Prelude functions and added a GHC extension to parse "abc" as a ByteString instead of [Char].

Arc itself has deviated on this point (from http://paulgraham.com/arcll1.html). Comments in arc.arc include:

  ; compromises in this implementation: 
  ...
  ; separate string type
  ;  (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail
  ;  not sure this is a mistake; strings may be subtly different from 
  ;  lists of chars

  ; idea: get rid of strings and just use symbols
  ; could a string be (#\a #\b . "") ?
PicoLisp uses the strings-are-symbols approach (http://software-lab.de/doc/faq.html#strings). And that illustrates that it's all a matter of focus. PicoLisp is dynamic, perhaps to a fault. By design, it has no arrays, strings, lambda keyword, or even a compiler. But if you have any investment in efficiency, proper strings seem smarter, if only as a library for when you need the performance (as in Haskell). Never mind arguable conceptual differences.

Arc awkwardly straddles this divide. It has (a) hash tables instead of alists, but (b) lists instead of arrays, (c) a "compiler" that's mostly to keep code from being prohibitively slow, and (d) proper strings, but everyone says they want lists.

It seems to me that the people who want strings == lists are mostly looking for a Grand Unified Theory of Sequences. The idea is that if you don't want the standard lib to be a mess of

  (case (type x)
    cons
      ...
    string
      (coerce 'string (... (coerce x 'cons)))
    god-forbid-we-add-another-sequence-type
      (coerce ...))
you'll just make everything lists, and to hell with the distinction. Everything's the same, so it's simpler!

You could tackle the problem from the other end. Other languages invest in abstractions to write code that sanely deals with different data types. What sort of abstractions do we get out of this? Often some form of object orientation, be it message-passing or generic functions or what-have-you. It starts looking pretty enticing: man, we could make everything an object, and to hell with the distinction. Everything's the same, so it's simpler!

Hmm...

-----

4 points by d0m 5499 days ago | link

Do you think it's a bad thing to have a specialized library for optimized string processing? In the big majority of cases, I personally don't care at all about the performance of my string processing. Would the needs arrive, I'd be perfectly willing to use another library specialized for that (Which I would accept that all the high level function won't work for optimization reasons).

Also, as you say, a couple of (case) would be needed in the standard library. But again, I hardly see why it is a bad thing? (Maybe multi-methods would be a better approach but that's another debate). My point is that I don't really care what the standard lib code looks like.. the important part is the end language result.. no?

Finally, I get your point about the "Grand Unified Theory of Sequences". However, this list of chars suggestion is more about making the code behaviour simpler.. not that much about trying to be "minimalist to be minimalist".

For instance, what (map bleh "test") should do? What should it return? If I know that "test" is simply a list of character, I know for sure what would happen (bleh would be applied on a character and map would returns a list of whatever the function returns). Now, what does it currently do? Maybe the same thing, maybe not. (See that post: http://arclanguage.org/item?id=12311) If my function returns a string instead of a character, it crashes.. since when does map expect something special? I.e. (map ... (range 1 10)) doesn't expect me to return a list of integer.

-----

2 points by fallintothis 5499 days ago | link

Do you think it's a bad thing to have a specialized library for optimized string processing?

Not in principle. Especially if it was totally transparent. This isn't the case in Haskell, because you wind up importing ByteString functions qualified with a prefix like B, so your code is littered with B.map this and B.length that.

But then, if it's totally transparent (i.e., same behavior & syntax between the inefficient one and the efficient one), what was the point of the separation to begin with? Just use the efficient one.

Also, as you say, a couple of (case) would be needed in the standard library.

Actually, no. I was saying that treating strings as lists would make code shorter. E.g., map has to check for strings manually right now.

  (def map (f . seqs)
    (if (some [isa _ 'string] seqs)
         (withs (n   (apply min (map len seqs))
                 new (newstring n))
           ((afn (i)
              (if (is i n)
                  new
                  (do (sref new (apply f (map [_ i] seqs)) i)
                      (self (+ i 1)))))
            0))
        (no (cdr seqs))
         (map1 f (car seqs))
        ((afn (seqs)
          (if (some no seqs)
              nil
              (cons (apply f (map1 car seqs))
                    (self (map1 cdr seqs)))))
         seqs)))
But if strings were built out of cons, we'd be able to prune out that first branch.

  (def map (f . seqs)
    (if (no (cdr seqs))
        (map1 f (car seqs))
        ((afn (seqs)
           (if (some no seqs)
               nil
               (cons (apply f (map1 car seqs))
                     (self (map1 cdr seqs)))))
         seqs)))
(On that note, would there be a difference between "" and nil?)

Finally, I get your point about the "Grand Unified Theory of Sequences". However, this list of chars suggestion is more about making the code behaviour simpler.. not that much about trying to be "minimalist to be minimalist".

My point was that fusing lists and strings is a popular idea since Arc doesn't really have good facilities for polymorphism. You currently need to do the (case ...) junk (or macroexpand into it or whatever), since there are sequences with their subtle interface differences. Strings-are-lists would get rid of this (until we needed another sequence type...). Object-oriented languages "do polymorphism" better (e.g., with multimethods), but breed a fervor for all-things-OO similar to the just-make-everything-a-list sentiment, even though both are extremes.

I don't really care what the standard lib code looks like.. the important part is the end language result.. no?

Right now Arc sits in the middle, without a unified sequence interface -- be it making everything a list or having some other abstraction -- so we all suffer for it, both in the standard library and in user code. Here's a fun one:

  arc> (count #\a '(#\a #\b #\c))
  1
  arc> (counts '(#\a #\b #\c))
  #hash((#\b . 1) (#\c . 1) (#\a . 1))
  arc> (count #\a "abc")
  1
  arc> (counts "abc")
  Error: "Can't take car of \"abc\""
The difference being count uses each, which is polymorphic (i.e., is written essentially with a big (case ...)), but counts hard-codes recursion on cdrs.

See that post: http://arclanguage.org/item?id=12311

Not to get too hung up on this example -- I can appreciate the simplicity of the strings-are-lists model where map is concerned -- but...

You could argue that map's current behavior is sane. There's a subtle difference between expected behaviors, depending on how you want to interpret map. Currently, map says the output sequence should be of the same type as the input. As such, you might expect an error from

  (map [+ "abc" _] "def")
if it was to be understood roughly as

  (map-as 'string [+ "abc" _] "def")
since strings aren't really lists -- they don't have arbitrary elements. (Using the map-as from my post wouldn't actually throw an error here. coerce special-cases strings and flattens them. Yet another subtlety in expectations: should map's "same type" behavior abide by coerce's rules?)

This tangentially relates back to the contagion point in http://paulgraham.com/arcll1.html -- how to shift between strings and lists depending on what sorts of elements are added.

-----

2 points by d0m 5499 days ago | link

(Note: I don't by any means want to be aggressive. I'm still a beginner in the lisp world.. so everything I say here as a big: "Me being a real newbie thinks: " in front :) )

"But then, if it's totally transparent (i.e., same behavior & syntax between the inefficient one and the efficient one), what was the point of the separation to begin with? Just use the efficient one."

Maybe I wasn't clear.. but I never said it needed to be transparent. If I need my strings to be highly optimized, I'd much prefer use a specialized string library and use, say:

  (let optimized-string (create-optimized-string (get-100000-chars))
   (optimized-string-map blah optimized-string))

or maybe a:

  (with/optimized-string
     (let bleh (get-100000-chars)
       (map blah bleh)))
(which would take care of rebinding core high level functions)

-----

Also, I'm not sure I agree with you on the "(until we needed another sequence type...)." Why another sequence type? Any sequences could be coerce to simple list which work with the core functions. Use map-hash, or map-file, or map-whatever if you need specialized version for other sequences. (Or as I said in the last example)

-----

Finally, again (sorry), I don't really agree on: "map says the output sequence should be of the same type as the input."

Is it true?

  (map [string "test" _] '(1 2 3)) -> ("test1" "test2" "test3")
  (map [string "test" _] "123")) -> err
It's only for string that the output needs to be the input. (Which is weird at best in my opinion).

-----

1 point by fallintothis 5498 days ago | link

I don't by any means want to be aggressive.

Neither do I. Just trying to be terse (not that I am anyways; I have a problem with conciseness). :)

I never said it needed to be transparent

No, but I was. In my limited experience, using ByteStrings in Haskell is still too much work, as opposed to having the standard Prelude functions work "out of the box". Instead of being able to say

  map (\x -> 'x') "abc"
you wind up needing to

  import qualified Data.Bytestring as B

  B.map (\x -> 'x') (B.pack "abc")
When you need to start changing every string-related function, the reader, the writer, blah blah blah, it gets to be a hassle. Perhaps it's less of a pain in dynamically-typed languages like Arc. I don't know.

Why another sequence type? Any sequences could be coerce to simple list which work with the core functions.

Not everything is a linked list. And forcefully coercing every data structure into a linked list wrecks the time & space complexities that give many data structures their purpose. You'd force ranges (virtual sequences, a la Python) to eat up memory, though you certainly want to (say) map over them. You force arrays to have O(n) random access times. You couldn't have immutable sequences because linked lists are mutable. Etc.

Use map-hash, or map-file, or map-whatever if you need specialized version for other sequences. (Or as I said in the last example)

Frankly, ad-hoc polymorphism is ugly. Take Scheme, for instance, whose (R5RS) standard comparison operators include:

  char=? char<? char>? char<=? char>=? string=? string<? string>? string<=?
  string>=?  = < > <= >=
where overloaded comparisons would do.

I don't really agree on: "map says the output sequence should be of the same type as the input."

  arc> (type '(1 2 3))
  cons
  arc> (type (map [string "test" _] '(1 2 3)))
  cons
  arc> (type "123")
  string
  arc> (type (map inc "123"))
  string

-----

1 point by d0m 5498 days ago | link

"When you need to start changing every string-related function, the reader, the writer, blah blah blah, it gets to be a hassle. Perhaps it's less of a pain in dynamically-typed languages like Arc. I don't know."

I also don't know :) Maybe advanced arc users could share their opinion on that?

"Not everything is a linked list. And forcefully coercing every data structure into a linked list wrecks the time & space complexities that give many data structures their purpose."

Yes, this is true.

In fact, it's a decision that needs to be taken.. should the core high level function work against different data structure? (As it does with clojure?)

As for now, map does work with string (but has a weird behavior in my opinion) and doesn't work with hash. Is this what we want?

Also, what do you think about multimethods? Do you think it's something useful to be added in Arc?

---------

About the map input versus output, I get what you mean. However, (map [string "test" _] "123") should work :-/ Maybe the problem lies in the concatenation operator while constructing the new string. i.e.

  (map [coerce _ 'int] "123") could give "116101115116"
 
(I know it's not a good example... however, look at this one) :

  (map [string "(" _ ")"] "test") -> Shouldn't it return "(t)(e)(s)(t)" ?!

-----

2 points by fallintothis 5497 days ago | link

should the core high level function work against different data structure?

The answer seems to be a resounding yes. Polymorphism was one of Arc's main principles when it was 3 weeks old (http://paulgraham.com/arcll1.html), and one that's been more-or-less preserved to this day -- just clunkily.

doesn't work with hash

I don't care for maptable in Arc. Seems like something that should be done by map. I address the point about hash-tables-as-sequences more in http://arclanguage.org/item?id=12341.

Also, what do you think about multimethods? Do you think it's something useful to be added in Arc?

From what I've seen, generic functions (single dispatch or multimethods) seem a "Lisp-y" way of solving the type-dispatch problem, and you don't really need to go full-blown OO about it. But I don't know much about other options.

However, (map [string "test" _] "123") should work :-/

Oh, I agree. I was just saying, the case could be made. :P

I think my map-as (from your original thread: http://arclanguage.org/item?id=12341) reflects map's intended behavior best, since coerce is really our standard for how types should interact. Not that it should be implemented that way, necessarily. But since coerce will flatten strings, it seems fair to say that

  (map [string "test" _] "123")
and

  (coerce (map1 [string "test" _] (coerce "123" 'cons)) 'string)
should return the same thing. At present, map errors, but

  arc> (coerce (map1 [string "test" _] (coerce "123" 'cons)) 'string)
  "test1test2test3"
The map-as approach would still preserve the input type == output type behavior, but do it by coerce's rules, which happen to play nicely with strings here.

-----

1 point by akkartik 5497 days ago | link

I've been thinking about the semantics of maptable. Right now it seems misnamed; it iterates over the table before returning it unmodified. But what should the right semantics be? Should it return a table with modified values for the same set of keys? Or should it return a list? Or should each iteration return a (k v) pair so you can get a new table with entirely new keys (I think map-as does this)? All of these could be useful; I think we need a more elaborate language than just map/fold to describe them.

-----

2 points by rocketnia 5497 days ago | link

(map [string "(" _ ")"] "test")

The function [string "(" _ ")"] returns strings, so if anything, the result of that expression should be a sequence of strings, not a string itself.

Nevertheless, maybe (mappend [string "(" _ ")"] "test") should do what you're thinking.

-----

1 point by fallintothis 5505 days ago | link | parent | on: Arc based on?

Chicken Scheme comes to mind: http://www.call-cc.org/

-----

More