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

Support nested maps with "map-get" #1739

Open
blackfalcon opened this Issue Jun 4, 2015 · 29 comments

Comments

Projects
None yet
9 participants
@blackfalcon

blackfalcon commented Jun 4, 2015

@lunelson project sass-list-maps has support for nested list maps, using the map-get-z function.
https://github.com/lunelson/sass-list-maps

The following example says it all:

$list-map-z: (
  alpha (
    beta (
      gamma 3
    )
  )
);

.demo {
  out: map-get-z($list-map-z, alpha); // -> ( beta ( gamma 3 ) )
  out: map-get-z($list-map-z, alpha, beta); // -> ( gamma 3 )
  out: map-get-z($list-map-z, alpha, beta, gamma); // -> 3
}

FACT: Up to a point, even when using maps extended naming conventions come into play. Using map-get-z we can actually use structure to categorize versus naming conventions. I vote for structure every time.

$carousel: (
  color (
    header (
      border gray,
    )
  )
);

$side-nav: (
  bg gray,
  color (
    hover (
      search yellow,
      home red,
      filter blue
    )
  )
);

block {
  color: map-get-z($side-nav, color, hover, filter);
}

block {
  border-color: map-get-z($carousel, color, header, border);
}

I don't mind using the plug-in, but I think that this is a feature that Sass should support natively.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jun 5, 2015

Contributor

I'm not opposed to allowing map-get() to take multiple keys to do nested retrieval.

Off-topic, but:

$side-nav: (
  bg gray,
  color (
    hover (
      search yellow,
      home red,
      filter blue
    )
  )
);

This isn't a map, it's a (nested) list of pairs. It works with map functions for compatibility reasons, but (in addition to being harder to read than a real map) it's likely to be much less efficient.

Contributor

nex3 commented Jun 5, 2015

I'm not opposed to allowing map-get() to take multiple keys to do nested retrieval.

Off-topic, but:

$side-nav: (
  bg gray,
  color (
    hover (
      search yellow,
      home red,
      filter blue
    )
  )
);

This isn't a map, it's a (nested) list of pairs. It works with map functions for compatibility reasons, but (in addition to being harder to read than a real map) it's likely to be much less efficient.

@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Jun 5, 2015

@nex3 yes @blackfalcon is referring to my non-native library which used lists of pairs; I have a newer version which implements the same argument patterns for map-get-z() and map-merge-z() with native maps—https://github.com/lunelson/sass-maps-plus —and so what do you think of comma-separated keys syntax for map-merge? Notably, in my implementation, providing a non-map as the final argument causes causes it to work like a map-set(), and empty keys/nests are created as necessary. So you could write merges in one of two ways:

$map: ();
$map: merge($map, alpha, beta, (gamma: 5));

// n.b. equivalent to above
// $map: merge($map, alpha, beta, gamma, 5); 

// result
// $map: (alpha: (beta: (gamma: 5)));

lunelson commented Jun 5, 2015

@nex3 yes @blackfalcon is referring to my non-native library which used lists of pairs; I have a newer version which implements the same argument patterns for map-get-z() and map-merge-z() with native maps—https://github.com/lunelson/sass-maps-plus —and so what do you think of comma-separated keys syntax for map-merge? Notably, in my implementation, providing a non-map as the final argument causes causes it to work like a map-set(), and empty keys/nests are created as necessary. So you could write merges in one of two ways:

$map: ();
$map: merge($map, alpha, beta, (gamma: 5));

// n.b. equivalent to above
// $map: merge($map, alpha, beta, gamma, 5); 

// result
// $map: (alpha: (beta: (gamma: 5)));
@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jun 5, 2015

Contributor

@lunelson 👍

Contributor

nex3 commented Jun 5, 2015

@lunelson 👍

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jun 6, 2015

Sorry for offtopic again, but I always wondered why Sass didn't use more understandable arrays ([]) instead of lists and objects (dicts) ({}) instead of maps, it would be much easier to understand $side-nav example:

$side-nav: [
  bg gray,
  color: {
    hover: {
      search: yellow,
      home: red,
      filter: blue
    }
  }
];

Regarding $map: merge($map, alpha, beta, (gamma: 5)); — I have a feeling that it will result in issue, which natively has JavaScript, where you're unable to use keywords to define function's arguments.

In other words, such approach would be more flexible, since dosn't require strict order of arguments:

// bulletproof way, since even if to API of `merge` will be added new methods,
// you would have more chances to be on safe side
$map: merge($map, $path: (alpha, beta, gamma), $set: true);

// or when you want shorter way and don't care much about any changes
$map: merge($myMap, (alpha, beta, gamma), true);

In other words, $path should contain list of keys for lookup.

I'm not enough experienced programmer to say, but as far as I know most languages trying to avoid relying on providing of endless arguments unless it's 100% guaranteed that there won't be no new methods for function in future. And, since nobody can foresee future...

Anyway, I definitely vote for native support of map-get-z-like function 👍

ArmorDarks commented Jun 6, 2015

Sorry for offtopic again, but I always wondered why Sass didn't use more understandable arrays ([]) instead of lists and objects (dicts) ({}) instead of maps, it would be much easier to understand $side-nav example:

$side-nav: [
  bg gray,
  color: {
    hover: {
      search: yellow,
      home: red,
      filter: blue
    }
  }
];

Regarding $map: merge($map, alpha, beta, (gamma: 5)); — I have a feeling that it will result in issue, which natively has JavaScript, where you're unable to use keywords to define function's arguments.

