Back in the day when I was making videogames ( http://www.mysterystudio.com ), I implemented something similar for my framework. Most of the framework was declarative. There were Sprite objects that had an associated Image and a position (among other things). The onUpdate(dt) method of the "screen" didn't explicitly draw things on the screen, it only updated positions and other attributes of the Sprites, and the rend…
The unreasonable effectiveness of declarative programming
121–128 of 128 posts
Re: The unreasonable effectiveness of declarative programming
#122I'm curious about how one is supposed to reason about the time and space complexity of declarative programs. I don't work in a realm where CPU and memory costs can be assumed to be infinite (or even cheap). How do declarative programming paradigms typically offer guarantees or bounds on computation cost? I'm assuming that any declarative language powerful enough for general use is expressive enough to represent an NP…
Re: The unreasonable effectiveness of declarative programming
#123Earlier quoted context omitted.
C doesn't have first-class functions, because you can't define new functions in general places (only at top level).
The definition of first-class functions is the ability to treat functions as data, which C supports. Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. GCC even allows nested function definitions.
It's not just syntax sugar. Try writing a function that takes an array of integers and an integer x, and sorts the array mod x by calling qsort. In C this is not just cumbersome but impossible.
Re: The unreasonable effectiveness of declarative programming
#124Earlier quoted context omitted.
The definition of first-class functions is the ability to treat functions as data, which C supports. Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. GCC even allows nested function definitions.
> Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. It's not just syntax sugar. Try writing a function that takes an array of integers and an integer x, and sorts the array mod x by calling qsort. In C this is not just cumbersome but impossible.
static int reg;
int cmp(int a, int b) {
return a%reg - b%reg;
}
void qsortModX(int[] a, size_t len, int x) {
int tmp = reg;
reg = x;
qsort(a, len, sizeof(*a), cmp);
reg = tmp;
}
We could even wrap qsort so that you could pass something much closer to a closure to it : static void* g_ctx;
static int(*g_cmp)(void*, const void*, const void*)
int compare(const void* a, const void* b) {
return g_cmp(g_ctx, a, b) ;
}
void qsort_cls(void*[] a, size_t len, size_t elem, int(*cmp)(void*, const void*, const void*), void* ctx) {
void* tmp = g_ctx;
g_ctx = ctx;
int(*tmp_f)(void*, const void*, const void*) = g_cmp;
g_cmp = cmp;
qsort(a, len, sizeof(*a), compare);
g_ctx = tmp;
g_cmp = tmp_f;
}
With this, you can define a `struct closure` that encapsulates a function and some data and use that to pass to qsort_cls. You can even make it thread safe by using thread local variables instead of globals.Basically, if you want higher-order functions in C, you can do it, but you need to take a context pointer and a function which accepts a context pointer. The writers of qsort didn't think of that, so we had to resort to global variables to do it, but you could also re-write qsort to avoid the need for the global variable.
As I said, we're missing a lot of syntax sugar, but we can still work with functions as first class objects in pure C.
Re: The unreasonable effectiveness of declarative programming
#125Earlier quoted context omitted.
> Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. It's not just syntax sugar. Try writing a function that takes an array of integers and an integer x, and sorts the array mod x by calling qsort. In C this is not just cumbersome but impossible.
We can do it with a global variable. static int reg; int cmp(int a, int b) { return a%reg - b%reg; } void qsortModX(int[] a, size_t len, int x) { int tmp = reg; reg = x; qsort(a, len, sizeof(*a), cmp); reg = tmp; } We could even wrap qsort so that you could pass something much closer to a closure to it : static void* g_ctx; static int(*g_cmp)(void*, const void*, const void*) int compare(const void* a, const void* b)…
Re: The unreasonable effectiveness of declarative programming
#126Looking at the first code sample, I wouldn't call it declarative at all. For me, the defining feature of declarative code, is that it doesn't have a list of actions to be performed one after another. That code sample is such a list of actions, which for me makes it imperative code, meaning "first do this, then do that, then do the other thing." The "list of actions" approach is what makes code complexity grow exponen…
> That code sample is such a list of actions, which for me makes it imperative code, meaning "first do this, then do that, then do the other thing." Animation at its core is a sequence of images. Imperative animation code would describe how those images change every frame. (For example, a for loop in which you multiply properties by i in order to change them over time). Declarative animation code would let you define…
Re: The unreasonable effectiveness of declarative programming
#127Earlier quoted context omitted.
We can do it with a global variable. static int reg; int cmp(int a, int b) { return a%reg - b%reg; } void qsortModX(int[] a, size_t len, int x) { int tmp = reg; reg = x; qsort(a, len, sizeof(*a), cmp); reg = tmp; } We could even wrap qsort so that you could pass something much closer to a closure to it : static void* g_ctx; static int(*g_cmp)(void*, const void*, const void*) int compare(const void* a, const void* b)…
What you're proposing requires extra work to be threadsafe, is even less typesafe than normal C functions (you've lost checking that `ctx` is actually an `int`), requires you to reimplement it for each standard function you want to use, and is significantly syntactically more cumbersome even after you've done all that. If that's "first class" then how bad would things have to get before you called them "second class"…
I would note though that with C's type system, you always have to choose between type safety and genericity, this is not exclusive to higher order functions. C doesn't have a notion of threads or thread safety, so talking about thread safety in pure C does not make sense. And the fact that the designers of the stdlib didn't think about supporting closures still doesn't mean that the language itself doesn't support them. Other foundational libs, like pthreads, do have support for this style of closures built in.
Re: The unreasonable effectiveness of declarative programming
#128Earlier quoted context omitted.
What you're proposing requires extra work to be threadsafe, is even less typesafe than normal C functions (you've lost checking that `ctx` is actually an `int`), requires you to reimplement it for each standard function you want to use, and is significantly syntactically more cumbersome even after you've done all that. If that's "first class" then how bad would things have to get before you called them "second class"…
I am sympathetic to what you're saying, and in the end this is just a matter of definitions. I would note though that with C's type system, you always have to choose between type safety and genericity, this is not exclusive to higher order functions. C doesn't have a notion of threads or thread safety, so talking about thread safety in pure C does not make sense. And the fact that the designers of the stdlib didn't t…
True, but functions defined via some kind of "struct closure" scheme are non-typesafe even when monomorphic (e.g. if all the types are int).
> C doesn't have a notion of threads or thread safety, so talking about thread safety in pure C does not make sense.
I'd hold that a function that relies on a global variable is noticeably second-class in a number of ways. Multithreading is one place where this comes up, but you also have to be careful about using it in a recursive context, or use in a library that might be called from more than one place.
> And the fact that the designers of the stdlib didn't think about supporting closures still doesn't mean that the language itself doesn't support them.
Any Turing-complete language "supports" any feature of any other language in a sense, because it's always possible to emulate that other language. If we say functions are first class then we mean not only that it's possible to represent functions as values (because that will always be possible), but that functions represented this way are just as good values as the language's built-in notion of values, and just as good functions as the language's built-in notion of functions.