anonymous functions #472

Open
chriseppstein opened this Issue Aug 6, 2012 · 6 comments

Comments

Projects
None yet
7 participants
@chriseppstein
Member

chriseppstein commented Aug 6, 2012

This code is in a patch for compass that I'm about to approve and I really feel like this can be cleaned up dramatically by adding some basic functional programming abilities to Sass.

In particular the importance of lists in css and the need to manipulate them using side-effects is causing severe pain in many of the more complex sass projects that I've seen under development recently.

Consider this code written in Sass's current syntax:

@mixin rem($property, $values, $use-px-fallback: $rem-with-px-fallback) {
    // Create a couple of empty lists as output buffers.
    $px-values: ();
    $rem-values: ();

    // Ensure $values is a list.
    @if type-of($values) != 'list' {
        $values: join((), $values);
    }

    // Loop through the $values list
    @each $value in $values {
        // For each property value, if it's in rem or px, derive both rem and
        // px values for it and add those to the end of the appropriate buffer.
        // Ensure all pixel values are rounded to the nearest pixel.
        @if type-of($value) == number and not unitless($value) and (unit($value) == px or unit($value) == rem) {
            @if unit($value) == px {
                $px-values: join($px-values, round($value));
                $rem-values: join($rem-values, convert-length($value, rem));
            }
            @else {
                $px-values: join($px-values, round(convert-length($value, px)));
                $rem-values: join($rem-values, $value);
            }
        }
        @else {
            $px-values: join($px-values, $value);
            $rem-values: join($rem-values, $value);
        }
    }

    // Use pixel fallback for browsers that don't understand rem units.
    @if $use-px-fallback {
        #{$property}: $px-values;
    }

    // Use rem values for everyone else (overrides pixel values).
    #{$property}: $rem-values;
}

I'm not sure what the syntax should be, but so that we have something to throw darts at I propose that if the first statement of a ruleset is ->(<argument list>); then the block would become an anonymous function if passed to a function. This same syntax could also be used by anonymous content blocks to receive arguments from mixins to their @content blocks.

.mapped {
  foo: map-to(1 2 3) {->($v);
    @return $v * 2;
  };
}

which would emit:

.mapped {
  foo: 2 4 6;
}

This would necessitate the need for a built-in function that can be used in @function to call the anonymous function. Again, maybe there's a better name and semantics, but let's start with yield:

@function foo($arg) {
  @return yield($arg * 2);
}

.foo {
  prop: foo(30px) {->($a); @return $a * 2; }
}

would compile to:

.foo {
  prop: 120px
}

Following this, I would like Sass to add a number of Sass functions for operating on lists to make working with them much simpler. For instance a map-to function for mapping each value in a list to a new value. reject and keep could be used to filter lists, etc.

The question of whether to allow named functions to be passed to other functions is a natural follow on. I would start with no for now because an anonymous function can be used to invoke a named function and we have this same issue with content blocks and named mixins. But later on, I imagine this pointer syntax could be used in a fairly natural way:

div {
  @include foo(10px) -> my-mixin;
  prop: map-to(10px 20px 30px) -> my-function;
}

With these abilities the above original code above cleans up to:

@mixin rem($property, $values, $use-px-fallback: $rem-with-px-fallback) {
  // Use pixel fallback for browsers that don't understand rem units.
  @if $use-px-fallback {
    #{$property}: map-to($values) {->($value);
      @return if(type-of($value) == number
                 and not unitless($value)
                 and (unit($value) == px or unit($value) == rem),
                if(unit($value) == px,
                   round($value),
                   round(convert-length($value, px))),
                $value);
    }
  }

  // Use rem values for everyone else (overrides pixel values).
  #{$property}: map-to($values) {->($value);
    @return if(type-of($value) == number
               and not unitless($value)
               and (unit($value) == px or unit($value) == rem),
              if(unit($value) == px,
                 convert-length($value, rem),
                 $value)),
              $value);
  }
}
@metaskills

This comment has been minimized.

Show comment Hide comment
@metaskills

metaskills Aug 7, 2012

I found myself needing something like this the other day when needing to map a list to a set of selectors. Giving the current limitations, I did the following. https://gist.github.com/3170386#gistcomment-379715

I found myself needing something like this the other day when needing to map a list to a set of selectors. Giving the current limitations, I did the following. https://gist.github.com/3170386#gistcomment-379715

@ravinggenius

This comment has been minimized.

Show comment Hide comment
@ravinggenius

ravinggenius Aug 8, 2012

I would rather assign the function to a parameter instead of using yield. Function parameters more clearly document what is expected so developers will not have to scan a function's source to find if/how a callback function is used. Binding functions to parameters also allow multiple functions to be passed in, which may be desirable in some cases.