In other words, such approach would be more flexible, since dosn't require strict order of arguments:

// bulletproof way, since even if to API of `merge` will be added new methods,
// you would have more chances to be on safe side
$map: merge($map, $path: (alpha, beta, gamma), $set: true);

// or when you want shorter way and don't care much about any changes
$map: merge($myMap, (alpha, beta, gamma), true);

In other words, $path should contain list of keys for lookup.

I'm not enough experienced programmer to say, but as far as I know most languages trying to avoid relying on providing of endless arguments unless it's 100% guaranteed that there won't be no new methods for function in future. And, since nobody can foresee future...

Anyway, I definitely vote for native support of map-get-z-like function 👍

@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Jun 8, 2015

@ArmorDarks The upside of this (so-called) map-get-z() argument syntax is that it can also be a simple map-get(): that is, it works as either nested or non-nested depending on the number of arguments provided. So this proposal is not really for a new map-get-z() function but rather to consider an extension to the utility of the existing map-get(): nothing breaks, nothing changes; but if you provide more arguments they would be assumed to be keys at which to address nested maps. The idea was also, simply, that comma-separating keys reads in a similar way, to the way you address objects in javascript or other languages, i.e. dot notation.

WRT your other point, I'm sure this is because curly braces and square braces have very specific meanings in CSS and they wish to avoid confusion. Sass has always restricted itself to syntax styles that seem like part of CSS, and round braces are the only kind seen in vanilla CSS rules (e.g. css transforms etc.)

lunelson commented Jun 8, 2015

@ArmorDarks The upside of this (so-called) map-get-z() argument syntax is that it can also be a simple map-get(): that is, it works as either nested or non-nested depending on the number of arguments provided. So this proposal is not really for a new map-get-z() function but rather to consider an extension to the utility of the existing map-get(): nothing breaks, nothing changes; but if you provide more arguments they would be assumed to be keys at which to address nested maps. The idea was also, simply, that comma-separating keys reads in a similar way, to the way you address objects in javascript or other languages, i.e. dot notation.

WRT your other point, I'm sure this is because curly braces and square braces have very specific meanings in CSS and they wish to avoid confusion. Sass has always restricted itself to syntax styles that seem like part of CSS, and round braces are the only kind seen in vanilla CSS rules (e.g. css transforms etc.)

@HugoGiraudel

This comment has been minimized.

Show comment
Hide comment
@HugoGiraudel

HugoGiraudel Jun 9, 2015

Contributor

I'm not opposed to allowing map-get() to take multiple keys to do nested retrieval.

Now that's good news!

Too, I don't see a good reason to introduce map-get-z(..) (or map-deep-get(..), no matter ho you all it). Although, it would be nice to see map-get(..) taking an unknown number of keys, like map-remove(..) does already. This won't break any current code and would implement a new handy feature without introducing yet another function.

Contributor

HugoGiraudel commented Jun 9, 2015

I'm not opposed to allowing map-get() to take multiple keys to do nested retrieval.

Now that's good news!

Too, I don't see a good reason to introduce map-get-z(..) (or map-deep-get(..), no matter ho you all it). Although, it would be nice to see map-get(..) taking an unknown number of keys, like map-remove(..) does already. This won't break any current code and would implement a new handy feature without introducing yet another function.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Jun 9, 2015

Member

If we're going to have a deep version of map-get I'd like to see a corresponding version of map-merge.

Member

chriseppstein commented Jun 9, 2015

If we're going to have a deep version of map-get I'd like to see a corresponding version of map-merge.

@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Jun 11, 2015

@chriseppstein what's your opinion on my comment above?

lunelson commented Jun 11, 2015

@chriseppstein what's your opinion on my comment above?

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jun 11, 2015

Maybe issue #1349 should be reviewed once more.

In case it will be implemented, you won't need endless arguments for map-get(..), since you would be able to use dot notation in plain form, or inside map-get(..) like map-get($myMap, alpha.beta.gamma).

However, ability to provide map-get(..) with endless arguments (no matter how exactly it will be implemented, via endless arguments, or $path argument with list of keys) would be useful anyway, since there maybe more complex situations, when one of keys would be presented as another map. As far as I know, usually dot and brackets notations doesn't allow to traverse through such complex maps.

ArmorDarks commented Jun 11, 2015

Maybe issue #1349 should be reviewed once more.

In case it will be implemented, you won't need endless arguments for map-get(..), since you would be able to use dot notation in plain form, or inside map-get(..) like map-get($myMap, alpha.beta.gamma).

However, ability to provide map-get(..) with endless arguments (no matter how exactly it will be implemented, via endless arguments, or $path argument with list of keys) would be useful anyway, since there maybe more complex situations, when one of keys would be presented as another map. As far as I know, usually dot and brackets notations doesn't allow to traverse through such complex maps.

@HugoGiraudel

This comment has been minimized.

Show comment
Hide comment
@HugoGiraudel

HugoGiraudel Jun 11, 2015

Contributor

As much as I'm used to it, I'm pretty sure dot notation would be a bad idea in the current Sass echosystem.

Contributor

HugoGiraudel commented Jun 11, 2015

As much as I'm used to it, I'm pretty sure dot notation would be a bad idea in the current Sass echosystem.

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jun 11, 2015

@HugoGiraudel Can you share with us your opinion why is it so, in mentioned issue above? Thanks in advance

ArmorDarks commented Jun 11, 2015

@HugoGiraudel Can you share with us your opinion why is it so, in mentioned issue above? Thanks in advance

@davidkpiano

This comment has been minimized.

