Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

List assignment operator support #852

Open
tylermenezes opened this Issue Jul 15, 2013 · 10 comments

Comments

Projects
None yet
7 participants
@tylermenezes
Copy link

tylermenezes commented Jul 15, 2013

Currently Sass supports appending to a list, but not setting an element to a list to a new value. The only way to implement this natively is to iterate through the list:

@function set-nth($list, $n, $value)
{
    $new_list: ();

    @for $i from 1 through max($n, length($list)) {
        @if ($i == $n) {
            $new_list: append($new_list, $value);
        } @else {
            @if (length($list) >= $i) {
                $new_list: append($new_list, nth($list, $i));
            } @else {
                $new_list: append($new_list, null);
            }
        }
    }

    @return $new_list;
}

This is exceptionally slow, and loading a 3rd party tool to add support for setting elements seems very silly.

set-nth should be introduced to the core, taking (list, n, value) and returning a new instance of the modified list.

This is related to #537, but this proposal retains the immutability of lists, creating a function which returns the modified list, in line with append.

@HugoGiraudel

This comment has been minimized.

Copy link

HugoGiraudel commented Jul 16, 2013

Encountered the case once or twice. Would also like to see this feature into Sass core.

@chriseppstein

This comment has been minimized.

Copy link
Member

chriseppstein commented Jul 16, 2013

👍

@jakob-e

This comment has been minimized.

Copy link

jakob-e commented Aug 4, 2013

Nice :)
Extra list functions are more than welcome...
here are a few of my own cookings:

// ==================================================================== 
// List functions
// ==================================================================== 
// Create list - (fix one-item-multidimensional-list "bug")
// -------------------------------------------------------------------
@function list($args...){
    $list:();
    @for $i from 1 through length($args){$list:append($list,nth($args,$i));};
    @return $list;
}

// Find list item (including nested lists) 
// -------------------------------------------------------------------
// $list  - the list
// $index - index (and nested indexes) to match
// 
// $list:list(list(a,b,c),list(d,list(e1,e2,list(e3a,e3b,wally)),f)); 
// .item { where-is:item($list,2,2,3,3)} // .item { where-is: wally }
@function item($list,$index...){
    $item:$list;
    @for $i from 1 through length($index){$item:nth($item,nth($index,$i));};
    @return $item;
}

// Replace list item
// -------------------------------------------------------------------
// $list  - the list 
// $value - new value
// $index - index (and nested indexes) to match
// 
// $list:list(list(a,b,c),list(d,list(e1,e2,list(e3a,e3b,wally)),f)); 
// .list { items:$list;}   // .list { items: a b c d e1 e2 e3a e3b wally f; }
// $list:replace($list,notwally,2,2,3,3); 
// .list { items:$list;}   // .list { items: a b c d e1 e2 e3a e3b notwally f; }
@function replace($list,$value,$index...){
    $newlist:();
    @for $i from 1 through length($list){
        $itm:nth($list,$i);
        @if(nth($index,1)==$i){ 
            @if(length($index)>1){ $itm:replace($itm,$value,slice($index,2)...); }
            @else{ $itm:$value; }
        } 
        $newlist:append($newlist,$itm);
    } @return $newlist;
}

// Remove list item -  (:replace style - just nulling the value)
// -------------------------------------------------------------------
// $list  - the list 
// $index - index (and nested indexes) to match
// 
// $list:list(list(a,b,c),list(d,list(e1,e2,list(e3a,e3b,wally)),f)); 
// .list { items:$list;}   // .list { items: a b c d e1 e2 e3a e3b wally f; }
// $list:remove($list,2,2,3,3); 
// .list { items:$list;}   // .list { items: a b c d e1 e2 e3a e3b f; }
@function remove($list,$index...){
    @return replace($list,null,$index...);
}

// List slice (js style)
// -------------------------------------------------------------------
@function slice($list,$start,$end:null){
    $newlist:();
    $end:if($end==null,length($list),$end); 
    @for $i from $start through $end{ $newlist:append($newlist,nth($list,$i));};
    @return $newlist;
}

// Prepend (:append style)
// -------------------------------------------------------------------
@function prepend($list,$value,$separator:auto){
    $newlist:list($value);
    @for $i from 1 through length($list){$newlist:append($newlist,nth($list,$i),$separator);};
    @return $newlist;
}
@HugoGiraudel

This comment has been minimized.

Copy link

HugoGiraudel commented Aug 5, 2013

I've made a couple of advanced lists functions too:

There is still a lot of room for improvements, starting with function names. I wasn't sure how to call them.

