5 ms·
I must admit that I never understood what these particular examples had to do with Lisp being such a great programing language given that you can implement exac
by simias 6y ago
I must admit that I never understood what these particular examples had to do with Lisp being such a great programing language given that you can implement exactly the same logic in basically any programing language, including for instance C (although you'd have to cheat a bit for the `deriv` function, admittedly):
#include <stdio.h>
#define DX 0.0001
#define DERIV(_f) float deriv_##_f(float x) { return (_f(x + DX) - _f(x)) / DX; }
float cube(float x) { return x * x * x; }
DERIV(cube)
int main(void) {
printf("%f\n", deriv_cube(2));
printf("%f\n", deriv_cube(3));
printf("%f\n", deriv_cube(4));
}
Any language supporting closures or generics could get rid of the unsexy DERIV macro.
What sets Lisp apart is its "code as data" approach which lets the coder write incredibly powerful macro (and also incredibly hard to understand code). The rest can easily be emulated in other languages.
I do like this naive approach mind you, there's a certain Zen to be found reimplementing these mathematical concepts from first principles, I just really fail to see what makes Lisp so remarkable in this context.
- Turing_Machine 6y agoWell, the major difference here is that your C method requires that all this stuff be defined at compile time, while the Scheme deriv works with even functions that are dynamically defined at run time, anonymous functions, etc. I'm not sure how you would do that in C, other than by Greenspunning up an implementation of first-class functions.
- xirbeosbwo1234 6y agoYou can get most of the way there with function pointers. float deriv(float (*f)(float), float x) { return (f(x + DX) - f(x)) / dx; } I can't think of a way to make a deriv function that returns the derivative, only one that returns the derivative at one particular point.
- whatshisface 6y agoIn C you would normally do this with functions that accepted function pointers, not a #define.
- vitus 6y agoSort of. You'd be able to define double fprime(double (*f)(double), double x); but you wouldn't be able to define double (*deriv(double (*f)(double)))(double); as C does not support closures. Meanwhile, in C++ you might use std::function<double(double)> deriv(std::function<double(double)> f);
- simias 6y agoI wanted to stick to the original as close as possible, I agree that my code isn't very idiomatic (or even generally a good idea).
- souprock 6y agoDoing "code as data" doesn't seem too difficult. The obvious way for C++ would be to link with llvm. That would let you generate strings of C++ that you can then compile and run. Being less strict about the requirements, it is not so hard for C. You'd write out a C file, compile and link it into something appropriate (.so or .dll or *.dylib), and load it. If you don't insist on the code being in the same language, it's even easier. Emit some native code, then call it. Every JIT does this.
- dunefox 6y agoYes, indeed, Code as Data isn't difficult if you just do something else. Your example is completely different from what the concept means in Lisp.
- souprock 6y agoIt's really not different, particularly the first example. If you're going to claim it is different, then I claim that LISP can't do addition. When you have (+ 1 1) that is something completely different, because you have the syntax fucked up. It's not addition like I know it, so it doesn't count.
- dunefox 6y agoIt's very different since Lisp doesn't 'compile and run strings', it treats code as data structures - which means you can manipulate code just like data since they live in the same space, so to speak. And also, it has nothing to do with the syntax (+ 1 1), the keyword is homoiconicity.
- souprock 6y agoYou mean a toy LISP or Scheme? Any serious LISP system uses a compiler. It compiles and runs strings. The "eval" creates native binary executable code. No decent-performing system totally skips compilation. You just don't notice that it is happening. Even a web browser compiles the javascript before running it; that is what a JIT does. I mentioned the syntax "(+ 1 1)" because you're trying to disqualify C++ homoiconicity on a superficial syntactic basis. If you can do that, then it is equally valid to say that LISP can't do simple addition.
- rusk 6y agoYou could do deriv properly in C if you have support for “blocks”; otherwise you’ll have to store your state in a struct. I really wouldn’t have got this stuff 5 years ago but functions returning functions is a super sweet tool and I’m glad it’s finally seeing mainstream adoption. It’s a small leap from the imperative mindset but results in much neater flows when done well.
- yudlejoza 6y agoI'm a big fan of C but, to be fair, your example makes border-line abuse of the macro system. In my opinion, the example doesn't show that C language per se (without the preprocessor part) is capable of higher-order functions (but as others have pointed out, it is, in the form of function pointers).
- badsectoracula 6y agoThe preprocessor is part of the language. Also there is no really such a thing as "abuse" of the macro system, this is taking advantage of a language facility to do something one wants. It is like saying someone abuses the type system when they define a handle type or abusing the namespace system when they define a static variable. C's preprocessor is very powerful and like the rest of the language it can be used to shoot yourself in the foot - but that doesn't mean you shouldn't take full advantage of it or that actually taking advantage of it instead of treating it as a poor man's module system is "abusing" it.
- pjmlp 6y agoLisp was doing that 10 years before C was even an idea.