Arc Forumnew | comments | leaders | submitlogin
1 point by absz 5894 days ago | link | parent

Since most languages are SOV and SVO, Lisp already does not read like most human languages. Lisp is either VSO or VOS (depending on how it is written); the verb (function) comes first. However, the English or math-speak phrase here is "map f onto x". It is not "map x through f" or "map x by f;" neither of these is ever used. Set-builder notation writes { x/2 : x \in Z }; this is, in many ways, what map is based on, and it puts the function before the list. I find (map xs -) to be far less readable than (map - xs), not to mention (map xs ys +) vs. (map + xs ys). Furthermore, this way of writing it mimics function application: (car x) becomes (map car xs), and so on.

There is a slight gain in indentation-based readability with your version, but I'm not convinced it's substantial enough to warrant the less-readable, less-regular, less-common inversion to be the standard; at most, a provided variant, but probably something that one should define oneself.

And for what it's worth, if the function is particularly long, I usually write

  (map
    [foo (bar _)
         (frob nitz)
         (xyzzy (quux _ _)
                alpha)]
    betas
    gammas)
which preserves the visibility of all the arguments.


1 point by tokipin 5894 days ago | link

i see your point about it mimicking function application, and that seems to me the reason if there was any for why that order was chosen

> I find (map xs -) to be far less readable than (map - xs), not to mention (map xs ys +) vs. (map + xs ys)

well, if the function is named, the order isn't as significant. i personally don't see a difference in either of those pairs, but compare (map + xs ys) to a version where + is a lambda, and likewise with (map xs ys +)

i wonder which usage is more common: map with a named function, or map with a lambda. i'm sure it is the lambda usage, but i haven't programmed much in lisp. it would be interesting to analyze some code and see some numbers

  (map betas gammas
      [foo (bar _)
           (frob nitz)
           (xyzzy (quux _ _)
                  alpha)])

  (map betas gammas (fn (a b)
      (foo (bar a)
           (frob nitz)
           (xyzzy (quux b a)
                  alpha))))
also, it could be made to accept any order. though i don't see a reason for that besides serving as a model for other functions w/ respect to the implied underscore notation

-----

1 point by absz 5894 days ago | link

Clearly, readability is largely personal preference. As I said, I like the syntax of your version for lengthy anonymous functions. But I don't see it as a huge net win. It might, on the other hand, be nice to have a mapover macro which turned

  (mapover as bs ... ys zs (a b ... y z)
    (do-stuff a b ... y z))
into

  (map (fn (a b ... y z) (do-stuff a b ... y z))
       as bs ... ys zs)
which might be clearer in the really-lengthy-function case. Here it is (lightly tested):

  (mac mapover (arg1 arg2 arg3 . arg4+)
    " The last two arguments to mapover are treated as the parameter list and body
      of a function `f', which is then applied to each element of the given
      sequences (the other, previous arguments) in turn; a list consisting of the
      results of this function is returned.  In short, this is equivalent to
      (map f arg1 arg2 ... argN), where `f' is the aforementioned constructed
      function.
      Note that there is *not* an implicit `do' around the body of the function;
      if there were, it would be impossible to tell where the lists ended and the
      function began.
      See also [[map]] [[each]]. "
    (withs (args  (apply   list arg1 arg2 arg3 arg4+)
            body  (last    args)
            parms (car:cut args -2 -1) ; 2nd to last
            lists (cut     args  0 -2))
      `(map (fn ,parms ,body) ,@lists)))
Note that it's limited in that there is no implicit do around the body of the function; since it can take any number of lists, there would be no way to tell where they stopped and the function began. Also note that this may well rely on the Anarki, but I'm not 100% sure if it does.

(And I'm sure someone will tell me that CL's loop can do this in 3... 2... 1...)

Overall, I do think that (map f xs) is cleaner in the named-, short-, and moderately-long-function cases, but it's always possible to write an inversion if you think differently. But what do you think of mapover as another approach?

-----

1 point by tokipin 5894 days ago | link

i wouldn't mind that, though it would probably be considered repetitive with map, and the first thing i would do would be (= map mapover) [edit]oops, macros aren't first class... yet

ya'll confuse me wit dem macros @_@. i made my own function called nap (the other option was pam, which rolls off the tongue with an undesirable cooking-spray sort of feel)

  (def nap args
       (apply map (car (rem acons args)) (keep acons args)))
i'm not exactly sure if it works as it should but it seems ok. i noticed a nifty application of this any-order form:

  arc> (nap '(1 2 3) < '(3 2 1))
  (t nil nil)

-----

2 points by absz 5894 days ago | link

Note that mapover is less general than map. It's explicitly designed for the "long anonymous function" case, so you cannot do

  (mapover '(1 2 3) '(4 5 6) +)
; instead, you must do

  (mapover '(1 2 3) '(4 5 6) (x y)
    (+ x y))
. This is why it is a macro: it needs to treat its last two arguments as part of a function body, so it cannot evaluate them. As a macro, it can package them up and put them in a function, which can be passed to map along with the lists.

nap is a clever function, but will oddly allow you to write (map '(1 2 3) < > '(4 5 6)), which is meaningless. Nevertheless, It's not a terrible idea.

-----

1 point by almkglor 5894 days ago | link

  (mac mapeach (var . rest)
    (if (acons var)
        (with (vals (firstn (len var) rest)
               body (nthcdr (len var) rest))
          `(map (fn ,var ,@body) ,@vals))
        (let (val . body) rest
          `(map (fn (,var) ,@body)) ,val))))

-----