/* ------------------------------------------------------------------------------- *
 * A couple of advanced Sass list functions
 * ------------------------------------------------------------------------------- *
 *
 * first($list)
 * Returns first element of $list
 *
 * last($list)
 * Returns last element of $list
 *
 * last-index($list, $value)
 * Returns last index of $value in $list
 *
 * to-string($list, $glue: '', $is-nested: false)
 * Joins all elements of $list with $glue
 *
 * prepend($list, $value)
 * Add $value as first index of $list
 *
 * insert-nth($list, $index, $value)
 * Add $value at $index in $list
 *
 * replace($list, $old-value, $new-value, $recursive: false)
 * Replace $old-value by $new-value in $list
 *
 * replace-nth($list, $index, $value)
 * Replace value at $index from $list by $value
 *
 * remove($list, $value, $recursive: false)
 * Remove value(s) $value from $list
 *
 * remove-nth($list, $index)
 * Remove value from $list at index $index
 *
 * slice($list, $start: 1, $end: length($list))
 * Slices $list between $start and $end
 *
 * reverse($list, $recursive: false)
 * Reverses the order of $list
 *
 * loop($list, $value: 1)
 * Shift indexes from $list of $value
 * 
 * ------------------------------------------------------------------------------- *
 * CodePen: http://codepen.io/HugoGiraudel/pen/loAgq
 * Repository: https://github.com/HugoGiraudel/Sass-snippets/blob/master/src/list-functions/
 * ------------------------------------------------------------------------------- */


/**
 * Returns first element of $list
 * -------------------------------------------------------------------------------
 * @example first(a, b, c) => a
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * -------------------------------------------------------------------------------
 * @return [Literal]
 */
@function first($list) {
  @return nth($list, 1);
}

/**
 * Returns last element of $list
 * -------------------------------------------------------------------------------
 * @example last( (a, b, c) ) => c
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * -------------------------------------------------------------------------------
 * @return [Literal]
 */
@function last($list) {
  @return nth($list, length($list));
}

/**
 * Returns last index of $value in $list
 * -------------------------------------------------------------------------------
 * @example last-index( (a, b, c), z )    => null
 * @example last-index( (a, b, c, a), a ) => 4
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $value [Literal] : value to be searched for
 * -------------------------------------------------------------------------------
 * @return [Number]
 */
@function last-index($list, $value) {
  @for $i from length($list)*-1 through -1 {
    @if nth($list, abs($i)) == $value {
      @return abs($i);
    }
  }

  @return null;
}

/**
 * Joins all elements of $list with $glue
 * -------------------------------------------------------------------------------
 * @example to-string( (a, b, c) )      => abc
 * @example to-string( (a, b, c), '-' ) => a-b-c
 * @example to-string( (a, b c, d) )    => abcd
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $glue [String] : value to use as a join string
 * @param $is-nested [Boolean] : strictly internal boolean for recursivity
 * -------------------------------------------------------------------------------
 * @return [String]
 */
