Search code examples
dartlanguage-features

How to do lazy evaluation in Dart?


Is there a native (language supported) lazy evaluation syntax? Something like lazy val in Scala.

I've gone through the docs, and could not find anything. There is only a chapter about "lazily loading a library", but it's not what I am asking.

Based on this research I incline to believe (please correct me if I'm wrong) that currently there is no such thing. But maybe you know of any plans or feature requests which will provide the functionality? Or maybe it was considered and rejected by the Dart team?

If indeed there is no native support for this, then what is the best practice (best syntax) for implementing lazy evaluation? An example would be appreciated.

Edit:

The benefits of the feature that I am looking for are mostly the same as in implementation in other languages: Scala's lazy val or C#'s Lazy<T> or Hack's __Memorize attribute:

  1. concise syntax
  2. delayed computation until the value is needed
  3. cache the result (the by-need laziness)
  4. don't break pure functional paradigm (explanation below)

A simple example:

class Fibonacci {

  final int n;
  int _res = null;

  int get result {
    if (null == _res) {
      _res = _compute(this.n);
    }
    return _res;
  }

  Fibonacci(this.n);

  int _compute(n) {
    // ...
  }
}

main(List<String> args) async {
  print(new Fibonacci(5).result);
  print(new Fibonacci(9).result);
}

The getter is very verbose and has a repetitive code. Moreover I can't make the constructor const because the caching variable _res has to be computed on demand. I imagine that if I had a Scala-like lazy feature then I would also have language support for having a constant constructor. That's thanks to the fact, that the lazy evaluated _res is referentially transparent, and would not be in the way.

class Fibonacci {

  final int n;
  int lazy result => _compute(this.n);

  const Fibonacci(this.n);  // notice the `const`

  int _compute(n) {
    // ...
  }
}

main(List<String> args) async {
  // now these makes more sense:
  print(const Fibonacci(5).result);
  print(const Fibonacci(9).result);
}

Solution

  • update2

    From @lrn s comment - using an Expando for caching makes it work with const:

    class Lazy<T> {
      static final _cache = new Expando();
      final Function _func;
      const Lazy(this._func);
      T call() {
        var result = _cache[this];
        if (identical(this, result)) return null;
        if (result != null) return result;
        result = _func();
        _cache[this] = (result == null) ? this : result;
        return result;
      }
    }
    
    
    defaultFunc() {
      print("Default Function Called");
      return 42;
    }
    main([args, function = const Lazy(defaultFunc)]) {
      print(function());
      print(function());
    }
    

    Try it in DartPad

    update

    A reusable Lazy<T> could look like below in Dart but that also doesn't work with const and can't be used in field initializers if the calculation needs to refer instance members (this.xxx).

    void main() {
      var sc = new SomeClass();
      print('new');
      print(sc.v);
    }
    
    class SomeClass {
      var _v  = new Lazy<int>(() {
        print('x');
        return 10;
      });
      int get v => _v();
    }
    
    class Lazy<T> {
      final Function _func;
      bool _isEvaluated = false;
      Lazy(this._func);
      T _value;
      T call() {
        if(!_isEvaluated) {
          if(_func != null) {
            _value = _func();
          }
          _isEvaluated = true;
        }
        return _value;
      }
    }
    

    Try it in DartPad

    original

    Dart version of http://matt.might.net/articles/implementing-laziness/ using a closure to lazy evaluate:

    void main() {
      var x = () { 
        print ("foo"); 
        return 10; 
      }();
      print("bar");
      print(x);
      // will print foo, then bar then 10.
      print('===');
      // But, the following Scala program:
      x = () { 
        print("foo"); 
        return 10; 
      };
      print ("bar");
      print (x());
      // will print bar, then foo, then 10, since it delays the computation of x until it’s actually needed.
    }
    

    Try it in DartPad