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 a map data type to Sass #642

Closed
ByScripts opened this Issue Jan 25, 2013 · 14 comments

Comments

Projects
None yet
5 participants
@ByScripts

Edited by @nex3 to change the title.

I don't find tickets about collections.

I read some closed tickets abouts namespaces and scopes, but they don't seems to describe what I'm thinking about.

So, instead of a description, here is an example :

// variables.scss
$categories: ($name: 'first', $c: #000, $bg: #111, $mt: 10px, $h: 128px, $w: 100px),
             ($name: 'second', $c: #222, $bg: #333, $mt: 14px, $h: 124px, $w: 90px),
             ($name: 'third', $c: #444, $bg: #555, $mt: 12px, $h: 126px, $w: 104px);

// menu.scss
@mixin build-menu($name, $c, $w) {
    body.#{$name} {
        nav {
            width: $w;
            a {
                color: $c;
                &.#{$name} { color: darken($c, 15%); }
            }
        }
    }
}

// info.scss
@mixin build-info-block($name, $c, $bg, $mt) {
    body.#{$name} {
        div.info {
            margin-top: $mt;
            background-color: $bg;
            h1 { color: $c; }
        }
    }
}

// global.scss
@import "variables";
@import "menu";
@import "info";

@each $category in $categories {
    @include build-menu($category...);
    @include build-info-block($category...);
}

This would output :

body.first nav { width: 100px; }
body.first nav a { color: #000; }
body.first nav a.first { color: #000; }
body.first div.info { margin-top: 10px; background-color: #111; }
body.first div.info h1 { color: #000; }

body.second nav { width: 90px; }
body.second nav a { color: #222; }
body.second nav a.second { color: #1a1a1a; }
body.second div.info { margin-top: 14px; background-color: #333; }
body.second div.info h1 { color: #222; }

body.third nav { width: 104px; }
body.third nav a { color: #444; }
body.third nav a.third { color: #3a3a3a; }
body.third div.info { margin-top: 12px; background-color: #555; }
body.third div.info h1 { color: #444; }

More than collections, for this to work, we should be able to pass more arguments than mixins wants, as long as they are named.

In short, this should work without throwing error (sass should just ignore named arguments which are not in its signature):

@mixin mymixin($foo, $bar) {}
@include mymixin($whatever: 10px, $foo: #fff, $other: bold, $bar: 2em);

Sorry for being so long.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Feb 2, 2013

Contributor

We're planning on adding support for maps of some sort, and they will interact with variable-length argument lists. However, I don't think we'll support passing unknown arguments to a mixin. This makes it too hard for users to track down typos in argument names.

Contributor

nex3 commented Feb 2, 2013

We're planning on adding support for maps of some sort, and they will interact with variable-length argument lists. However, I don't think we'll support passing unknown arguments to a mixin. This makes it too hard for users to track down typos in argument names.

@ByScripts

This comment has been minimized.

Show comment
Hide comment
@ByScripts

ByScripts Feb 2, 2013

Good to hear for maps :)

On unknown arguments, I understand.

Upon reflection, I'll just have to add unused arguments to the mixin signature... Even if I don't use them in the mixin body.

Thank you.

Good to hear for maps :)

On unknown arguments, I understand.

Upon reflection, I'll just have to add unused arguments to the mixin signature... Even if I don't use them in the mixin body.

Thank you.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Mar 5, 2013

Contributor

I want to start working on this in earnest soon, so I'm going to start brainstorming syntax and function names. I think the two biggest things we need to nail down is the map-creation syntax and the element access syntax. There are some other ancillary things, but creating and accessing maps are by far the most common use cases.

@chriseppstein, I'm looking for your input on all this in particular.

Creating Maps

There are two main paths to explore here. Either we have built-in syntax for creating maps, or we farm it out to a function. The former is terser and closer to what scripting languages do, but there's also intrinsic conceptual load to introducing syntax, as well as a greater risk of running into syntax conflicts with CSS.

New Syntax

What would this look like? Would it have explicit delimiters as in scripting languages, or would it follow Sass lists and have no delimiters with the option to use parentheses? The lack of delimiters on Sass lists has proven challenging. However, much of this challenge comes from treating individual values as single-item lists, which is not an issue for maps, and delimiters eat up syntax that might be used by CSS.

Would keys be prefixed with $ or would they be bare strings? $ fits the syntax of named arguments, the closest analogue to maps that we have at the moment, but it adds another context where $foo doesn't refer to the variable $foo. It also makes it impossible to use variables as keys, and it's unclear what would happen if you tried to programmatically access the key. It seems clear that bare-string keys are preferable.

What about non-string keys? Would we allow 1: value? #fff: value?

One approach here would be to match the syntax of media queries. This would mean no explicit delimiters and bare-string keys. It would allow @media #{$map} to work.

Function

The most obvious way to make a function to create a map would be to use the existing keyword argument syntax, as in create-map($key1: value1, $key2: value2). However, this has the same issues as using $ in a new syntax, most notably making it impossible to create a map with programmatically-generated keys. This seems like an unacceptable sacrifice.

The other option is to use our existing data structure, lists, to do something like create-map(key1 value1, key2 value2). This doesn't read as well as a custom syntax would, but it's possible that the benefits of using a function outweigh that downside.

Accessing Maps

Once again we're choosing between adding new syntax and adding a function. The clear choice for the access syntax would be $map[key]. There are many downsides to this, though. In addition to all the normal issues with adding new syntax, this would be confusingly different than the use of a function to access lists (nth).

The question then becomes what name to give the access function. get is terse but extremely general and likely to come into conflict. map-get follows the naming convention used by the new string functions, but still suffers from the relative vagueness of "get".

Contributor

nex3 commented Mar 5, 2013

I want to start working on this in earnest soon, so I'm going to start brainstorming syntax and function names. I think the two biggest things we need to nail down is the map-creation syntax and the element access syntax. There are some other ancillary things, but creating and accessing maps are by far the most common use cases.

@chriseppstein, I'm looking for your input on all this in particular.

Creating Maps

There are two main paths to explore here. Either we have built-in syntax for creating maps, or we farm it out to a function. The former is terser and closer to what scripting languages do, but there's also intrinsic conceptual load to introducing syntax, as well as a greater risk of running into syntax conflicts with CSS.

New Syntax

What would this look like? Would it have explicit delimiters as in scripting languages, or would it follow Sass lists and have no delimiters with the option to use parentheses? The lack of delimiters on Sass lists has proven challenging. However, much of this challenge comes from treating individual values as single-item lists, which is not an issue for maps, and delimiters eat up syntax that might be used by CSS.

Would keys be prefixed with $ or would they be bare strings? $ fits the syntax of named arguments, the closest analogue to maps that we have at the moment, but it adds another context where $foo doesn't refer to the variable $foo. It also makes it impossible to use variables as keys, and it's unclear what would happen if you tried to programmatically access the key. It seems clear that bare-string keys are preferable.

What about non-string keys? Would we allow 1: value? #fff: value?

One approach here would be to match the syntax of media queries. This would mean no explicit delimiters and bare-string keys. It would allow @media #{$map} to work.

Function

The most obvious way to make a function to create a map would be to use the existing keyword argument syntax, as in create-map($key1: value1, $key2: value2). However, this has the same issues as using $ in a new syntax, most notably making it impossible to create a map with programmatically-generated keys. This seems like an unacceptable sacrifice.

The other option is to use our existing data structure, lists, to do something like create-map(key1 value1, key2 value2). This doesn't read as well as a custom syntax would, but it's possible that the benefits of using a function outweigh that downside.

Accessing Maps

Once again we're choosing between adding new syntax and adding a function. The clear choice for the access syntax would be $map[key]. There are many downsides to this, though. In addition to all the normal issues with adding new syntax, this would be confusingly different than the use of a function to access lists (nth).

The question then becomes what name to give the access function. get is terse but extremely general and likely to come into conflict. map-get follows the naming convention used by the new string functions, but still suffers from the relative vagueness of "get".

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Mar 5, 2013

Member

Another thing to note: Maps need to be ordered. This is because a map may be used to hold a sequence of property name/value pairs and the order of properties is very important.

Creation Syntax

I really think that a function for creating a map is not the way, the syntax is just too awkward. So what should the syntax be?

My initial reaction is that what css other than selectors and maps? So why not simply let curly braces, when used in a script context be a map?

$foo: {
  1: 'something';
  'foo': 1px;
  whatever: null;
  nth($colors, 1): nth($colors, 2);
}

I prefer semi-colons here for the similarity to rulesets and to avoid requiring parenthesis when creating maps of values to comma-delimited lists which I think will be fairly common.

Unfortunately this creates a syntactic ambiguity that cannot be resolved with a finite look-ahead because of nested properties if we have accessor syntax instead function accessors. Example:

div {
  background: {
    color: blue;
  }[color]
}

But I think that using functions for map access and manipulation is better and more consistent than creating new syntax.

The other strangeness of this syntax is that is would leave us with semicolons in the middle of a script expression which may cause a readers eye to be mislead about where the end of the expression is. However, I think this will actually be quite rare in practice, most maps will be created separately from where they are used.

Iteration

I would also amend the @each directive to be map-aware:

div.stuff {
  @each $key, $value in $map {
    #{$key}: $value;
  }
}

Library

I agree that get is too general, but I don't think map-get is confusing, so let's go with that and see what people think.

  • map-get($map, $key) - returns the value stored for the key.
  • map-set($map, $key1, $value1, $key2, $value2, ...) - returns a new map having the keys set to their corresponding values. if a key already exists, the order of that key will stay the same, but the value it is mapped to will change.
  • length($map) returns a number that is the number of pairs in the map.
  • map-merge($map1, $map2) - returns a new map that is the result of successively adding the keys from $map2 to $map1 by calling map-set. while this can be done in pure-sass, having it built-in ruby reduces the number of temporary map objects (and hence GC).
  • map-keys($map) - returns a comma delimited list of keys in the order they were first set.
  • map-values($map) - returns a comma delimited list of values in the order they were first set.
  • map-apply($map, $function-name) - returns a new map where the values have been transformed according to the values returned by the function given by $function-name (which should accept two arguments -- the key and the current value). While this could be done in pure-sass, having it provided internally will avoid the creation of many intermediate map objects because of the immutability of map objects.
  • nth($map, $index) returns a comma delimited list of the key and value stored at the map's index.
Member

chriseppstein commented Mar 5, 2013

Another thing to note: Maps need to be ordered. This is because a map may be used to hold a sequence of property name/value pairs and the order of properties is very important.

Creation Syntax

I really think that a function for creating a map is not the way, the syntax is just too awkward. So what should the syntax be?

My initial reaction is that what css other than selectors and maps? So why not simply let curly braces, when used in a script context be a map?

$foo: {
  1: 'something';
  'foo': 1px;
  whatever: null;
  nth($colors, 1): nth($colors, 2);
}

I prefer semi-colons here for the similarity to rulesets and to avoid requiring parenthesis when creating maps of values to comma-delimited lists which I think will be fairly common.

Unfortunately this creates a syntactic ambiguity that cannot be resolved with a finite look-ahead because of nested properties if we have accessor syntax instead function accessors. Example:

div {
  background: {
    color: blue;
  }[color]
}

But I think that using functions for map access and manipulation is better and more consistent than creating new syntax.

The other strangeness of this syntax is that is would leave us with semicolons in the middle of a script expression which may cause a readers eye to be mislead about where the end of the expression is. However, I think this will actually be quite rare in practice, most maps will be created separately from where they are used.

Iteration

I would also amend the @each directive to be map-aware:

div.stuff {
  @each $key, $value in $map {
    #{$key}: $value;
  }
}

Library

I agree that get is too general, but I don't think map-get is confusing, so let's go with that and see what people think.

  • map-get($map, $key) - returns the value stored for the key.
  • map-set($map, $key1, $value1, $key2, $value2, ...) - returns a new map having the keys set to their corresponding values. if a key already exists, the order of that key will stay the same, but the value it is mapped to will change.
  • length($map) returns a number that is the number of pairs in the map.
  • map-merge($map1, $map2) - returns a new map that is the result of successively adding the keys from $map2 to $map1 by calling map-set. while this can be done in pure-sass, having it built-in ruby reduces the number of temporary map objects (and hence GC).
  • map-keys($map) - returns a comma delimited list of keys in the order they were first set.
  • map-values($map) - returns a comma delimited list of values in the order they were first set.
  • map-apply($map, $function-name) - returns a new map where the values have been transformed according to the values returned by the function given by $function-name (which should accept two arguments -- the key and the current value). While this could be done in pure-sass, having it provided internally will avoid the creation of many intermediate map objects because of the immutability of map objects.
  • nth($map, $index) returns a comma delimited list of the key and value stored at the map's index.
@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Mar 6, 2013

Contributor

Creation Syntax

There are a lot of look-ahead issues that arise from using {}. Not just nested properties, but anywhere that a curly bracket immediately follows script... in @each, for example. I'd rather avoid these issues.

Iteration

I would also amend the @each directive to be map-aware:

div.stuff {
  @each $key, $value in $map {
    #{$key}: $value;
  }
}

I agree with this, although I think @each $key: $value in $map looks a little better.

  • map-set($map, $key1, $value1, $key2, $value2, ...) - returns a new map having the keys set to their corresponding values. if a key already exists, the order of that key will stay the same, but the value it is mapped to will change.

I'm not a big fan of this function. I think we should use map-merge as the canonical way of adding values to a map. These functions are pretty much identical in semantics, except that map-set is less general. The name also incorrectly implies that it mutates the map.

  • map-apply($map, $function-name) - returns a new map where the values have been transformed according to the values returned by the function given by $function-name (which should accept two arguments -- the key and the current value). While this could be done in pure-sass, having it provided internally will avoid the creation of many intermediate map objects because of the immutability of map objects.

This is a pretty wacky function. What are the use cases for it?

  • length($map) returns a number that is the number of pairs in the map.
  • nth($map, $index) returns a comma delimited list of the key and value stored at the map's index.

If we implement these, we should have a general policy of list functions treating maps as lists of pairs. This isn't the craziest thing (and makes () work nicely as both the empty list and the empty map), but we run into plenty of problems with single-element lists as it is. Do you have ideas for dealing with that?

Contributor

nex3 commented Mar 6, 2013

Creation Syntax

There are a lot of look-ahead issues that arise from using {}. Not just nested properties, but anywhere that a curly bracket immediately follows script... in @each, for example. I'd rather avoid these issues.

Iteration

I would also amend the @each directive to be map-aware:

div.stuff {
  @each $key, $value in $map {
    #{$key}: $value;
  }
}

I agree with this, although I think @each $key: $value in $map looks a little better.

  • map-set($map, $key1, $value1, $key2, $value2, ...) - returns a new map having the keys set to their corresponding values. if a key already exists, the order of that key will stay the same, but the value it is mapped to will change.

I'm not a big fan of this function. I think we should use map-merge as the canonical way of adding values to a map. These functions are pretty much identical in semantics, except that map-set is less general. The name also incorrectly implies that it mutates the map.

  • map-apply($map, $function-name) - returns a new map where the values have been transformed according to the values returned by the function given by $function-name (which should accept two arguments -- the key and the current value). While this could be done in pure-sass, having it provided internally will avoid the creation of many intermediate map objects because of the immutability of map objects.

This is a pretty wacky function. What are the use cases for it?

  • length($map) returns a number that is the number of pairs in the map.
  • nth($map, $index) returns a comma delimited list of the key and value stored at the map's index.

If we implement these, we should have a general policy of list functions treating maps as lists of pairs. This isn't the craziest thing (and makes () work nicely as both the empty list and the empty map), but we run into plenty of problems with single-element lists as it is. Do you have ideas for dealing with that?

@lunelson

This comment has been minimized.

Show comment
Hide comment
@lunelson

lunelson Apr 30, 2013

For users anxious to have this function, this hack might work for some use-cases:

@function value($map, $key) {
  @each $pair in $map {
    @if index($pair, $key) {
        @return nth($pair, 2);
    } 
  }
}

$test-map: 
    position absolute, 
    bottom 0px,
    right 0px;


.test {
  position: value($test-map, position);
  bottom: value($test-map, bottom);
  right: value($test-map, right);
}

produces the output:

.test {
  position: absolute;
  bottom: 0px;
  right: 0px;
}

For users anxious to have this function, this hack might work for some use-cases:

@function value($map, $key) {
  @each $pair in $map {
    @if index($pair, $key) {
        @return nth($pair, 2);
    } 
  }
}

$test-map: 
    position absolute, 
    bottom 0px,
    right 0px;


.test {
  position: value($test-map, position);
  bottom: value($test-map, bottom);
  right: value($test-map, right);
}

produces the output:

.test {
  position: absolute;
  bottom: 0px;
  right: 0px;
}
@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 24, 2013

Member

Creation Syntax

There are a lot of look-ahead issues that arise from using {}. ... I'd rather avoid these issues.

Fair enough. The visual similarity with a ruleset may be confusing to read instead of being helpful.

Though it seems that you're proposing using() which seems to introduce similar look-ahead issues with lists because of arbitrary key expressions. Perhaps we should use [] instead. Or maybe we should go back to a creation function like map(key1 value1, key2 value2, key3 value3)

Iteration

I agree with this, although I think @each $key: $value in $map looks a little better.

I agree that does read better for the common case of maps. Let's consider the edge cases a bit.

First, what should the behavior of @each $pair in $map be? Is it an error? or should we simply set $pair to ($key, $value) on each successive iteration. I am a fan of the latter as this allows code to be written which can accept a map or an old-style list of pairs.

I see a fair bit of sass code that boils down to setting variables to successive indexes in a list. We can clean up such code eventually with a multi-set expression like $a, $b, $c: $list-of-three-elements.

And so, with the above multi-set I think a map-pair and a list of two elements can be made syntacticly indistinguishable. which leaves me thinking that an automatic multi-set in the @each iterator using comma makes for a nice syntax especially if you have a list of n-tuples, etc.

I guess the answer could be allow both syntaxes. where $key: $value is handled for a two element list. but I think I prefer a single syntax that serves more use cases and provides syntactic & mental consistency with a multi-set.

map-set function

I think we should use map-merge as the canonical way of adding values to a map.

I think this is good.

map-apply function

This is a pretty wacky function. What are the use cases for it?

I want to also add list-apply which would do a map of values to values. Because I see so much code that looks like this:

$new-list: ();
@each $e in $old-list { $new-list: append($new-list, calculation-for($e)); }

which creates a lot of garbage list objects in addition to being less readable than:

$new-list: list-apply($old-list, calculation-for);

Additionally, the list-apply makes it drop-dead simple to pass a mutated list as an argument or use as a property value. So I suspect that we'll see the same patterns emerge with maps and I know from my own coding experience that transforming a map's values is a fairly common operation. All that said, I'm ok with not adding the map-apply function in the initial API and seeing how things evolve in practice.

length and nth functions

If we implement these, we should have a general policy of list functions treating maps as lists of pairs.

Yes. As I stated above, I'm a big fan of this as well as treating lists of pairs as maps. It makes new code more backwards compatible with existing data structures and I don't think it has any downsides that I can think of.

Keyword arguments

We didn't cover this yet, but integration with variable arguments was a key driver for this feature. So I just want to give it a mention. As discussed we will implement the following:

@function foo($arguments...) {
   $kw-args: keywords($arguments);
   // ... use the keywords passed in where the keys are strings of the names of the variables passed.
   // So foo($a: 1, $b: 2) results in $kw-args being (a: 1, b: 2)
}

Similarly, a map can be used to pass keywords. but I don't think we settled on the syntax for that.

Should it accept multiple ... arguments like so:

$positional-args: 10px, #fff;
$kw-args: (a: 1, b: 2);
$returned-value: foo($positional-args..., $kw-args...)

Or should it continue to only accept a single ... argument with a special arglist constructor function:

$positional-args: 10px, #fff;
$kw-args: (a: 1, b: 2);
$returned-value: foo(arglist($positional-args, $kw-args)...)

I prefer the former. I have a fair bit of time today if you want to discuss this.

Member

chriseppstein commented May 24, 2013

Creation Syntax

There are a lot of look-ahead issues that arise from using {}. ... I'd rather avoid these issues.

Fair enough. The visual similarity with a ruleset may be confusing to read instead of being helpful.

Though it seems that you're proposing using() which seems to introduce similar look-ahead issues with lists because of arbitrary key expressions. Perhaps we should use [] instead. Or maybe we should go back to a creation function like map(key1 value1, key2 value2, key3 value3)

Iteration

I agree with this, although I think @each $key: $value in $map looks a little better.

I agree that does read better for the common case of maps. Let's consider the edge cases a bit.

First, what should the behavior of @each $pair in $map be? Is it an error? or should we simply set $pair to ($key, $value) on each successive iteration. I am a fan of the latter as this allows code to be written which can accept a map or an old-style list of pairs.

I see a fair bit of sass code that boils down to setting variables to successive indexes in a list. We can clean up such code eventually with a multi-set expression like $a, $b, $c: $list-of-three-elements.

And so, with the above multi-set I think a map-pair and a list of two elements can be made syntacticly indistinguishable. which leaves me thinking that an automatic multi-set in the @each iterator using comma makes for a nice syntax especially if you have a list of n-tuples, etc.

I guess the answer could be allow both syntaxes. where $key: $value is handled for a two element list. but I think I prefer a single syntax that serves more use cases and provides syntactic & mental consistency with a multi-set.

map-set function

I think we should use map-merge as the canonical way of adding values to a map.

I think this is good.

map-apply function

This is a pretty wacky function. What are the use cases for it?

I want to also add list-apply which would do a map of values to values. Because I see so much code that looks like this:

$new-list: ();
@each $e in $old-list { $new-list: append($new-list, calculation-for($e)); }

which creates a lot of garbage list objects in addition to being less readable than:

$new-list: list-apply($old-list, calculation-for);

Additionally, the list-apply makes it drop-dead simple to pass a mutated list as an argument or use as a property value. So I suspect that we'll see the same patterns emerge with maps and I know from my own coding experience that transforming a map's values is a fairly common operation. All that said, I'm ok with not adding the map-apply function in the initial API and seeing how things evolve in practice.

length and nth functions

If we implement these, we should have a general policy of list functions treating maps as lists of pairs.

Yes. As I stated above, I'm a big fan of this as well as treating lists of pairs as maps. It makes new code more backwards compatible with existing data structures and I don't think it has any downsides that I can think of.

Keyword arguments

We didn't cover this yet, but integration with variable arguments was a key driver for this feature. So I just want to give it a mention. As discussed we will implement the following:

@function foo($arguments...) {
   $kw-args: keywords($arguments);
   // ... use the keywords passed in where the keys are strings of the names of the variables passed.
   // So foo($a: 1, $b: 2) results in $kw-args being (a: 1, b: 2)
}

Similarly, a map can be used to pass keywords. but I don't think we settled on the syntax for that.

Should it accept multiple ... arguments like so:

$positional-args: 10px, #fff;
$kw-args: (a: 1, b: 2);
$returned-value: foo($positional-args..., $kw-args...)

Or should it continue to only accept a single ... argument with a special arglist constructor function:

$positional-args: 10px, #fff;
$kw-args: (a: 1, b: 2);
$returned-value: foo(arglist($positional-args, $kw-args)...)

I prefer the former. I have a fair bit of time today if you want to discuss this.

@nwwells

This comment has been minimized.

Show comment
Hide comment
@nwwells

nwwells May 25, 2013

I was just playing around with maps, and then found this issue. Glad to see this is something you guys want to do.

For anyone who comes to this issue after this and before it's implemented, I implemented a tuple lookup function you can see here:

https://gist.github.com/nwwells/5644618

It's basically the same as @lunelson's but I support tuples rather than maps only. Also, you can specify a default value.

nwwells commented May 25, 2013

I was just playing around with maps, and then found this issue. Glad to see this is something you guys want to do.

For anyone who comes to this issue after this and before it's implemented, I implemented a tuple lookup function you can see here:

https://gist.github.com/nwwells/5644618

It's basically the same as @lunelson's but I support tuples rather than maps only. Also, you can specify a default value.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 31, 2013

Contributor

Chris and I discussed this privately a fair amount last week. Here's where we came down (to the best of my recollection):

length and nth functions

We will treat maps as lists of pairs, so these functions will work transparently. I don't think we'll treat lists of pairs as maps, though... dealing with inconsistencies such as duplicate keys would be too much of a headache.

Functions like join and append will always return lists, even if they're passed maps.

Creation Syntax

We'll use (foo: bar), with the parentheses mandatory (unlike for lists). Treating maps as lists make () work as both the empty map and the empty list.

Iteration

We'll use @for $pair in $map or @for $key, $value in $map. These will piggyback on a more general ability to destructure a list elements in a loop. This means that @for $e1, $e2 in (foo bar, baz qux) will work.

map-apply function

We're not going to support first-class functions in 3.3, so we'll wait on addressing this for now.

Keyword arguments

The syntax for passing a list as variable arguments and a map as keyword arguments will be fn($list..., $map...).

Contributor

nex3 commented May 31, 2013

Chris and I discussed this privately a fair amount last week. Here's where we came down (to the best of my recollection):

length and nth functions

We will treat maps as lists of pairs, so these functions will work transparently. I don't think we'll treat lists of pairs as maps, though... dealing with inconsistencies such as duplicate keys would be too much of a headache.

Functions like join and append will always return lists, even if they're passed maps.

Creation Syntax

We'll use (foo: bar), with the parentheses mandatory (unlike for lists). Treating maps as lists make () work as both the empty map and the empty list.

Iteration

We'll use @for $pair in $map or @for $key, $value in $map. These will piggyback on a more general ability to destructure a list elements in a loop. This means that @for $e1, $e2 in (foo bar, baz qux) will work.

map-apply function

We're not going to support first-class functions in 3.3, so we'll wait on addressing this for now.

Keyword arguments

The syntax for passing a list as variable arguments and a map as keyword arguments will be fn($list..., $map...).

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 31, 2013

Member

Regarding keyword arguments: for symmetry, I'd like to support

@function foo($list..., $map...) {
}

What's more I'm pretty sure I dislike the magic keyword values that are stored behind the argument list. It makes it too easy to author functions and mixins that silently ignore keyword arguments. I would be ok with deprecating it.

Member

chriseppstein commented May 31, 2013

Regarding keyword arguments: for symmetry, I'd like to support

@function foo($list..., $map...) {
}

What's more I'm pretty sure I dislike the magic keyword values that are stored behind the argument list. It makes it too easy to author functions and mixins that silently ignore keyword arguments. I would be ok with deprecating it.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 31, 2013

Contributor

Regarding keyword arguments: for symmetry, I'd like to support

@function foo($list..., $map...) {
}

This seems pretty unclear to me. It leans very heavily on the parameter names to clarify what's going on, and provides no visual indication that the two parameters are doing different things.

What's more I'm pretty sure I dislike the magic keyword values that are stored behind the argument list. It makes it too easy to author functions and mixins that silently ignore keyword arguments. I would be ok with deprecating it.

I don't understand this objection. We explicitly designed the existing semantics to avoid silently ignoring keyword arguments. If anything, your suggestion makes it easier to screw up in the case where you're forwarding all arguments to a function, because foo($args...) { @return bar($args...) } stops working.

Contributor

nex3 commented May 31, 2013

Regarding keyword arguments: for symmetry, I'd like to support

@function foo($list..., $map...) {
}

This seems pretty unclear to me. It leans very heavily on the parameter names to clarify what's going on, and provides no visual indication that the two parameters are doing different things.

What's more I'm pretty sure I dislike the magic keyword values that are stored behind the argument list. It makes it too easy to author functions and mixins that silently ignore keyword arguments. I would be ok with deprecating it.

I don't understand this objection. We explicitly designed the existing semantics to avoid silently ignoring keyword arguments. If anything, your suggestion makes it easier to screw up in the case where you're forwarding all arguments to a function, because foo($args...) { @return bar($args...) } stops working.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 31, 2013

Member

@nex3 It's a good point that you make about clarity there.

I like how python distinguishes keywords from positional arguments. Anyways, this is an orthogonal issue I'll think about it more and create a separate issue down the road after actually having used the current plan in practice. The pain of deprecation is already incurred, so delaying doesn't really add much cost.

Member

chriseppstein commented May 31, 2013

@nex3 It's a good point that you make about clarity there.

I like how python distinguishes keywords from positional arguments. Anyways, this is an orthogonal issue I'll think about it more and create a separate issue down the road after actually having used the current plan in practice. The pain of deprecation is already incurred, so delaying doesn't really add much cost.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Jul 19, 2013

Member

We need a key existence check (map-has-key($key)) because index(map-keys($map), $key) != null is annoying.

Member

chriseppstein commented Jul 19, 2013

We need a key existence check (map-has-key($key)) because index(map-keys($map), $key) != null is annoying.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jul 19, 2013

Contributor

We need a key existence check (map-has-key($key)) because index(map-keys($map), $key) != null is annoying.

Agree.

Contributor

nex3 commented Jul 19, 2013

We need a key existence check (map-has-key($key)) because index(map-keys($map), $key) != null is annoying.

Agree.

@nex3 nex3 referenced this issue Jul 30, 2013

Merged

Maps #864

nex3 added a commit that referenced this issue Aug 29, 2013

@nex3 nex3 closed this Aug 29, 2013

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