@function to-string($list, $glue: '', $is-nested: false) {
  $result: null;

  @for $i from 1 through length($list) {
    $e: nth($list, $i);

    @if type-of($e) == list {
      $result: $result#{to-string($e, $glue, true)};
    }

    @else {
      $result: if($i != length($list) or $is-nested, $result#{$e}#{$glue}, $result#{$e});
    }
  }

  @return $result;
}

/**
 * Add $value as first index of $list
 * -------------------------------------------------------------------------------
 * @example prepend( (a, b, c), z )   => z, a, b, c
 * @example prepend( (a, b, c), y z ) => y z, a, b, c
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $value [Literal] : value to prepend to the list
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function prepend($list, $value) {
  @return join($value, $list);
}

/** 
 * Add $value at $index in $list
 * -------------------------------------------------------------------------------
 * @example insert-nth( (a, b, c),  2, z ) => a, z, b, c
 * @example insert-nth( (a, b, c),  0, z ) => error
 * @example insert-nth( (a, b, c), -1, z ) => error
 * @example insert-nth( (a, b, c), 10, z ) => error
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $index [Number] : index to add
 * @param $value [Literal] : value to add
 * -------------------------------------------------------------------------------
 * @raise [Error] if $index isn't an integer
 * @raise [Error] if $index is strictly lesser than 1
 * @raise [Error] if $index is strictly greater than length of $list
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function insert-nth($list, $index, $value) {
  $result: null;

  @if type-of($index) != number {
    @warn "$index: #{quote($index)} is not a number for `insert-nth`.";
  }

  @else if $index < 1 {
    @warn "List index 0 must be a non-zero integer for `insert-nth`";
  }

  @else if $index > length($list) {
    @warn "List index is #{$index} but list is only #{length($list)} item long for `insert-nth'.";
  }

  @else {
    $result: ();

    @for $i from 1 through length($list) {
      @if $i == $index {
        $result: append($result, $value);
      }

      $result: append($result, nth($list, $i));
    }
  }

  @return $result;
}

/**
 * Replace value at $index from $list by $value
 * -------------------------------------------------------------------------------
 * @example replace-nth( (a, b, c),   2, z ) => a, z, c
 * @example replace-nth( (a, b, c),   0, z ) => error
 * @example replace-nth( (a, b, c),  -1, z ) => a, b, z
 * @example replace-nth( (a, b, c),  10, z ) => error
 * @example replace-nth( (a, b, c), -10, z ) => error
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $index [Number] : index to update
 * @param $value [Literal] : new value for index $index
 * -------------------------------------------------------------------------------
 * @raise [Error] if $index isn't an integer
 * @raise [Error] if $index is 0
 * @raise [Error] if abs value of $index is strictly greater than length of $list
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function replace-nth($list, $index, $value) {
  $result: null;

  @if type-of($index) != number {
    @warn "$index: #{quote($index)} is not a number for `replace-nth`.";
  }

  @else if $index == 0 {
    @warn "List index 0 must be a non-zero integer for `replace-nth`.";
  }

  @else if abs($index) > length($list) {
    @warn "List index is #{$index} but list is only #{length($list)} item long for `replace-nth`.";
  }

  @else {
    $result: ();
    $index: if($index < 0, length($list) + $index + 1, $index);  

    @for $i from 1 through length($list) {
      @if $i == $index {
        $result: append($result, $value);
      }

      @else {
        $result: append($result, nth($list, $i));
      }
    }
  }

  @return $result;
}

/** 
 * Replace $old-value by $new-value in $list
 * -------------------------------------------------------------------------------
 * @example replace( (a, b, c), b, z )         => a, z, c
 * @example replace( (a, b, c), y, z )         => a, b, c
 * @example replace( (a, b, c a), a, z )       => z, b, c z
 * @example replace( (a, b, c a), a, z, true ) => z, b, c z
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $old-value [Literal] : value to replace
 * @param $new-value [Literal] : new value for $old-value
 * @param $recursive [Boolean] : enable / disable recursivity
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function replace($list, $old-value, $new-value, $recursive: false) {
  $result: ();

  @for $i from 1 through length($list) {
    @if type-of(nth($list, $i)) == list and $recursive {
      $result: append($result, replace(nth($list, $i), $old-value, $new-value, $recursive));
    }

    @else {
      @if nth($list, $i) == $old-value {
        $result: append($result, $new-value);
      }

      @else {
        $result: append($result, nth($list, $i));
      }
    }
  }

  @return $result;
}

/**
 * Remove value from $list at index $index
 * -------------------------------------------------------------------------------
 * @example remove-nth( (a, b, c),   2 ) => a, c
 * @example remove-nth( (a, b, c),   0 ) => error
 * @example remove-nth( (a, b, c),  -1 ) => a, b
 * @example remove-nth( (a, b, c),  10 ) => error
 * @example remove-nth( (a, b, c), -10 ) => error
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $index [Number] : index to remove
 * -------------------------------------------------------------------------------
 * @raise [Error] if $index isn't an integer
 * @raise [Error] if $index is 0
 * @raise [Error] if abs value of $index is strictly greater then length of $list
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function remove-nth($list, $index) {
  $result: null;

  @if type-of($index) != number {
    @warn "$index: #{quote($index)} is not a number for `remove-nth`.";
  }

  @else if $index == 0 {
    @warn "List index 0 must be a non-zero integer for `remove-nth`.";
  }

  @else if abs($index) > length($list) {
    @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
  }

  @else {
    $result: ();
    $index: if($index < 0, length($list) + $index + 1, $index);  

    @for $i from 1 through length($list) {
      @if $i != $index {
        $result: append($result, nth($list, $i));
      }
    }
  }

  @return $result;
}

/**
 * Remove value(s) $value from $list
 * -------------------------------------------------------------------------------
 * @example remove( (a, b, c),   b )       => a, c
 * @example remove( (a, b, c),   z )       => a, b, c
 * @example remove( (a, b, c b), b )       => a, c b
 * @example remove( (a, b, c b), b, true ) => a, c
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $value [Literal] : value to remove
 * @param $recursive [Boolean] : enable / disable recursivity
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function remove($list, $value, $recursive: false) {
  $result: ();

  @for $i from 1 through length($list) {
    @if type-of(nth($list, $i)) == list and $recursive {
      $result: append($result, remove(nth($list, $i), $value, $recursive));
    }

    @else if nth($list, $i) != $value {
      $result: append($result, nth($list, $i));
    }
  }

  @return $result;
}

/**
 * Slices $list between $start and $end
 * -------------------------------------------------------------------------------
 * @example slice( (a, b, c, d),  2, 3 ) => b, c
 * @example slice( (a, b, c, d),  3, 2 ) => error
 * @example slice( (a, b, c, d),  3, 5 ) => error
 * @example slice( (a, b, c, d), -1, 3 ) => error
 * @example slice( (a, b, c, d),  0, 3 ) => error
 * @example slice( (a, b, c, d),  3, 3 ) => c
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $start [Number] : start index
 * @param $end [Number] : end index
 * -------------------------------------------------------------------------------
 * @raise [Error] if $start or $end aren't integers
 * @raise [Error] if $start is greater than $end
 * @raise [Error] if $start or $end is strictly lesser than 1
 * @raise [Error] if $start is strictly greater than length of $list
 * @raise [Error] if $end is strictly greater than length of $list
 * -------------------------------------------------------------------------------
 * @return [List]
 */ 
@function slice($list, $start: 1, $end: length($list)) {
  $result: null;

  @if type-of($start) != number or type-of($end) != number {
    @warn "Either $start or $end are not a number for `slice`.";
  }

  @else if $start > $end {
    @warn "The start index has to be lesser than or equals to the end index for `slice`.";
  }

  @else if $start < 1 or $end < 1 {
    @warn "List indexes must be non-zero integers for `slice`.";
  }

  @else if $start > length($list) {
    @warn "List index is #{$start} but list is only #{length($list)} item long for `slice`.";
  }

  @else if $end > length($list) {
    @warn "List index is #{$end} but list is only #{length($list)} item long for `slice`.";
  }

  @else {
    $result: ();

    @for $i from $start through $end {
      $result: append($result, nth($list, $i));
    }
  }

  @return $result;
}

/**
 * Reverses the order of $list
 * -------------------------------------------------------------------------------
 * @example reverse( (a, b, c) )         => c, b, a
 * @example reverse( (a, b, c a) )       => c a, b, a
 * @example reverse( (a, b, c a), true ) => a c, b, a
 * @example reverse( a )                 => a
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $recursive [Boolean] : enable / disable recursivity
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function reverse($list, $recursive: false) {
   $result: ();

   @for $i from length($list)*-1 through -1 {
      @if type-of(nth($list, abs($i))) == list and $recursive {
        $result: append($result, reverse(nth($list, abs($i)), $recursive));      
      }

      @else {
        $result: append($result, nth($list, abs($i)));
      }
   }

   @return $result;
}

/**
 * Shift indexes from $list of $value
 * -------------------------------------------------------------------------------
 * @example loop( (a, b, c, d, e) )     => e, a, b, c, d
 * @example loop( (a, b, c, d, e),  2 ) => d, e, a, b, c
 * @example loop( (a, b, c, d, e), -2 ) => c, d, e, a, b
 * -------------------------------------------------------------------------------
 * @param $list [List] : list
 * @param $value [Number] : number of position between old and new indexes
 * -------------------------------------------------------------------------------
 * @return [List]
 */
@function loop($list, $value: 1) {
  $result: ();

  @for $i from 0 to length($list) {
    $result: append($result, nth($list, ($i - $value) % length($list) + 1));
  }

  @return $result;
}
@chriseppstein

This comment has been minimized.

Copy link
Member

chriseppstein commented Sep 19, 2013

👍 Agree. We need better list manipulation functions.

@nex3 nex3 closed this Jan 8, 2014

@Snugug

This comment has been minimized.

Copy link

Snugug commented Aug 25, 2014

This was closed by @nex3 but I don't see a reason. Recently ran into the want of slice. Any chance of getting this re-opened and implemented?

@chriseppstein

This comment has been minimized.

Copy link
Member

chriseppstein commented Aug 27, 2014

Agree. List helpers are super important.

@chriseppstein chriseppstein reopened this Aug 27, 2014

@nex3 nex3 added the Feature label Aug 27, 2014

@cibulka

This comment has been minimized.

Copy link

cibulka commented Oct 8, 2014

@HugoGiraudel, @jakob-e: This is awesome! I'm trying to write my own map-merge-recursive, any idea how to achieve that? If I will succeed, I will post my solution here.

@HugoGiraudel

This comment has been minimized.

Copy link

HugoGiraudel commented Oct 8, 2014

If you mean deep merge, at-import/SassyMaps has one: https://github.com/at-import/Sassy-Maps/blob/0.x.x/sass/sassy-maps/_map-set.scss#L13-L51. I've made one as well: http://sassmeister.com/gist/9838432.

Pick the one you like.

@cibulka

This comment has been minimized.

Copy link

cibulka commented Oct 8, 2014

Thank you Hugo! I've just found a sollution designed specifically for merging two deep maps: at-import/Sassy-Maps#9 It seems it won't be implemented to Sassy maps for now, but it can still be used in addition to them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.