On Friday, 30 August 2013 at 21:11:32 UTC, Andrei Alexandrescu wrote:
* scope: cute and dangerous in equal proportions - great for a movie character, terrible for language design.

I'll argue for it and others can then destroy.

IME, a lot of the value of functional programming (by which I mean, programming with higher order functions) can be had with downward funargs, as found in Pascal (and more recently, Ada), which don't require garbage collection.

The D language expresses closures and higher order functions as delegates, and uses the heap to manage these. I had assumed that we could use 'scope' on the delegate being passed to prevent it's heap allocation, as discussed here:

http://stackoverflow.com/questions/4711309/meaning-of-scope-in-d-for-a-parameter

I wrote a small test case to try this out, in which I construct a 2-D integration function from a 1-D one, and I use Adam Ruppe's nogc code to seg fault when the GC is hit. Without using a 'scope' on a local variable, I'm forced to pile up the anonymous function calls to get the behavior I want.

import std.stdio, std.math;

// int delegate(int) adder(int a) { return (int b) { return a + b;}; }

float integrate(scope float delegate(float x) f, float lo, float hi, size_t n) {
  float result = 0.0;
  float dx = (hi - lo) / n;
  for (size_t i = 0; i < n; i++) {
    result += f(lo + i * dx) * dx;
  }
  return result;
}

float integrate(scope float delegate(float, float) f,
                 float x0, float x1,
                 float y0, float y1,
                 size_t nX, size_t nY) {
  scope auto f_y = delegate float(float y) =>
    integrate(delegate(float x) => f(x,y), x0, x1, nX);

  return integrate(f_y, y0, y1, nY);

// I'll have to write it like this to avoid heap allocation without
  // local variable scope
// return integrate((y) => integrate((x) =>f(x,y), x0, x1, nX), y0, y1, nY);
}

The code to test demonstrates the issue better, as I'm forced to use inline anonymous functions everywhere to avoid the GC calls (unitCircle and unitSphere omitted for brevity)


void main() {
  scope auto funcX_1 = delegate float(float x) => unitCircle(x);
float result1 = integrate(funcX_1 /* (x) => unitCircle(x) */, -1.0, 1.0, 500);
  writefln("integrate(func1, -1.0, 1.0, 500) = %g", result1);

scope auto funcXY_1 = delegate float(float x, float y) => unitSphere(x,y); result1 = integrate(funcXY_1 /* (x,y) => unitSphere(x,y) */, -1.0, 1.0, -1.0, 1.0, 100, 100); writefln("integrate(funcXY_1, -1.0, 1.0, -1.0, 1.0, 100, 100) = %g", result1);
}

Yes, it's a toy example, but I do program with higher order functions a lot in OCaml and I'd probably use them quite a bit in D, especially if I could avoid impacting the GC.

If scope on local variables is going away, it would be nice if the compiler could figure out when I'm using delegate args in a 'downward, non escaping' way and not heap allocate. My tests with DMD indicate that without those 'scope' on the local variable the GC does in fact get hit, and with them it does not.

-- Brian

PS: I don't really use classes much, so I have little to say about scope on objects

PPS: If my missive made little sense,

https://en.wikipedia.org/wiki/Funarg_problem
http://stackoverflow.com/questions/581182/what-are-downward-funargs







Reply via email to