question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dot notation for dealing with maps

See original GitHub issue

I would like to see the ability to use dot notation to call variables from a map. While the new map-get() in 3.3+ is awesome however it can get to be unreadable real quick.

For example say that you had this map.

$buttons: (
 color: #fff,
 primary: (
  background: #fff,
  color: #000
  ...
 ),
 secondary: (
  background: #000,
  ...
 )
);

Currently to call that you can use map-get() like this.

.button{
 background: map-get($buttons, background);
 color: map-get($buttons, color);
 &-primary{
  background: map-get(map-get($buttons, primary), background);
  &:hover{
   background: lighten(map-get(map-get($buttons, primary), background), 10%);
  }
 }
...
}

This example isn’t to bad but when you start calling functions inside of functions inside of functions inside of functions things get out of hand quickly and become really hard to follow. Especially if you are adding and subtracting things to get elements to line up correctly

top: em(nth-val(map-get($messaging, padding), 1) - map-get($messaging, line-height), map-get($messaging, font-size)) * -1;

If we had dot notation for calling variables It would make things like this more readable.

top: em(nth-val($messaging.padding, 1) - $messaging.line-height, $messaging.font-size) * -1

This comes in to play even more if you have a map that is more than 2 levels deep.

$example-map: (
  foo: (
    baz: (
      qux: (
        quux: (
          garply: (
            waldo: "Hello world"
          )
        )
      )
    ) 
  ) 
);

Currently to get waldo in the $example-map you would do this

content: map-get(map-get(map-get(map-get(map-get(map-get($example-map, foo), baz), qux), quux), garply), waldo);

Using dot notation you could get it like this.

content: $example-map.foo.baz.qux.quux.garply.waldo;

I realize this example is a little excessive but I hope it shows how useful it could be.

You shouldn’t have to worry about old sass code not working with this change since you can’t currently have a . in a variable name.

You would still be able to use interpolation with the variables.

@mixin button-theme($theme){
 background: $buttons.#{$theme}.background;
 color: $buttons.#{$theme}.color;
}

You should still have the ability to combine the dot notation with the other awesome map functions.

$map-to-merge: (
 fred: "",
 plugh: "",
 xyzzy: "",
 thud: ""
);
 $temp: map-merge($example-map.foo.bar, $map-to-merge)

Issue Analytics

  • State:closed
  • Created 9 years ago
  • Reactions:1
  • Comments:37

github_iconTop GitHub Comments

11reactions
tjbentoncommented, Jul 13, 2015

Sorry in advance for the lengthy post, but someone need to do it. This is a reply to #1739 (comment). @HugoGiraudel, @davidkpiano, @ArmorDarks I understand that sass is a function based language and that’s part of the reason I love using it because it makes doing difficult tasks easy; however using functions alone to handle maps and lists can make what should be simple tasks seem overly complicated. In my issue #1349 I suggested using dot/bracket notation for variables only, and the way I suggested it to be implemented would not interfere with the sass ecosystem and instead be a huge improvement to the best preprocessor available. The longer implementing dot/bracket notation is ignored, the bigger the issue becomes. There are several notable libraries out there that have implemented their own way of handling maps, including some that @HugoGiraudel has written. The mere fact that there have been so many libraries attempting to solve this problem should be a clear indicator that it’s an issue worth standardizing. It also means everyone learns how to handle large sass maps in a different way, and therefore every time someone moves jobs they have to change the way they deal with sass maps and that means relearning something that should have been implemented from the start. These various implementations make it harder to understand what’s going on in people’s code, and it make it even more difficult for new people who are trying to learn sass to understand what they are seeing.

@davidkpiano Is 100% correct map-get($map, foo.bar) is a very bad idea. That’s why I didn’t suggest using dot notation in this way. However, if you implement dot notation similarly to how other notable languages use dot notation, it would solve these ridiculous problems everyone runs in to with maps.

In js and other languages that use dot/bracket notation you can’t add . into the variable/key name for good reason, and it’s pretty much the same point that @davidkpiano is trying to make. The difference being sass does follow suit and doesn’t accept . in a variable/key name. If you tried to set a map key to be foo.bar you will get an error. The reason it returns an error is because sass interprets it as a css selector and since a css selector can’t be followed by : an error is returned. Variables can’t have a dot in them either for the same reason. So how does his example work? Well a sass key can be a type of string, list, number, color, or map. This means we can set a key to anything we want, and that allows us to do things like he’s showing in his example. The reason his example works is because nth(&, 1) is a type of list. So when map-get($map, nth(&, 1)) is called it’s looking for key that matches the list of nth(&, 1). If you absolutely have to have a . in your key name then you can do the same thing other languages do; just add quotes around the declaration to note that it’s a string "foo.bar"— and that makes the key valid.

Here’s @davidkpiano example explained in depth, and the live example is on sassmiester if you would like to see the output