Show comment
Hide comment
@davidkpiano

davidkpiano Jun 11, 2015

@ArmorDarks contrived example: (cc @HugoGiraudel)

This is legal:

foo.bar {
  $map: (
    nth(&, 1): 'baz',
    foo: (bar: 'qux')
  );
  $value: map-get($map, nth(&, 1)); // essentially map-get($map, foo.bar);

  test: inspect($value); // => 'baz'
}

However, map-get($map, foo.bar) is (currently) not legal syntax. If it were, which value should it refer to? 'baz' or 'qux'? There is ambiguity, so dot notation would be a bad idea because of the nature of CSS selectors and the necessity for Sass to play nicely with them.

davidkpiano commented Jun 11, 2015

@ArmorDarks contrived example: (cc @HugoGiraudel)

This is legal:

foo.bar {
  $map: (
    nth(&, 1): 'baz',
    foo: (bar: 'qux')
  );
  $value: map-get($map, nth(&, 1)); // essentially map-get($map, foo.bar);

  test: inspect($value); // => 'baz'
}

However, map-get($map, foo.bar) is (currently) not legal syntax. If it were, which value should it refer to? 'baz' or 'qux'? There is ambiguity, so dot notation would be a bad idea because of the nature of CSS selectors and the necessity for Sass to play nicely with them.

@HugoGiraudel

This comment has been minimized.

Show comment
Hide comment
@HugoGiraudel

HugoGiraudel Jun 11, 2015

Contributor

@ArmorDarks The short answer is that CSS (and thus Sass) is function based. If I am not mistaken, this is the main reason why Sass decided to go with functional notation instead of dot notation. Introducing the dot would not only be the source of hard to track bugs like the one explained by @davidkpiano, but also disrupt the global harmony.

Contributor

HugoGiraudel commented Jun 11, 2015

@ArmorDarks The short answer is that CSS (and thus Sass) is function based. If I am not mistaken, this is the main reason why Sass decided to go with functional notation instead of dot notation. Introducing the dot would not only be the source of hard to track bugs like the one explained by @davidkpiano, but also disrupt the global harmony.

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jun 11, 2015

@HugoGiraudel hm, can't agree with it. JavaScript is object-based, with hard rely on functions too. So, it shouldn't use dot notation too, since two ways to do same thing would make things disharmoned? Same for Python, etc.

Not to mention, that JS has dot notation, brackets notation and special functions for getting values from ES6 maps (not objects). And you can write your own function to get values from objects too. Which makes about 4 ways to do, from first glance, same thing, but each approach was introduced to target specific purposes.

@davidkpiano Thanks for your input. Though, I think that discussion should go into mentioned issue above, since we're diving into offtopic deeper.

Since you replied here, I'm probably forced to reply here too.

Regarding your example — there is no error here. With map-get($map, nth(&, 1)); you get baz, because you're refering to function which is key. With map-get($map, foo.bar) you will get qux. since you're traversing through existing keys, which are foo and bar.

map-get($map, foo.bar) is totally == to map-get($map, foo, bar) if endless arguments for map-get() would be implemented ever. So far I don't see issues with it. Maybe I'm missing something.


Once again, I'm sorry for post, that raised another discussion here, which in fact should be discussed here #1349.

Let's back to our current issue. I doubt that someone will argue with fact, that all would only benefit from having map-get() and map-merge() which will accept endless number of arguments.

To summarize, I think that there were only few little things that were missed:

Notably, in my implementation, providing a non-map as the final argument causes causes it to work like a map-set(), and empty keys/nests are created as necessary. So you could write merges in one of two ways:

by @lunelson #1739 (comment)

And my proposal to use $path argument with list inside instead of endless arguments #1739 (comment)

Regarding my proposal — it seems that Sass community more appreciate endless arguments approach, and using of $path argument with list seems to be a bit against current approaches of Sass.

ArmorDarks commented Jun 11, 2015

@HugoGiraudel hm, can't agree with it. JavaScript is object-based, with hard rely on functions too. So, it shouldn't use dot notation too, since two ways to do same thing would make things disharmoned? Same for Python, etc.

Not to mention, that JS has dot notation, brackets notation and special functions for getting values from ES6 maps (not objects). And you can write your own function to get values from objects too. Which makes about 4 ways to do, from first glance, same thing, but each approach was introduced to target specific purposes.

@davidkpiano Thanks for your input. Though, I think that discussion should go into mentioned issue above, since we're diving into offtopic deeper.

Since you replied here, I'm probably forced to reply here too.

Regarding your example — there is no error here. With map-get($map, nth(&, 1)); you get baz, because you're refering to function which is key. With map-get($map, foo.bar) you will get qux. since you're traversing through existing keys, which are foo and bar.

map-get($map, foo.bar) is totally == to map-get($map, foo, bar) if endless arguments for map-get() would be implemented ever. So far I don't see issues with it. Maybe I'm missing something.


Once again, I'm sorry for post, that raised another discussion here, which in fact should be discussed here #1349.

Let's back to our current issue. I doubt that someone will argue with fact, that all would only benefit from having map-get() and map-merge() which will accept endless number of arguments.

To summarize, I think that there were only few little things that were missed:

Notably, in my implementation, providing a non-map as the final argument causes causes it to work like a map-set(), and empty keys/nests are created as necessary. So you could write merges in one of two ways:

by @lunelson #1739 (comment)

And my proposal to use $path argument with list inside instead of endless arguments #1739 (comment)

Regarding my proposal — it seems that Sass community more appreciate endless arguments approach, and using of $path argument with list seems to be a bit against current approaches of Sass.

