Arc Forumnew | comments | leaders | submitlogin
Map et al arguments are backwards
4 points by tokipin 5882 days ago | 25 comments
The order of the arguments to map are backwards. Currently it is:

  (map fn list)
It should be:

  (map list fn)
The reason isn't SVO or CNN or anything like that, but simply that most of the time (or so I think), the size (in characters and line numbers) of the fn is larger than the size of the list, so what you get is:

  (map (blee (blah
    (+ @(bleh meh)
        (bluu meh))
    (* $(if moo))))
    list)
As you can see, the poor list is buried. When you start reading the expression, starting from '(map', you can't grasp what the whole expression is doing because your eyes are blasted with a monstrous fn. But if the arguments are in the proper order:

  (map list
    (blee (blah
    (+ @(bleh meh)
        (bluu meh))
    (* $(if moo)))))
You can readily see or at least anticipate what the expression is doing. Having the smaller half of the expression at the front makes the whole expression more readable, and if you know what the list is, you can anticipate the type of stuff fn will do and you will have some anchorage with which to parse it better, where you would otherwise have to shoot your eyes to the bottom and shoot them back to the top.

An idiomatic form could then be:

  (map list (fn (x y z)
    (dostuff x y)
    (domorestuff z)))
I know the lambda wouldn't be multivariate in this case, but though I use map here I'm talking generally.

The function could also be made to accept different orders, which would allow it to be curried at any argument(s) with the succinct "implied underscore" form of [... _ ...].



7 points by lojic 5882 days ago | link

What you're suggesting is closer to the way Ruby does it with the function being last:

  [1, 2, 3].map {|x| x * x }   # OR

  [1, 2, 3].map do |x|
    # larger function
  end
However, I think (map f list) is preferred since you're applying the function to the list. It's also the way Haskell does it, so it must be right :)

-----

4 points by tel 5882 days ago | link

Haskell chooses this order because partial application and pointfree style are really common.

   let squareall = map (^2)
   squareall [1,2,3] ===> [1,4,9]
   squareall [3,4,5] ===> [9,16,25]
   let f = foldr1 (+) . squareall . filter even
   f [1,2,3,4,5] ===> 20
But since partial application is not a default behavior in Arc ([map square] and [map _ square] are not that different from one another) the order of the fn and list aren't as important.

Rule of thumb might be: put the fastest changing piece last, but this syntax thing might be a stronger heuristic.

-----

4 points by cchooper 5882 days ago | link

In all the Arc code I've written so far, I've used map a lot. That's probably quite common for anyone programming in a language based on lists. I therefore propose that Arc have a special syntax that turns a normal function into one that maps over a list.

For example:

  ($car '((a 1) (b 2) (c 3))) == (map car '((a 1) (b 2) (c 3)))