// `.qux` was added to make it clear that `&` is a list of lists of strings
.foo.bar, .qux{
  $selector: &;
  selector: $selector; // => ((foo.bar,), (.qux,))
  type-of-selector: type-of($selector); // => list

  $first-selector: nth($selector, 1);
  first-selector: $first-selector; // => (foo.bar,)
  first-selector_type: type-of($first-selector); // => list

  $map: (
   $first-selector: "baz",
   foo: (
    bar: "qux"
   ),
   ("foo",): (
    bar: "waldo"
   ),
   nth($first-selector, 1): "garply" // => ".foo.bar": garply
  );


  $keys: map-keys($map);
  keys: $keys; // => ((.foo.bar .qux,), foo, .foo.bar)

  key-1: nth($keys, 1); // => (.foo.bar, .qux)
  key-1_type: type-of(nth($keys, 1)); // => list

  key-2: nth($keys, 2); // => "foo"
  key-2_type: type-of(nth($keys, 2)); // => string

  key-3: nth($keys, 3); // => ".foo.bar"
  key-3_type: type-of(nth($keys, 3)); // => string

  // getting the value of `$first-selector` in map
  get-test_selector: map-get($map, $first-selector); // => "baz"

  // getting the value of key that is a list
  get-test_list: inspect(map-get($map, ("foo",))); // => (bar: waldo)

  // getting a the value of a key when it contains a `.` in the name
  get-test_string-with-dot: map-get($map, ".foo.bar"); // => "garply"
  get-test_unquote-string-with-dot: map-get($map, unquote(".foo.bar")); // => "garply" still because even if you unquote a `string` it's still a type of `string`
}
These use cases can still use dot/bracket notation without any special functions being added
get-test_selector: $map[nth(&, 1)]; // => "baz"
get-test_list: $map[("foo",)] // => (bar: waldo)
get-test_string-with-dot: $map[".foo.bar"]; // => "garply"
get-test_unquote-string-with-dot: $map[unquote(".foo.bar")]; // => "garply" still because even if you unquote a `string` it's still a type of `string`

Implementing dot/bracket notation is something that needs to happen. It would make sass tremendously more readable, and it would make it easier for beginners with background knowledge of js and other languages that use dot/bracket notation easier to dive in to sass because they don’t have to re-learn the same concept of structures/maps in a different way than they already know. The reason why I started using sass is because of it’s extensibility, simplicity, and ease of understanding. The way maps(aka structures) are currently implemented makes it more complex than it needs to be. While I understand sass maps and use them on a daily basis, trying to teach a developer how to use sass map functions is harder than it needs to be and every time each developer has said "Well why didn’t they just use dot notation? That would be so much easier!”. I believe if there was a poll on implementing dot/bracket notation in sass and it was asked on all the major sass resource sites and email lists the majority of sass developers would agree that sass needs dot/bracket notation.

Examples and use cases

Here are some more examples and use cases, how they are written with the current implementation of maps, and how they could be written using dot/bracket notation:

Setting a key value using other values from the same map.

Here is a live example that I had to use on codepen.