@tjbenton

This comment has been minimized.

Show comment
Hide comment
@tjbenton

tjbenton Jul 13, 2015

@ArmorDarks I agree with you about list/arrays and maps/objects in your comment. I would have liked to see these features implemented in the same way that js and other major languages. It would make complex things easier to read because you can actually see the difference between getting a value from a map or list and calling a function with that value, and see the difference between () for order of operations, and () that are just getting a value to use inside of the order of operations. I think that the () are over used in sass and at times has cause a lot of confusion when developing functions/mixins and using lists inside of maps that call other functions that have (). I've see )))))) at the end of a line and I have no idea what ) is doing what without taking a few min to break it down into multiple lines. When it could be simplified to be ))) if dot/bracket notation was implemented.

In less than 3 seconds can you determine this ) is tied to in the example below? Order of operations, map a function, part of abs function?

$promo-bg: map-merge($promo-bg, (
 origin-fix: ((100% + abs(map-get(map-get($promo-bg, foo), offset))) / (map-get(map-get($promo-bg, foo), width) / (100% + abs(map-get(map-get($promo-bg, foo), offset))))) / 2
----------------------------------------------------------------------------------------------------------------------------------------------------------------------^
));

Here's the same test with dot notation.

$promo-bg.foo.orgin-fix: ((100% + abs($promo-bg.foo.offset)) / ($promo-bg.foo.width / (100% + abs($promo-bg.foo.offset)))) / 2;
----------------------------------------------------------------------------------------------------------------------^

tjbenton commented Jul 13, 2015

@ArmorDarks I agree with you about list/arrays and maps/objects in your comment. I would have liked to see these features implemented in the same way that js and other major languages. It would make complex things easier to read because you can actually see the difference between getting a value from a map or list and calling a function with that value, and see the difference between () for order of operations, and () that are just getting a value to use inside of the order of operations. I think that the () are over used in sass and at times has cause a lot of confusion when developing functions/mixins and using lists inside of maps that call other functions that have (). I've see )))))) at the end of a line and I have no idea what ) is doing what without taking a few min to break it down into multiple lines. When it could be simplified to be ))) if dot/bracket notation was implemented.

In less than 3 seconds can you determine this ) is tied to in the example below? Order of operations, map a function, part of abs function?

$promo-bg: map-merge($promo-bg, (
 origin-fix: ((100% + abs(map-get(map-get($promo-bg, foo), offset))) / (map-get(map-get($promo-bg, foo), width) / (100% + abs(map-get(map-get($promo-bg, foo), offset))))) / 2
----------------------------------------------------------------------------------------------------------------------------------------------------------------------^
));

Here's the same test with dot notation.

$promo-bg.foo.orgin-fix: ((100% + abs($promo-bg.foo.offset)) / ($promo-bg.foo.width / (100% + abs($promo-bg.foo.offset)))) / 2;
----------------------------------------------------------------------------------------------------------------------^
@davidkpiano

This comment has been minimized.

Show comment
Hide comment
@davidkpiano

davidkpiano Jul 13, 2015

Keep in mind, for lexical parsing reasons (and potential complexity), that this is legal:

$foo\.bar: 'baz';

.foo {
  test: $foo\.bar; // => "baz"
}

davidkpiano commented Jul 13, 2015

Keep in mind, for lexical parsing reasons (and potential complexity), that this is legal:

$foo\.bar: 'baz';

.foo {
  test: $foo\.bar; // => "baz"
}
@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jul 13, 2015

@tjbenton thanks for the support. I totally agree with you.

More of it, I was always confused, why Sass preferred to use () for maps. Some people pointing, that it's because CSS rules already using {}.

But am I the only person, that seeing, that in fact CSS's rules are objects itself, but only with ; instead of , as delimiter? You can even drop last ; in ruleset, just like in JavaScript's object, or Python's dict.

For the sake of illustration, object-like ruleset in CSS:

cssObject {
  width: 1px;
  color: #000;
}

and JavaScript object:

var cssObject = {
  width: 1px,
  color: #000
}

I hope everybody can see how they are close. Why would we need to invent new syntax with (), which already were used by functions and lists (arrays), while we already had objects in fact?

