assert() vs. enforce(), invariant() vs. ... ?

Brian Rogoff brogoff at gmail.com
Sat Aug 31 13:34:09 PDT 2013


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









More information about the Digitalmars-d mailing list