// base map for this example
$promo-bg: (
  arrow-width: 2.5em, // width of the arrow on the background
  colors: (#178399, #853381, #438d1d, #d44029, #ec6c21),
  offset: 10%, // offset from the container
  rotate: 3deg, // how much to rotate the background
  height: 80%, // height of the background
  width: 400% // total width of the background
);
With map functions
$promo-bg: map-merge($promo-bg, (
  origin-fix: ((100% + map-get($promo-bg, offset)) / (map-get($promo-bg, width) / (100% + map-get($promo-bg, offset)))) / 2
));
With dot/bracket notation
$promo-bg.orgin-fix: ((100% + $promo-bg.offset) / ($promo-bg.width / (100% + $promo-bg.offset))) / 2;

Calling a dynamic key

This would be a use case for mixins and functions to call a key based off an argument that was passed:

// base map for this example
$user-messaging: (
 success: (
   base: #3c763d,
   light: #dff0D8
  ),
  info: (
   base: #31708f,
   light: #d9edf7
  ),
  warning: (
   base: #8a6d3b,
   light: #fcf8e3
  ),
  danger: (
   base: #a94442,
   light: #f2dede
  )
);
With map functions
@function user-messaging($type, $shade: base){
 @return map-get(map-get($user-messaging, $type), $shade);
}
With dot/bracket notation
@function user-messaging($type, $shade: base){
 @return $user-messaging[$type][$shade];
}

Getting a variable from a deeply nested map

This example is taken from the original post

With map functions
map-get(map-get(map-get(map-get(map-get(map-get($example-map, foo), baz), qux), quux), garply), waldo);
With dot/bracket notation
$example-map.foo.baz.qux.quux.garply.waldo

Setting a value on a deeply nested key

This example uses the same map from the original post.

With functions

The only real possibility to do this is to use a third party functions, like the one that @HugoGiraudel wrote and shared on sitepoint, or the one that was written by @Snugug for Sass-maps, or another that was written by @cahnory for PlastiCSS, or the one that was written by @pascalduez gist, these are just a few I came across in a single post by @chriscoyier on csstricks and there are so many more out there, this was just from the first link in google.

// I can't give a single example because it depends on which third party function you decide use
With dot/bracket notation
// setting a value
$example-map.foo.baz.qux.quux.garply.waldo: "found him";

// replacing an existing map with a new map
$example-map.foo.baz.qux.quux.garply: (
 frank: ""
);

// merging a map
$map-to-merge: (
 fred: "",
 plugh: "",
 xyzzy: "",
 thud: ""
);

$example-map.foo.bar: map-merge($example-map.foo.bar, $map-to-merge);

Using map-remove with nested map

With map functions
// I can't give a single example because it depends on which third party function you decide use
With dot/bracket notation
// I would like to see it like this because it still makes sense, it's less that you have to type out which can reduce complexity
$example-map: map-remove($example-map.foo.baz.qux.quux, garply);

// I can understand if it was implemented like this because it follows the same way that sass currently works
$example-map.foo.baz.qux.quux: map-remove($example-map.foo.baz.qux.quux, garply);

Using map-keys, and map-values on a nested map

With map functions
map-keys(map-get(map-get(map-get(map-get(map-get($example-map, foo), baz), qux), quux), garply));
map-values(map-get(map-get(map-get(map-get(map-get($example-map, foo), baz), qux), quux), garply));
With dot/bracket notation
map-keys($example-map.foo.baz.qux.quux.garply);
map-values($example-map.foo.baz.qux.quux.garply);

Using map-has-key on a nested map

With map functions
map-has-key(map-get(map-get(map-get(map-get(map-get($example-map, foo), baz), qux), quux), garply));
With dot/bracket notation
map-has-key($example-map.foo.baz.qux.quux, garply);

// in addition you should be able to do this to check if it's undefined
@if $example-map.foo.baz.qux.quux.garply{
 // execute this if `$example-map.foo.baz.qux.quux.garply` is not undefined and is true
}

Bracket notation for sass lists

The bracket notation should also be applied to sass lists because it’s still dealing with the same concept of getting and setting things in variables.

Getting/setting an item in a list(aka array)

$list: ("baz", "qux", "quux", "garply");
With list functions
// getting
nth($list, 1); // => "baz"
nth($list, length($list)); // => "baz"

// setting
$list: set-nth($list, 1, "waldo"); // => ("waldo", "qux", "quux", "garply")
With bracket notation

Since sass isn’t zero based like js the first item in the array would still be accesed through 1 and the last element would be accesed by the length of the list

// getting
$list[1]; // => "baz"
$list[length($list)]; // => "garply"

// setting
$list[1]: "waldo"; // => ("waldo", "qux", "quux", "garply")

Getting/setting an item in a list(aka array) that is nested in a map

$nested-list: (
 foo: (
  foobar: (21, 7, 4),
  bar: (
   baz: ("qux", "quux", "garply")
  )
 )
);
With map/list functions
// getting
nth(map-get(map-get(map-get($nested-list, foo), bar), baz), 2) // => "quux"

// setting
// depends on the third party function you're using, because it's not easily possible with native functions
With dot/bracket notation
// getting
$nested-list.foo.bar.baz[2] // => "quux"

// setting
$nested-list.foo.bar.baz[2]: "waldo";
// => The full map after the item has been set
// $nested-list: (
//  foo: (
//   foobar: (21, 7, 4),
//   bar: (
//    baz: ("qux", "waldo", "garply")
//   )
//  )
// );

Dot notation is a very simple concept to understand regardless of your coding background, as it is already very common in several other languages. This would make it easer for other developers to learn sass, and utilize the power of sass maps. Implement it correctly would reduce the amount of wasted time by developers creating their own implementations of the sass map functions, as well as definitively solve a problem that others have already tried to hack their way around.

7reactions
KittyGiraudelcommented, May 4, 2016

@mistersender Of course that is none of my business, but if your choice of CSS preprocessor relies on the ability to use dot-notation, I am afraid you missed the point.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Dot Distribution vs Graduated Symbols vs Proportional ...
While dot distribution maps use multiple dots to represent quantity, graduated and proportional symbol maps adjust the size of a single dot ......
Read more >
How do I use dot notation to map into a value? - Stack Overflow
With JavaScript, you need to use backtick quotes if you want a variable to be interpolated into a string. await t.set(itemDoc, { [`${order....
Read more >
Working with Maps - Oracle Help Center
To reference the value of a map entry, use dot notation like this, using the may key value as if it were a...
Read more >
Property accessors - JavaScript - MDN Web Docs - Mozilla
Property accessors provide access to an object's properties by using the dot notation or the bracket notation.
Read more >
Dot notation vs Bracket notation. Which one should I use
Both notations can access object properties. But the question is often which one should I use 🤔. Wonder no more, just follow Airbnb's...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found