We can open issue about {} instead for objects and [] for maps, but I don't believe that it will be changed ever... though, I must admit, it's possible to add {} and {} as optional notation (it won't break anything so far), so that well-pointed overused () could be used by legacy projects and would be dropped in future, after few major versions.

ArmorDarks commented Jul 13, 2015

@tjbenton thanks for the support. I totally agree with you.

More of it, I was always confused, why Sass preferred to use () for maps. Some people pointing, that it's because CSS rules already using {}.

But am I the only person, that seeing, that in fact CSS's rules are objects itself, but only with ; instead of , as delimiter? You can even drop last ; in ruleset, just like in JavaScript's object, or Python's dict.

For the sake of illustration, object-like ruleset in CSS:

cssObject {
  width: 1px;
  color: #000;
}

and JavaScript object:

var cssObject = {
  width: 1px,
  color: #000
}

I hope everybody can see how they are close. Why would we need to invent new syntax with (), which already were used by functions and lists (arrays), while we already had objects in fact?

We can open issue about {} instead for objects and [] for maps, but I don't believe that it will be changed ever... though, I must admit, it's possible to add {} and {} as optional notation (it won't break anything so far), so that well-pointed overused () could be used by legacy projects and would be dropped in future, after few major versions.

@davidkpiano

This comment has been minimized.

Show comment
Hide comment
@davidkpiano

davidkpiano Jul 13, 2015

@ArmorDarks Keep in mind that CSS rulesets are not exactly objects like JavaScript objects. This is legal in CSS but not legal as an object/dict/map:

.ruleset {
  font-size: 24px;
  font-size: 2rem; // illegal in JS
}

davidkpiano commented Jul 13, 2015

@ArmorDarks Keep in mind that CSS rulesets are not exactly objects like JavaScript objects. This is legal in CSS but not legal as an object/dict/map:

.ruleset {
  font-size: 24px;
  font-size: 2rem; // illegal in JS
}
@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Jul 13, 2015

I would imagine the reason that both dot-notation and curly-brace-object-notation have not been used for Sass syntax, despite their well known meaning in other languages like JS, is that Sass' syntax has always been modelled to look like CSS and to not conflict with existing CSS syntactical patterns. Thus curly braces are exclusively used to delimit CSS declarations and dots—adjacent to strings—are exclusively used to indicate class selectors. This might seem unnecessary or perverse but in my observation keeping the language clear and accessible to new users has always been a high priority for Sass, and I believe it's correct

lunelson commented Jul 13, 2015

I would imagine the reason that both dot-notation and curly-brace-object-notation have not been used for Sass syntax, despite their well known meaning in other languages like JS, is that Sass' syntax has always been modelled to look like CSS and to not conflict with existing CSS syntactical patterns. Thus curly braces are exclusively used to delimit CSS declarations and dots—adjacent to strings—are exclusively used to indicate class selectors. This might seem unnecessary or perverse but in my observation keeping the language clear and accessible to new users has always been a high priority for Sass, and I believe it's correct

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jul 13, 2015

@davidkpiano I'm talking not about literal similarity, but about same conceptual approach. CSS's rulestes are objects by it's nature, but with slightly another syntax

@lunelson I can't agree with that. Introducing of () for list and in same time for maps (for two different entities) nor make syntax looks like CSS (because in CSS () used only for inner values, like url(), not for associated values), nor doesn't sound like solution to that problem, not to mention that it doesn't increase accessibility for new users, because () for arrays and objects are very uncommon in other languages. On top of it, mentioned by @tjbenton issue with (((((()))))) in functions with maps, lists etc.

ArmorDarks commented Jul 13, 2015

@davidkpiano I'm talking not about literal similarity, but about same conceptual approach. CSS's rulestes are objects by it's nature, but with slightly another syntax

@lunelson I can't agree with that. Introducing of () for list and in same time for maps (for two different entities) nor make syntax looks like CSS (because in CSS () used only for inner values, like url(), not for associated values), nor doesn't sound like solution to that problem, not to mention that it doesn't increase accessibility for new users, because () for arrays and objects are very uncommon in other languages. On top of it, mentioned by @tjbenton issue with (((((()))))) in functions with maps, lists etc.

@tjbenton

This comment has been minimized.

Show comment
Hide comment
@tjbenton

tjbenton Jul 13, 2015

While @davidkpiano is correct

CSS rule sets are not exactly objects like JavaScript objects
But it looks very similar, and when new people learn css with a JS background it's how they think of the styles because they think of a rule set as an Object. If you run typeof document.styleSheets[0].cssRules[0].style in a browser you can see that JS stores the rules in an Object. So it's not a far leap to think of them in that way.

@lunelson Adding dot notation and curly-brace object notation wouldn't conflict with existing CSS syntactical patterns. It would actually be more inline with CSS patterns that what is currently implemented.

Dots

  • CSS uses dots to increase the specificity level
  • Dot notation uses dots to take you deeper into a map

Brackets

  • CSS uses brackets to allow you to select attributes and that increases the specificity level
  • Bracket notation is used to select items via a variable, string, or because the key is a list or map, and that takes you one level deeper in the map.

Rule set/structure

  • A CSS rule set is comprised of a selector followed by a declaration block of properties and values; the rule set applies the declarations listed in the declaration block to all elements matched by the selector.
    scss .foo{ border: { top: { right: { radius: 6px; }; }; }; }
    • A curly brace structure is comprised of a variable(key, or in an array) followed by a declaration block of keys and values; the structure is associated with the variable/array/key
$foo: {
 bar: [
  {
   name: Lorem,
   theme: a,
  },
  {
   name: Lorem,
   theme: c,
  }
],
 baz: {
  qux: "garply"
 }
};

Using [] for lists instead () is the only one that doesn't follow css. But you can't honestly, tell me that using () follows css either. You can try argue that linear-gradient(#fff 0%, #000 100%) is the reason that () were used and why you can have space and comma delimited lists. But I don't think that's a valid argument because in CSS linear-gradient is a CSS function and the lists inside of it are actually an arguments list. You could also argue that the reason why you can have both spaces and commas are because that is how CSS selectors work, but css selectors don't have () in them unless it includes a pseudo selector like :matches(section, article, aside, nav) which is acting like a function.

As for the parsing of it, I don't think that it would be that difficult because the only dot/bracket notation has to be tied to a variable. When the variable is defined it has to be followed by a : and you can't end a css selector with a : because it's not a valid selector. Every SASS variable has to start with a $. Given that information it's easy to tell the difference between a sass variable and a css selector. Even when used in interpolation it would still be easy to see the difference

$foo: {
 bar: ".baz.qux.quux"
}; // structure
.foo{}; // css selector
#{$foo.bar}[class*="awesome"]{}

While I would love to see sass change to use {}, and [] I don't see sass changing it or even having it be an optional syntax while the other is phased out, and if by some miracle it was approved, It would take a while for it into production.

tjbenton commented Jul 13, 2015

While @davidkpiano is correct

CSS rule sets are not exactly objects like JavaScript objects
But it looks very similar, and when new people learn css with a JS background it's how they think of the styles because they think of a rule set as an Object. If you run typeof document.styleSheets[0].cssRules[0].style in a browser you can see that JS stores the rules in an Object. So it's not a far leap to think of them in that way.

@lunelson Adding dot notation and curly-brace object notation wouldn't conflict with existing CSS syntactical patterns. It would actually be more inline with CSS patterns that what is currently implemented.

Dots

  • CSS uses dots to increase the specificity level
  • Dot notation uses dots to take you deeper into a map

Brackets

  • CSS uses brackets to allow you to select attributes and that increases the specificity level
  • Bracket notation is used to select items via a variable, string, or because the key is a list or map, and that takes you one level deeper in the map.

Rule set/structure

  • A CSS rule set is comprised of a selector followed by a declaration block of properties and values; the rule set applies the declarations listed in the declaration block to all elements matched by the selector.
    scss .foo{ border: { top: { right: { radius: 6px; }; }; }; }
    • A curly brace structure is comprised of a variable(key, or in an array) followed by a declaration block of keys and values; the structure is associated with the variable/array/key
$foo: {
 bar: [
  {
   name: Lorem,
   theme: a,
  },
  {
   name: Lorem,
   theme: c,
  }
],
 baz: {
  qux: "garply"
 }
};

Using [] for lists instead () is the only one that doesn't follow css. But you can't honestly, tell me that using () follows css either. You can try argue that linear-gradient(#fff 0%, #000 100%) is the reason that () were used and why you can have space and comma delimited lists. But I don't think that's a valid argument because in CSS linear-gradient is a CSS function and the lists inside of it are actually an arguments list. You could also argue that the reason why you can have both spaces and commas are because that is how CSS selectors work, but css selectors don't have () in them unless it includes a pseudo selector like :matches(section, article, aside, nav) which is acting like a function.

As for the parsing of it, I don't think that it would be that difficult because the only dot/bracket notation has to be tied to a variable. When the variable is defined it has to be followed by a : and you can't end a css selector with a : because it's not a valid selector. Every SASS variable has to start with a $. Given that information it's easy to tell the difference between a sass variable and a css selector. Even when used in interpolation it would still be easy to see the difference

$foo: {
 bar: ".baz.qux.quux"
}; // structure
.foo{}; // css selector
#{$foo.bar}[class*="awesome"]{}

While I would love to see sass change to use {}, and [] I don't see sass changing it or even having it be an optional syntax while the other is phased out, and if by some miracle it was approved, It would take a while for it into production.

@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Jul 14, 2015

I think we have to create new issue about it, since particularly that issue related to map-get function improvement.

ArmorDarks commented Jul 14, 2015

I think we have to create new issue about it, since particularly that issue related to map-get function improvement.

@tjbenton

This comment has been minimized.

Show comment
Hide comment
@tjbenton

tjbenton Jul 16, 2015

@chriseppstein Reply to (comment) about map merge. I can see this working in a couple of different ways. Going under the assumption map-get is implemented like map-get($map, foo, bar, baz).

  1. a. Update map-merge to work like this awesome extend function written by @HugoGiraudel. This way map-merge is strickly for merging multiple maps, and it adds a recursive functionality that wouldn't break the current implementation.
    b. Add a new map-set function that would work exactly like map-get except the last argument would be the value that's set.

    $map: (
     foo: (
      bar: (
       baz: (
        qux: "qux"
       )
      )
     )
    );
    
    // setting a value would replace the current value of `qux` to be "waldo"
    $map: map-set($map, foo, bar, baz, qux, "waldo");
    
    // Combining `map-set` with `map-merge`
    $map: map-set($map, foo, bar, baz, map-merge(map-get($map, foo, bar, baz), (
           quux: true,
           garply: false
          )));

    The down side would be having to combine it with map-get for combining deep maps. But the upside is that you leave the functionality of map-merge to just merge maps which is what I would think it would do based off the name. For more examples on how map-set would be useful see below.

  2. a. Update map-merge to work just like map-get except the last argument would be the map to merge

    $map: map-merge($map, foo, bar, baz, (fox: true));

    It would return the map of the first argument's map in the arguments list, not the 2nd to last(aka: baz) arguments map.
    b. Add a new map-extend extend function. It solves a lot of merging issues you may run into when merging multiple maps, and it's recursive.

  3. A quazi hybrid of map-get, and @HugoGiraudel extend function

    // @arg {list} $get - A list where the last item is the map to merge
    // @arg {ArgList} $maps... - A list of maps you want to merge
    // @arg {bool} $recursive [false] - recursive mode
    // map-merge($get, $maps.../*, $recursive */);
    $map-4: map-merge($map foo bar baz, $map-1, $map-2, $map-3, true);

All 3 map-merge options are backwards compatable to sass 3.3+. So they're all viable options. Personally I like #1 the most because it would match map-get closer for consistency, and because I'm not a huge fan of space delimited lists used in #2. But I can see how #2 is appealing because no new functions have to be added to deal with map merging and it still adds all the functionality of the extend.

No matter which is direction is chosen for dealing with merging maps, I would still like to see a map-set function added that's specifically used for setting values in a map. I think it would be useful because sometimes you need to update a value of a map and at the same time remove some keys from a map, and sometimes you just want to set a single key in a map; and, in my opinion, writing map-merge($map, (foo: "bar")) to set a single value in a map is weird.

If map-set was implemented then there wouldn't be a need to update map-remove which would be good because since map-remove already uses a an args list and dot notation isn't gonna happen, the first argument would have to be a space delimited list to get to deep values, and then it would have to return the map of first item in the list. Here are some examples more examples of map-set combined with map-remove, and how you would do it without map-set.

$map: (
 foo: (
  bar: (
   baz: "baz",
   qux: "qux",
   quux: "quux",
   corge: "corge",
   grault: "grault"
  )
 ),
);
  • Note: map-remove would still return a new map of the map that it was passed.
  • Note: these examples are using map-merge suggestion #1 from above

Examples: With map-set

Remove values of a nested map, and not update values.
// 80 characters, 6 parentheses
$map: map-set($map, foo, bar, map-remove(map-get($map, foo, bar), qux, quux));
Removing qux, and quux, and setting baz to "waldo"
// 101 characters, 8 parentheses
$map: map-set($map, foo, bar, map-set(map-remove(map-get($map, foo, bar), qux, quux), baz, "waldo"));
Setting a value of a nested map
// 44 characters, 2 parentheses
$map: map-set($map, foo, bar, baz, "waldo");

Examples: The same examples without map-set

Remove values of a nested map, and not update values.
// 84 characters, 8 parentheses
$map: map-merge($map, foo, (bar: map-remove(map-get($map, foo, bar), qux, quux)));
Removing qux, and quux, and setting baz to "waldo"
// 109 characters, 12 parentheses
$map: map-merge($map, foo, (bar: map-merge(map-remove(map-get($map, foo, bar), qux, quux), (baz: "waldo"))));
Setting a value of a nested map
// 48 characters, 4 parentheses
$map: map-merge($map, foo, bar, (baz: "waldo"));

In all cases map-set uses less characters and parentheses than map-merge, and less which increases readability. As mentioned before this would leave the functionality of map-merge to be just for merging maps which in my opinion would very good thing. Hope this leads to a solution for this issue.

tjbenton commented Jul 16, 2015

@chriseppstein Reply to (comment) about map merge. I can see this working in a couple of different ways. Going under the assumption map-get is implemented like map-get($map, foo, bar, baz).

  1. a. Update map-merge to work like this awesome extend function written by @HugoGiraudel. This way map-merge is strickly for merging multiple maps, and it adds a recursive functionality that wouldn't break the current implementation.
    b. Add a new map-set function that would work exactly like map-get except the last argument would be the value that's set.

    $map: (
     foo: (
      bar: (
       baz: (
        qux: "qux"
       )
      )
     )
    );
    
    // setting a value would replace the current value of `qux` to be "waldo"
    $map: map-set($map, foo, bar, baz, qux, "waldo");
    
    // Combining `map-set` with `map-merge`
    $map: map-set($map, foo, bar, baz, map-merge(map-get($map, foo, bar, baz), (
           quux: true,
           garply: false
          )));

    The down side would be having to combine it with map-get for combining deep maps. But the upside is that you leave the functionality of map-merge to just merge maps which is what I would think it would do based off the name. For more examples on how map-set would be useful see below.

  2. a. Update map-merge to work just like map-get except the last argument would be the map to merge

    $map: map-merge($map, foo, bar, baz, (fox: true));

    It would return the map of the first argument's map in the arguments list, not the 2nd to last(aka: baz) arguments map.
    b. Add a new map-extend extend function. It solves a lot of merging issues you may run into when merging multiple maps, and it's recursive.

  3. A quazi hybrid of map-get, and @HugoGiraudel extend function

    // @arg {list} $get - A list where the last item is the map to merge
    // @arg {ArgList} $maps... - A list of maps you want to merge
    // @arg {bool} $recursive [false] - recursive mode
    // map-merge($get, $maps.../*, $recursive */);
    $map-4: map-merge($map foo bar baz, $map-1, $map-2, $map-3, true);

All 3 map-merge options are backwards compatable to sass 3.3+. So they're all viable options. Personally I like #1 the most because it would match map-get closer for consistency, and because I'm not a huge fan of space delimited lists used in #2. But I can see how #2 is appealing because no new functions have to be added to deal with map merging and it still adds all the functionality of the extend.

No matter which is direction is chosen for dealing with merging maps, I would still like to see a map-set function added that's specifically used for setting values in a map. I think it would be useful because sometimes you need to update a value of a map and at the same time remove some keys from a map, and sometimes you just want to set a single key in a map; and, in my opinion, writing map-merge($map, (foo: "bar")) to set a single value in a map is weird.

If map-set was implemented then there wouldn't be a need to update map-remove which would be good because since map-remove already uses a an args list and dot notation isn't gonna happen, the first argument would have to be a space delimited list to get to deep values, and then it would have to return the map of first item in the list. Here are some examples more examples of map-set combined with map-remove, and how you would do it without map-set.

$map: (
 foo: (
  bar: (
   baz: "baz",
   qux: "qux",
   quux: "quux",
   corge: "corge",
   grault: "grault"
  )
 ),
);
  • Note: map-remove would still return a new map of the map that it was passed.
  • Note: these examples are using map-merge suggestion #1 from above

Examples: With map-set

Remove values of a nested map, and not update values.
// 80 characters, 6 parentheses
$map: map-set($map, foo, bar, map-remove(map-get($map, foo, bar), qux, quux));
Removing qux, and quux, and setting baz to "waldo"
// 101 characters, 8 parentheses
$map: map-set($map, foo, bar, map-set(map-remove(map-get($map, foo, bar), qux, quux), baz, "waldo"));
Setting a value of a nested map
// 44 characters, 2 parentheses
$map: map-set($map, foo, bar, baz, "waldo");

Examples: The same examples without map-set

Remove values of a nested map, and not update values.
// 84 characters, 8 parentheses
$map: map-merge($map, foo, (bar: map-remove(map-get($map, foo, bar), qux, quux)));
Removing qux, and quux, and setting baz to "waldo"
// 109 characters, 12 parentheses
$map: map-merge($map, foo, (bar: map-merge(map-remove(map-get($map, foo, bar), qux, quux), (baz: "waldo"))));
Setting a value of a nested map
// 48 characters, 4 parentheses
$map: map-merge($map, foo, bar, (baz: "waldo"));

In all cases map-set uses less characters and parentheses than map-merge, and less which increases readability. As mentioned before this would leave the functionality of map-merge to be just for merging maps which in my opinion would very good thing. Hope this leads to a solution for this issue.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jul 17, 2015

Contributor

This thread is getting extremely off-topic, so I'm locking it. In summary, we will add support for multiple keys to map-get and map-merge with the following semantics:

$map: (a: (b: (c: d)))
map-get($map, a, b, c) // => d
map-merge($map, a, b, c, x) // => (a: (b: (c: x)))
Contributor

nex3 commented Jul 17, 2015

This thread is getting extremely off-topic, so I'm locking it. In summary, we will add support for multiple keys to map-get and map-merge with the following semantics:

$map: (a: (b: (c: d)))
map-get($map, a, b, c) // => d
map-merge($map, a, b, c, x) // => (a: (b: (c: x)))

@sass sass locked and limited conversation to collaborators Jul 17, 2015

@nex3 nex3 added the Help Wanted label Aug 21, 2015

@nex3 nex3 changed the title from Support map-get-z to Support nested maps with "map-get" Aug 29, 2015

This was referenced Nov 13, 2015

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Nov 13, 2015

Contributor

Looking at this again, I've changed my mind about map-merge(). Giving it the same semantics as the semantic map-set function produces confusion. I'd like to preserve the invariant that map-merge($map1, ($k: $v)) is always the same as map-merge($map1, $k, $v), but that's not backwards-compatible when $v is itself a map.

Given that, I think I agree with @tjbenton's option 1: require map-merge's final parameter to always be a map, and add a new map-set function for setting individual values.

Contributor

nex3 commented Nov 13, 2015

Looking at this again, I've changed my mind about map-merge(). Giving it the same semantics as the semantic map-set function produces confusion. I'd like to preserve the invariant that map-merge($map1, ($k: $v)) is always the same as map-merge($map1, $k, $v), but that's not backwards-compatible when $v is itself a map.

Given that, I think I agree with @tjbenton's option 1: require map-merge's final parameter to always be a map, and add a new map-set function for setting individual values.

@sass sass unlocked this conversation Nov 13, 2015

@hcatlin

This comment has been minimized.

Show comment
Hide comment
@hcatlin

hcatlin Nov 13, 2015

Member

@nex3 I'm not entirely sure what you are proposing.

You want map-merge($map1, $keys, $map2)? Or, just leave map-merge as it is with two maps? Would $map2 support being a nested map?

Can you provide some examples of what you are proposing?

Member

hcatlin commented Nov 13, 2015

@nex3 I'm not entirely sure what you are proposing.

You want map-merge($map1, $keys, $map2)? Or, just leave map-merge as it is with two maps? Would $map2 support being a nested map?

Can you provide some examples of what you are proposing?

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Nov 13, 2015

Contributor

You want map-merge($map1, $keys, $map2)?

I would write this as map-merge($map1, $keys..., $map2), but yeah, basically.

Would $map2 support being a nested map?

No. This would be a backwards-incompatible change.

Can you provide some examples of what you are proposing?

map-merge((a: b), (c: d)) //=> (a: b, c: d)
map-merge((a: (b: c)), a, (d: e)) //=> (a: (b: c, d: e))
map-merge((a: (b: c)), (a: (d: e))) //=> (a: (d: e))
Contributor

nex3 commented Nov 13, 2015

You want map-merge($map1, $keys, $map2)?

I would write this as map-merge($map1, $keys..., $map2), but yeah, basically.

Would $map2 support being a nested map?

No. This would be a backwards-incompatible change.

Can you provide some examples of what you are proposing?

map-merge((a: b), (c: d)) //=> (a: b, c: d)
map-merge((a: (b: c)), a, (d: e)) //=> (a: (b: c, d: e))
map-merge((a: (b: c)), (a: (d: e))) //=> (a: (d: e))
@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Nov 15, 2015

I agree that separate map-set and map-merge functions, with a restriction on arguments of the latter will make them more semantically clear 👍

lunelson commented Nov 15, 2015

I agree that separate map-set and map-merge functions, with a restriction on arguments of the latter will make them more semantically clear 👍

hcatlin added a commit to hcatlin/sass that referenced this issue Feb 1, 2016

Implemented nested map functions #1739
Now, map-merge, map-get, and map-has-key all accept various forms of nested Map logic as per @nex3's specifications in #1739, with the addition of a nested compatible map-has-key.
@ArmorDarks

This comment has been minimized.

Show comment
Hide comment
@ArmorDarks

ArmorDarks Feb 5, 2018

I wonder, is that feature has been put on hold?

ArmorDarks commented Feb 5, 2018

I wonder, is that feature has been put on hold?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment