Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the index iteration variable to @each #996

Open
lolmaus opened this issue Nov 4, 2013 · 13 comments
Open

Add the index iteration variable to @each #996

lolmaus opened this issue Nov 4, 2013 · 13 comments
Labels
enhancement New feature or request under consideration The Sass team is debating whether to do this

Comments

@lolmaus
Copy link

lolmaus commented Nov 4, 2013

Whenever i do:

@for $i from 1 through length($colors)
  $color: nth($colors, $i)
  .column:nth-child(#{$i})
    background-color: $color

i feel like a kitten dies somewhere in misery and loneliness. And i find myself doing that in almost any project, so that's quite a lot of kittens. :(

I wish i could do this instead:

@each $color, $index in $colors
  .column:nth-child(#{$index})
    background-color: $color

Please spare the kittens. It's our war, not theirs.

@KittyGiraudel
Copy link

Would be nice, indeed.

@nex3
Copy link
Contributor

nex3 commented Dec 6, 2013

The suggested syntax steps on the toes of the map/pair iteration in 3.3, so that's not workable.

I'm not entirely opposed to this if a very elegant syntax can be devised, but I'm skeptical of that. The syntax needs to be easy to understand when reading the code for someone with only passing familiarity with Sass, and easy to remember when writing Sass. It should also not give users the impression that constructs like @each can have a million different configurations that do different things. I think this is a tall order, but you're welcome to try.

In the meantime, I don't think it's too burdensome to use a @for loop here. Many languages more powerful than Sass make do with that.

@Snugug
Copy link

Snugug commented Dec 6, 2013

@nex3 what about something like the following:

@each $color in $colors with index $i

@lolmaus
Copy link
Author

lolmaus commented Dec 7, 2013

Or something like

@each $color as $index in $colors

@nex3
Copy link
Contributor

nex3 commented Dec 14, 2013

@each $color in $colors with index $i

This is okay, although it relies on users knowing the term "index". I'm also not a big fan of requiring two words; I think that contributes to the "word salad" feeling about @each.

@each $color as $index in $colors

as isn't descriptive enough.

@Snugug
Copy link

Snugug commented Dec 14, 2013

IMO requiring people to know what "index" means when dealing with iterators is OK, but maybe "counter" would work too (although I bet most people who would use this feature would prefer "index")

How about something like this:

@each $color in $colors, index $i (or w/o the ,)

  • or -

@each $color in $colors, counter $i

@nex3
Copy link
Contributor

nex3 commented Dec 16, 2013

IMO requiring people to know what "index" means when dealing with iterators is OK, but maybe "counter" would work too (although I bet most people who would use this feature would prefer "index")

It's fine to require knowledge for people who are writing the code, but a major part of the weight of a new syntax construct is the added difficulty of reading the code, especially for inexperienced users or users who aren't well-versed in programming. Manually tracking a counter may be slightly more work when writing the loop in the first place, but it's easy for even an inexperienced reader to figure out what's going on when reading it.

Many widely-used languages have no more concise way of doing this than Sass does right now, and they do just fine. This is why I'm being so stringent in my requirements for this syntax: I think it adds relatively little value and expressiveness, so it needs to be extremely comprehensible to all users in order to mitigate its added weight.

@Snugug
Copy link

Snugug commented Dec 17, 2013

Yah, I understand that's a concern. I agree that it's entirely possible for this functionality to be done in round-about ways currently and that many languages don't have an each with iterator, so it should be OK for Sass.

I think the key distinction, though, between Sass and other languages in terms of an each with iterator is the medium in which we're working. In Sass, because we're outputting to CSS, there is a need for both strings and numbers coming from an each, especially CSS's nth. Frameworks in Sass often rely upon lots of lists (and now maps), and those often need indexes when looping. I'm sure, of course, you understand all of this, so I'm not going to continue.

Instead, let's discuss syntax. What are your current objections to counter $i or index $i?

@Snugug
Copy link

Snugug commented Dec 17, 2013

One place where I think this can make it much easier to work is an each with key/value. Take the following (which is what we need to do now):

$zoo: (
  "salamander": (
    background: green,
    border-color: red
  ),
  "chicken": (
    background: yellow,
    border-color: red
  ),
  "elephant": (
    background: purple,
    border-color: blue
  )
);

$zoo-length: length($zoo);

@for $i from 1 through $zoo-length {
  $animal: nth($zoo, $i);
  $name: nth($animal, 1);
  $properties: nth($animal, 2);
  li:nth-of-type(#{$i}) {
    content: $name;
    @each $key, $value in $properties {
      #{$key}: $value;
    }
  }
}

This could be rewritten in the following way (which IMO is much more legible). While a user may not know what an index is immediately, what the code is trying to do is fairly easy to comprehend (much more so than the original)

$zoo: (
  "salamander": (
    background: green,
    border-color: red
  ),
  "chicken": (
    background: yellow,
    border-color: red
  ),
  "elephant": (
    background: purple,
    border-color: blue
  )
);

@each $animal, $properties in $zoo, counter $i {
  li:nth-of-type(#{$i}) {
    content: $name;
    @each $property, feature in $properties {
      #{$key}: $value;
    }
  }
}

@lolmaus
Copy link
Author

lolmaus commented Jan 10, 2014

I got curious how Python folk resolve this issue, as Python doesn't boast to be syntactically sweet as well as Sass.

It turned out, they use an enumerate helper function for this which does a simple job: turns an array into a dictionary.

So i made a similar function for Sass.

Usage:

$list: (dog, cat, bird, cow)

@each $index, $value in enumerate($list)
  li:nth-child( #{ length($list) }n + #{ $index } ):before
    content: quote($value)

Source:

@function enumerate($list)
  $map: ()

  @for $index from 1 through  length($list)
    $value: nth($list, $index)
    $map: map-merge($map, ($index: $value))

  @return $map

Seems to be working fine and handy to use.

Demo: http://sassmeister.com/gist/8346425

It doesn't cover @Snugug's nifty use case though.

@chriseppstein
Copy link

The above code is how I'd like to address this. It can be made more efficient by adding a Sass function like range($n). So that range(10) => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10). Then enumerate can become:

@function enumerate($list-or-map) {
  @return zip($list-or-map, range(length($list-or-map));
}

@yairEO

This comment has been minimized.

@bstrilziw

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request under consideration The Sass team is debating whether to do this
Projects
None yet
Development

No branches or pull requests

7 participants