I would rather assign the function to a parameter instead of using yield. Function parameters more clearly document what is expected so developers will not have to scan a function's source to find if/how a callback function is used. Binding functions to parameters also allow multiple functions to be passed in, which may be desirable in some cases.

@cimmanon

This comment has been minimized.

Show comment Hide comment
@cimmanon

cimmanon Jan 14, 2013

First class functions and mixins are vastly more useful than lambdas (anonymous functions). Consider the following Haskell code:

a = map (\ a -> a * 2 ) [1..10]

vs

squareIt x = x * 2
a = map squareIt [1..10]

Both samples will do the same thing: take a list containing the numbers 1 through 10 and returns a list containing each number squared. If I only have the ability to use lambdas, I can certainly write this:

a = map (\ a -> squareIt a ) [1..10]

That's perfectly fine in situations where we're dealing with things that won't or probably shouldn't change, but its just syntactic sugar for something we can already write quite easily.

Having true first class functions/mixins, on the other hand, opens the door for greater flexibility in customizing mixins in ways we never could before.

@mixin prettyElementA {
    border: 1px solid;
}

@mixin prettyElementB {
    background: yellow;
}

@mixin coolWidget($block, $list) {
    ul, ol {
        @include $list;
        padding: 1em;
    }

    div {
        @include $block;
        border: 1px solid green;
    }

    ul, ol, div {
        @content;
    }
}

.myWidget {
    @include coolWidget(prettyElementA, prettyElementB) {
        color: red;
    }
}

Right now, the closest I can come to making the coolWidget mixin customizable is by hard coding the mixin calls and encouraging overwriting of those mixins. But once the mixins have been overwritten, I can't get them back.

If I had to choose between lambdas or first class functions, I wouldn't even have to think about it. First class functions are by far more powerful and more useful.

First class functions and mixins are vastly more useful than lambdas (anonymous functions). Consider the following Haskell code:

a = map (\ a -> a * 2 ) [1..10]

vs

squareIt x = x * 2
a = map squareIt [1..10]

Both samples will do the same thing: take a list containing the numbers 1 through 10 and returns a list containing each number squared. If I only have the ability to use lambdas, I can certainly write this:

a = map (\ a -> squareIt a ) [1..10]

That's perfectly fine in situations where we're dealing with things that won't or probably shouldn't change, but its just syntactic sugar for something we can already write quite easily.

Having true first class functions/mixins, on the other hand, opens the door for greater flexibility in customizing mixins in ways we never could before.

@mixin prettyElementA {
    border: 1px solid;
}

@mixin prettyElementB {
    background: yellow;
}

@mixin coolWidget($block, $list) {
    ul, ol {
        @include $list;
        padding: 1em;
    }

    div {
        @include $block;
        border: 1px solid green;
    }

    ul, ol, div {
        @content;
    }
}

.myWidget {
    @include coolWidget(prettyElementA, prettyElementB) {
        color: red;
    }
}

Right now, the closest I can come to making the coolWidget mixin customizable is by hard coding the mixin calls and encouraging overwriting of those mixins. But once the mixins have been overwritten, I can't get them back.

If I had to choose between lambdas or first class functions, I wouldn't even have to think about it. First class functions are by far more powerful and more useful.

@nex3

This comment has been minimized.

Show comment Hide comment
@nex3

nex3 Jan 15, 2013

Contributor

@cimmanon The functionality you're discussing is more germane to #626.

Contributor

nex3 commented Jan 15, 2013

@cimmanon The functionality you're discussing is more germane to #626.

@FWeinb

This comment has been minimized.

Show comment Hide comment
@FWeinb

FWeinb Mar 24, 2014

With the introduction of call and function-exists this seams to be the next step for Sass to get some more functional programming abilities.
Is this still planed for the language?

FWeinb commented Mar 24, 2014

With the introduction of call and function-exists this seams to be the next step for Sass to get some more functional programming abilities.
Is this still planed for the language?

@ncoden

This comment has been minimized.

Show comment Hide comment
@ncoden

ncoden Apr 30, 2016

This issue was opened in Aug 6, 2012.

Sass lack of many logical basic features, like:

  • anonymous function
  • functions without return
  • really abstract components (mix between placeholders and mixins)
  • namespaces
  • standard library for basic features (map, list)

Sorry to be crude, but i'm very surprised too see a so used tool to stagnate like that. Is there reasons for this lack of features that I don't know ?

ncoden commented Apr 30, 2016

This issue was opened in Aug 6, 2012.

Sass lack of many logical basic features, like:

  • anonymous function
  • functions without return
  • really abstract components (mix between placeholders and mixins)
  • namespaces
  • standard library for basic features (map, list)

Sorry to be crude, but i'm very surprised too see a so used tool to stagnate like that. Is there reasons for this lack of features that I don't know ?

@chriseppstein chriseppstein referenced this issue May 13, 2016

Open

Dynamic @include. Closes #626. #2057

7 of 8 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment