17 ms·
Lisp for C++ programmers
- mrottenkolber 13y agoI won't go into details about deficiencies of Lisp. That's another problem to be discussed; an extremely painful problem, in fact. I don't buy it. Nobody wants to talk about these apparently obvious (to non Lispers) problems of Lisp, yet they are still there? Bring them up and be argued with or be quiet. Later he talks about the ugliness of Lisp syntax, especially CL, but Scheme and Clojure are supposed to be better. On what basis? I may admit scheme is elegant, but calling clojure's syntax an improvement over CL is a pretty long jump.
- cbp 13y ago> Later he talks about the ugliness of Lisp syntax, especially CL, but Scheme and Clojure are supposed to be better. On what basis? I may admit scheme is elegant, but calling clojure's syntax an improvement over CL is a pretty long jump. Having syntax for 4 different data structures instead of just one would be one of my main reasons. Also, the syntax of a lisp-1 is cleaner.
- mrottenkolber 13y agoBecause what if there where 6 equally important datastructures, or 100? The only datastructure you need to express when you are programming is the AST, in my humble opinion, and lists serve it well, visual trees might be interesting. The Lisp-2 design has a very good practical reason: 50% of a common type of mistake are impossible to make. More namespaces don't hurt a language, while less namespaces usually do (Javascript for instance).
- Jtsummers 13y agoWhile I like scheme and clojure, why do you think lisp-1's are cleaner? Perhaps it was just my order of learning, but I rather liked the different namespaces CL provided, primarily as it eliminated name collisions between variables and functions. If list were an appropriate variable name, I could call a thing list versus the abbreviated form lst in scheme (that's a trivial example, but I recall more name collisions back when I was using scheme and CL as hobby languages). I guess, syntax-wise, the main difference is no longer needing funcall, which was a headache when I was learning CL.
- blueblob 13y agoI think many people that don't like lisp haven't spent much time with it. I personally do not like lisp. I took an artificial intelligence class where we wrote search techniques in common lisp. One problem that I had with lisp is the end of my A* search had a massive number of parenthesis at the end. I think this would have been a little better if we were taught about common lisp structs earlier on. My next big problem was that I was coming from C where you occasionally put extra parenthesis in to group portions of code (sometimes necessary in macros or if statements). This is a very bad habit in lisp because it tries to call a function.
- dllthomas 13y agoThere've been dialects of Lisp where a single ] would close all outstanding open parentheses. Having never actually used one, I reserve judgment on whether this is a good or bad idea.
- AUmrysh 13y agoI think, just like any other syntactic sugar, it's fine if used with proper understanding of possible consequences. I'd like to see lisp use indention instead of parens like Python. I'm sure someone's done it and it isn't popular for some reason or another, but I think getting rid of the parens would go a long way to making people stop hating LISP.
- blueblob 13y agoI don't see how lisp could get rid of parends with indentation. Parends are used for function calls so you would then have to indent one extra level to call a function? If there was a workaround for the function calls, then it would also require every function to be written on a separate line, there would be no concept of a one liner.
- dllthomas 13y agoBuilding a tree with indentation is easy. You would need some method to distinguish between a one element list and a value, and some method of specifying an empty list (though you could just write nil). One option that leaves everything completely uniform is to use something that says "this starts a list", and then have the (zero or more) elements indented including the list head if present. If we use (), this would read like: () prin1 () * () + 3 4 7 Exceedingly vertical, obviously, if you don't allow inline parens at all. If you were, you could compress any run of lines by simply moving everything with no children into the parent parens. Applying that only to the deepest bit of the above, we'd get: () prin1 () * (+ 3 4) 7 Of course, the original could be transformed into actual lisp by simply moving end-parens: ( prin1 ( * ( + 3 4 ) 7 ) )
- rsynnott 13y ago> but calling clojure's syntax an improvement over CL is a pretty long jump. Well, it has all of _three_ types of brackets, which may be reassuring to the C++ programmer.
- rayiner 13y agoIt seems as if every new iteration of C++ proves Greenspun more and more correct. The standards committee has been trying very hard to push templates as an "ad hoc, informally-specified, bug-ridden, slow implementation" of macros. As for Lisp syntax being ugly, I beg to differ. (Common) Lisp syntax is somewhat arcane, with terminology and abbreviations that aren't common these days, but it reads pretty smoothly. You know what's ugly? C++ lambda syntax: [=]() mutable throw() -> int At some point C++ started following Perl down the road of using as many symbols above the number keys as possible. That's ugly.
- detrino 13y agoYou are being intentionally dishonest by using the most verbose lambda expression you can think of. It would be very unusual to write something like that. You even managed to include one of the rare deprecated language features.
- rayiner 13y agoThe example is from the first hit for a Google search for "C++ lambda syntax" (an MSDN article). In any case, unusual language features still are part of the syntax. They must be parsed and they must be understood by programmers in case they come across them. The ugly parts of a language don't go away just because you don't use them that often. Also, I'm not sure what you're objecting to. The throw clause? The ugly part here is the overloading of the brackets and equal sign to specify the capture list.
- detrino 13y ago"[=]" Capture all variables by value, fair enough. "mutable" Don't capture them const, this is very rare. "throw()" Promise not to throw an exception, this is very rare for a lambda, and also deprecated in favor of noexcept. "-> int" Explicit return type, usually not needed, and nearly never needed in C++14. I was objecting to a contrived example used out of context to make C++'s syntax look worse than it is.
- 13y ago
- dllthomas 13y ago"In fact, many people claim that Lisp "has no syntax". This really means that Lisp has uniform syntax: every piece of code is just a list delimited with parentheses." For an appropriate definition of "uniform syntax", perhaps. What I mean when I say "lisp has no syntax" is that you effectively write out the AST. If the syntax were somehow uniform but had a radically different structure than the AST I would claim that Lisp has syntax.
- chimeracoder 13y ago> What I mean when I say "lisp has no syntax" is that you effectively write out the AST Agreed. This is a very common misunderstanding of the statement, but understanding the meaning of the statement correctly demonstrates the single reason that Lisp is so powerful: its homoiconicity, and everything that implies.
- spdegabrielle 13y agoRacket [http://www.racket-lang.org http://www.racket-lang.org] is a modern Lisp with STATIC TYPING. Quote from the static typing example on home page: Racket's type system is designed to let you add types after you've worked for a while in untyped mode — even if your untyped program wouldn't fit nicely in a conventional type system. #lang typed/racket ;; Using higher-order occurrence typing (define-type SrN (U String Number)) (: tog ((Listof SrN) -> String)) (define (tog l) (apply string-append (filter string? l))) (tog (list 5 "hello " 1/2 "world" (sqrt -1))) To run the example, install Racket, start DrRacket, paste the example program into the top area in DrRacket, and click the Run button. Alternatively, save the program to a file and run racket on the file.
- brudgers 13y agoHell, you don't even need to use typed/racket to implement static types. You can just use contracts with plain old Racket to achieve true lispyness...because contracts let you impose static type-checking selectively within your code and for staticly-typed code to symbiose with dynamically-typed code. Racket Guide on contracts: http://docs.racket-lang.org/guide/contracts.html http://docs.racket-lang.org/guide/contracts.html Racket Reference on contracts: http://docs.racket-lang.org/reference/contracts.html http://docs.racket-lang.org/reference/contracts.html There's even this groovy academic paper: http://www.eecs.northwestern.edu/~robby/pubs/papers/oopsla2012-stff.pdf http://www.eecs.northwestern.edu/~robby/pubs/papers/oopsla20... It almost goes without saying that contracts are the mechanism by which typed/racket is statically typed.
- hajile 13y agoShen is a lisp dialect with a turing complete type system and lazy typing (both of which are optional for maximum programmer flexibility). http://shenlanguage.org/index.html http://shenlanguage.org/index.html
- xenophonf 13y agoCommon Lisp is a modern Lisp with static typing, too. For example, here's compile-time type checking performed by the SBCL compiler: * (defun test (x) (declare (fixnum x)) (the string x)) ; in: DEFUN TEST ; (THE STRING X) ; ; caught WARNING: ; Derived type of X is ; (VALUES FIXNUM &OPTIONAL), ; conflicting with its asserted type ; STRING. ; See also: ; The SBCL Manual, Node "Handling of Types" ; ; compilation unit finished ; caught 1 WARNING condition STYLE-WARNING: redefining COMMON-LISP-USER::TEST in DEFUN TEST * (test 1) debugger invoked on a SIMPLE-TYPE-ERROR: Value of X in (THE STRING X) is 1, not a STRING. Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL. restarts (invokable by number or by possibly-abbreviated name): 0: [ABORT] Exit debugger, returning to top level. (SB-C::%COMPILE-TIME-TYPE-ERROR (1) STRING #<unavailable argument> ((THE STRING X) X)) 0] 0
- brudgers 13y agoCalling "strawman" on: Which is more readable: The article misformats the example Lisp code [at least in two of my browsers]: (aset a x (+ (aref a x) (aref b (- x 1)) (* 3 (aref b (+ x 1))))) should be: (aset a x (+ (aref a x) (aref b (- x 1)) (* 3 (aref b (+ x 1)))))
- S4M 13y agoYeah, I wanted to point it out as well, but I realized it's the text renderer that didn't format his expression correctly. If you do a copy-paste, you'll see that he indented it properly.
- brudgers 13y agoI did in fact copy and paste the code directly from the article into my comment above. The only reformatting I did was to add the same number of spaces to the beginning of each line in order to have HN display it as code. If I was being pedantic about the formatting, the error would be excusable. But the code is presented as evidence that Lisp code is hard to read, so the poor formatting is highly relevant. Furthermore, when presenting C++ code earlier in the article, the formatting is correct: typedef std::map<std::string, int>::value_type vtype; vtype things[] = [vtype("one", 1), vtype("two", 2), vtype("three", 3)]; std::map<std::string, int> m3(&things[0]
- EpicEng 13y agoPerhaps I'm biased, but that hardly helps. Still far less readable than the C++ version which looks almost exactly how you would write out a math problem.
- michaelfeathers 13y agoIt would be interesting to write a steganographic C++ program which is a valid lisp program when everything but identifiers and parentheses are stripped away.
- greyfade 13y agoThat would be fairly easy if each line were prepended by a semicolon.
- deleted 13y ago[deleted]
- ufo 13y agoOne thing I still miss from this and most other Lisp tutorials is good practical examples of problems you would solve with macros. Its one of the biggest differences between Lisp and other languages but I have no idea where I should use macros and where I should simply use functions instead. For example, in this tutorial he shows a stopwatch macro but this is precisely the sort of stuff that is easy to do with lambdas: (define stopwatch (body) (start-timer) (body) (end-timer)) (stopwatch (lambda () (my_code 42)))
- rayiner 13y agoMacros unfortunately make themselves most useful in cases that don't lend themselves to short snippets. My favorite example involves looking at some real code. Compare how to add a new intrinsic to LLVM: http://llvm.org/docs/ExtendingLLVM.html http://llvm.org/docs/ExtendingLLVM.html versus how to do it for SBCL: http://pvk.ca/Blog/Lisp/hacking_SSE_intrinsics-part_1.html http://pvk.ca/Blog/Lisp/hacking_SSE_intrinsics-part_1.html. Look at how SBCL's x86-64 backend defines the x86-64 register set and specifies what kind of values can go in what registers: https://github.com/sbcl/sbcl/blob/master/src/compiler/x86-64/vm.lisp https://github.com/sbcl/sbcl/blob/master/src/compiler/x86-64.... Compare that to the equivalent for LLVM: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/X86/X86RegisterInfo.td?revision=187030&view=markup http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/X8.... The register descriptions are written in a very similar, declarative, way, except the SBCL code uses macros built into the language while the LLVM code uses a separate tool called TableGen that generates C++ code from the declarative descriptions. Using a separate tool means complicating the build process, having an ad-hoc parser/expander that can be a source of bugs, losing access to compiler facilities that allow you to step through the expansions and look at the expanded code, etc. If you can't get through the prefix syntax, look at how the OpenDylan compiler uses a macro to define the x86 instruction set: https://github.com/dylan-lang/opendylan/blob/master/sources/harp/x86/instructions.dylan https://github.com/dylan-lang/opendylan/blob/master/sources/... Also see the x86-64 assembler I wrote in Common Lisp: https://github.com/rayiner/amd64-asm/blob/master/encoders.lisp https://github.com/rayiner/amd64-asm/blob/master/encoders.li.... Specifically, lines 374-504, 621+. Each instruction description basically specifies a list of alternative patterns that are matched against the assembly source. When a pattern matches, it is handed off to a compiled function that generates the actual encoded bytes. That function is automatically generated at compile-time from the declarative list of patterns. Not exactly brilliant code like you'll find in SBCL, but in a couple of weekends I was getting bit-for-bit identical assembly to YASM over a substantial subset of the instruction set. Part of the testsuite uses the same instruction descriptions that are used to generate the encoder: https://github.com/rayiner/amd64-asm/blob/master/testsuite.lisp https://github.com/rayiner/amd64-asm/blob/master/testsuite.l... (Line 128).
- cageface 13y agoUntil I can write something like Ableton Live in any Lisp I'll pass, thanks.
- Jtsummers 13y agoI'm trying to figure out why you posted this, it just seems like a non sequitur. Setting aside, for a moment, the issue of performance on signal processing tasks, from a GUI perspective there's no reason that an interface akin to Ableton Live shouldn't be producible in Common Lisp, Clojure or any currently maintained Scheme implementation. If you're dependent on 3rd party C/C++ libraries for the signal processing, many schemes are pretty good for FFI, I've not used Clojure FFI or Common Lisp FFI so I can't comment much on them. Clojure seems like it would be as good or at least no worse than Java for interfacing with existing C/C++ libraries. Common Lisp FFI is going to be implementation dependent (like with scheme). I do know that Chicken Scheme's FFI is pretty easy. I made a wrapper for a subset of GLUT and OpenGL, and adding additional components as they became relevant to the project was straightforward. Regarding performance, I won't comment on Common Lisp or Clojure, I never measured their performance for numeric tasks. Scheme, however, with something like Chicken Scheme has good performance, and again FFI is straightforward so making a C module to do some heavy lifting is not unreasonable.
- cageface 13y agoClearly the signal processing/audio side of the app can't be written in Lisp, and that actually makes up a pretty large chunk of the code. And having to bridge everything over FFI really isn't very practical for a UI of this complexity either for performance and code overhead reasons. Being able to access your internal data structures directly without jumping through FFI/translation hoops is a big win for both efficiency and code maintenance. So Lisp zealots can ding my comment all they want but it doesn't change the fact that intelligent, informed programmers all over the world continue to reject Lisp for very practical reasons that the true believers of the Lisp community will never understand.
- route66 13y agoIn the meantime you might want to notate some ideas: http://scorecleaner.com/ http://scorecleaner.com/ (uses also C++) or do yourself a bit of algorithmic composing (http://www.symboliccomposer.com/page_main.shtml, http://www.symboliccomposer.com/page_main.shtml,. since the 90's). And together we wait impatiently for http://opusmodus.com/ http://opusmodus.com/ to be available (down for maintenance right now).
- avifreedman 13y agoIt's more likely to be GC that makes Lisp unattractive as an alternate to C++ for the things for which I'd normally use C/C++. I have spent way too much time wrangling Wowza, Hadoop, and other things written in Java in the last years, and have yet to see a good incremental garbage collector in a JVM to support things that do gigabits of throughput and need to do it every second day in+out. Pointers to the contrary are welcome...
- pbsd 13y agoThis is mostly orthogonal to the article's point, but the motivating example ignores the actual syntax that already exists in C++(11) to initialize classes: std::map<std::string, int> m1 = { {"one", 1}, {"two", 2}, {"three", 3} }; It may be relevant to mention IntelLib [1] here, which is one of many (ab)uses of C++ to enact DSLs. [1] http://www.intelib.org/intro.html http://www.intelib.org/intro.html