Note that $car is an expression that returns a function, so you could pass it to other functions.

  (apply $car '(((a 1) (b 2) (c 3))))
Also, $$car would be a valid expression, as would $[car _] or $(fn (x) (car x)) and so on.

-----

2 points by Jekyll 5881 days ago | link

  (def $ (x . lists)
         (if lists
             (apply map x lists)
             (fn (lists) (apply map x lists)))) 
More or less does what you want, without special syntax. ($$car... would have to be written ($($ car)... though.

To me this looks like a specific case of wanting currying/partial application built in for functions of semi-fixed arity, rather than a need for new syntax.

-----

2 points by absz 5881 days ago | link

You can even be cleaner: $car is $.car (though this doesn't work for $.$.car). But I don't think this is really a specific case of partial application (though I see where you're coming from). While partial application would be ((map car) '((1 2) (3 4) (5 6))) and this would be ($car '((1 2) (3 4) (5 6))), the biggest win (for me) is not the partial application aspect, but the conciseness of having just one character, as opposed to (currently) ([map car _] '((1 2) (3 4) (5 6))) or ([apply map car __] '((1 2) (3 4) (5 6))), or even the partial application version. And even so, this strikes me as more similar to a request for explicit partial application, which is already implementable, than implicit partial application.

-----

4 points by cchooper 5881 days ago | link

Yes, it was the conciseness I was after. The syntax was inspired by K, where you can do this by putting a ' before a function name.

Of course, K often looks like line noise, but then K takes this technique to its logical conclusion and uses special syntax for everything. There's no need to go that far.

-----

1 point by nex3 5882 days ago | link

I dislike this idea, mostly because the syntax is pretty trivial anyway using the bracket currying suggested elsewhere. [map car] isn't much shorter than $car, and it's much more flexible and leaves $ (or whatever symbol might end up being used) open for some other meaning. It's also more explicit, although I'm not sure that's worth much.

-----

1 point by absz 5882 days ago | link

That's not necessarily true; as I pointed out further down (http://arclanguage.org/item?id=4704), you can get the effect you want. On the other hand, you do take a speed hit. I'm inclined towards supporting both, but swapping the args is very easy to write and may not belong in the core.

-----

2 points by tel 5882 days ago | link

pg said he is planning on opening up intrasymbol syntax and, presumably, presymbol syntax as well. Once that's there this is a trivial addition to play with.

-----

1 point by absz 5882 days ago | link

But when? That may be the single biggest change I want, actually--much of the rest is implementable within Arc, but this is not. Certainly, when that happens this is trivial, but it hasn't yet, and there's no inkling of when.

-----

4 points by tel 5882 days ago | link

Well, pg has at least suggested that he will add that functionality. This is the first I've seen of this presymbol operator.

If you can't wait, implement this or presymbol syntax into Anarki.

-----

1 point by absz 5882 days ago | link

I really like this idea, but I don't know that $ is the best choice. On the other hand, I can't think of something better... ^car? &car? At any rate, fantastic idea.

-----

5 points by nex3 5882 days ago | link

I believe this ordering is designed to allow map to take multiple lists to map over. As cooldude127 pointed out, you can't really do varargs if you put the function at the end.

-----

1 point by Darmani 5878 days ago | link

  (def foo args
    (apply (last args) (cut 0 (- (len args) 1))))
  arc>(foo 1 2 3 +)
  6
You can, but I can't exactly call that elegant.

-----

1 point by absz 5882 days ago | link

Hmmm, I see what you mean. On the other hand, one might also often do something like (keep even mylist), which doesn't have that problem and does, I think, read better. One good option might be to provide both forms (map and fmap (f for flip or function), for instance)--map would be used where the list was longer, and fmap where the function was. You could even maintain the n-arity of map by doing

  (def map (xs arg . args)
    (withs (args (cons arg args)
            f    (last args)
            lsts (firstn (- (len args) 1) args))
      #;(...)))
To be clear, I think that providing both is better, or at least providing flip by default in arc.arc. But it's worth thinking about.

-----

1 point by Jesin 5881 days ago | link

Several issues with this. First, this becomes impossible:

  (def dotprod (xs ys)
    (apply + (map * xs ys)))
How would you propose doing that? By messing with the fundamental semantics of Lisp lists?

Then there's the problem of thinking about it. Which is easier, thinking of

  (map a b)
as "map a onto b", or as "map b onto a" (or "map a under b")?

Prettiness of indentation in a few special cases of map, ability to map onto multiple lists, and consistency of language semantics. Pick two.

-----

1 point by absz 5881 days ago | link

That's not quite true, though. As I pointed out below (http://arclanguage.org/item?id=4704), you can set up map so that you could write

  (def dotprod (xs ys)
    (apply + (map xs ys *)))
Now, I'd rather have map stay as it is, as I've said. One should have fmap for "flipped map" which does this, and it may or may not belong in the core. After all, (map f xs) does read better than (map xs f). But even so, it is doable.

EDIT: The more I look at this, the more wrong it looks. I'm even more inclined against this; even if we have it, it shouldn't be called something so similar to map.

-----

1 point by tokipin 5881 days ago | link

for multiple lists, the lists would all go first and the function last. i don't see why this is a problem

as for what makes sense, that gets into SVO vs VOS etc and i don't think it is significant in programming. i know i don't read programs as if they were english. the most important portion of the expression is the word "map." once i see that word, i want to know what the function is and what the list(s) are because that's all that is relevant. conceptually, it doesn't matter what order they're in, and neither order makes more "sense." not for me at least

[edit]i've used map in both orders (in functional languages that don't have map, i make my own,) and again, i haven't noticed a difference besides one of readability

-----

1 point by absz 5881 days ago | link

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

-----

2 points by cooldude127 5882 days ago | link

i think this makes map confusing when applying to multiple lists. where does the function argument go? at the end? after the first list? it just makes more sense if the lists are last.

-----