37

After a few months of learning about and playing with Lisp, both CL and a bit of Clojure, I'm still not seeing a compelling reason to write anything in it instead of C#.

I would really like some compelling reasons, or for someone to point out that I'm missing something really big.

The strengths of Lisp (per my research):

  • Compact, expressive notation - More so than C#, yes... but I seem to be able to express those ideas in C# too.
  • Implicit support for functional programming - C# with LINQ extension methods:
    • mapcar = .Select( lambda )
    • mapcan = .Select( lambda ).Aggregate( (a,b) => a.Union(b) )
    • car/first = .First()
    • cdr/rest = .Skip(1) .... etc.
  • Lambda and higher-order function support - C# has this, and the syntax is arguably simpler:
    • "(lambda (x) ( body ))" versus "x => ( body )"
    • "#(" with "%", "%1", "%2" is nice in Clojure
  • Method dispatch separated from the objects - C# has this through extension methods
  • Multimethod dispatch - C# does not have this natively, but I could implement it as a function call in a few hours
  • Code is Data (and Macros) - Maybe I haven't "gotten" macros, but I haven't seen a single example where the idea of a macro couldn't be implemented as a function; it doesn't change the "language", but I'm not sure that's a strength
  • DSLs - Can only do it through function composition... but it works
  • Untyped "exploratory" programming - for structs/classes, C#'s autoproperties and "object" work quite well, and you can easily escalate into stronger typing as you go along
  • Runs on non-Windows hardware - Yeah, so? Outside of college, I've only known one person who doesn't run Windows at home, or at least a VM of Windows on *nix/Mac. (Then again, maybe this is more important than I thought and I've just been brainwashed...)
  • The REPL for bottom-up design - Ok, I admit this is really really nice, and I miss it in C#.

Things I'm missing in Lisp (due to a mix of C#, .NET, Visual Studio, Resharper):

  • Namespaces. Even with static methods, I like to tie them to a "class" to categorize their context (Clojure seems to have this, CL doesn't seem to.)
  • Great compile and design-time support
    • the type system allows me to determine "correctness" of the datastructures I pass around
    • anything misspelled is underlined realtime; I don't have to wait until runtime to know
    • code improvements (such as using an FP approach instead of an imperative one) are autosuggested
  • GUI development tools: WinForms and WPF (I know Clojure has access to the Java GUI libraries, but they're entirely foreign to me.)
  • GUI Debugging tools: breakpoints, step-in, step-over, value inspectors (text, xml, custom), watches, debug-by-thread, conditional breakpoints, call-stack window with the ability to jump to the code at any level in the stack
    • (To be fair, my stint with Emacs+Slime seemed to provide some of this, but I'm partial to the VS GUI-driven approach)

I really like the hype surrounding Lisp and I gave it a chance.

But is there anything I can do in Lisp that I can't do as well in C#? It might be a bit more verbose in C#, but I also have autocomplete.

What am I missing? Why should I use Clojure/CL?

talonx
  • 2,762
  • 2
  • 23
  • 32
  • If you miss Visual Studio features, you might want to look into [F#](http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/), which is a functional programming language for .NET. I've heard complaints that it gives up too much "functionalness" to work with .NET (it has mutable variables, etc), but I think it's worth a look. –  Feb 20 '11 at 16:53
  • 4
    Since you're already programming in a functional style and it sounds like you rely fairly heavily on the Windows infrastructure, I can't see any compelling reason for you to change from C#. Most everyone I know uses Mac or Linux, so a lot clearly depends on the crowd you run with (I've used every version of Windows since 3.1 but I still prefer working with cross-platform software and *nix systems in general... but then I don't do any native GUI work, all servers and web UI stuff) – Sean Corfield Feb 20 '11 at 17:41
  • 2
    You could have a look at [The Swine Before Perl](http://www.cs.brown.edu/~sk/Publications/Talks/SwineBeforePerl/). It's a fun talk to listen to, and presents a simple example of what macros are good for. – Matthias Benkard Feb 20 '11 at 20:14
  • 4
    "I haven't seen a single example where the idea of a macro couldn't be implemented as a function" -- I'd like to see how you'd write `AND` or `OR` as a function. It could be done (given `LAMBDA`, which is also a macro) but I don't see an obvious way to do it that wouldn't totally suck. –  Feb 21 '11 at 17:01
  • 1
    "Namespaces. ... Clojure seems to have this, CL doesn't seem to" -- can you be more specific about what you're looking for? I've not really used Clojure but its namespaces look pretty much just like CL's packages. –  Feb 21 '11 at 17:05
  • Namespaces, in the sense of taxonomy of functions and classes. One namespace for handling sequences/collections, one for handling core network protocols, one for handling each UI framework, with the idea of importing the namespaces you need for your project. In .NET, it's sort of a two-level process; you "reference" other assemblies/libraries on a per-project basis, and then on a per-file basis you select which namespaces you're using, and there are mechanisms for handling namespace conflicts in the same file (e.g., "Session" could be "Http.Session" or "Chat.Session") –  Feb 21 '11 at 17:39
  • Re: Macros, I definitely need to understand their power through experience. That seems to be my biggest failure thus far. (Further elaboration is in comments below.) –  Feb 21 '11 at 17:41
  • 4
    Jonathan: CL uses `:` (or `::` for un-exported symbols) for naming symbols in packages, e.g., your example here would use `http:session` and `chat:session`. –  Feb 21 '11 at 18:27
  • Rich Hickey does not have the same resources as MSFT. Imagine if he did ... It is hard to compete with Visual Studio, the IDE, but the .Net itself ... I can see the day when MSFT will support Clojure. – Job Feb 23 '11 at 02:48
  • "but I could implement it as a function call in a few hours" - I'd like to see that, actually. Double-dispatching is enough of a pain, and requires you to write specific visitors each time, that you have to edit each time you add new types. And for _triple_ dispatch? – Frank Shearar Apr 10 '11 at 07:27
  • If you list a procedural type system as a pro, try Haskell, you will love its type system (Which is so much better than any procedural static system!) – alternative Apr 20 '11 at 12:55
  • There is no language that is best in every aspect. The opposite is true. You need to specify what you want to use the language for. For example, if you are looking for a job as a web developer, the market for LISP is not as large as it is for C#.Net. However, if you are on your way for academia, then the argument may be reversed somewhat. – NoChance Sep 11 '11 at 05:33
  • @Ken: "I'd like to see how you'd write `AND` or `OR` as a function." Umm... I'd like to see why you would *want* to, given that they're both intrinsic operators on every language worth their salt. And if you really need an `OR` function, (for a reduce/fold operation, for example,) it's trivial to write a function that takes two parameters and applies the `OR` operator in pretty much any language. So what need is there for macros? – Mason Wheeler Jun 18 '12 at 18:13
  • Regarding the Visual Studio thing... 90% of what these IDEs do (and I'm a big IntelliJ IDEA user) is unnecessary when programming using Lisp dialects. I'm not saying they're not doing a few interesting things that Emacs cannot do: there **are** a few really cool things IDEs can do. All I'm saying is 90% of the stuff they do is unnecessary when using Lisp. The problem of these IDEs is that their "text editor" part does totally and utterly s*ck. vim/emacs are way faster at "text editing" (and many other things too). Visual Studio ain't doing half of what 1 million elisp lines are doing ; ) – Cedric Martin Jul 10 '12 at 15:36

7 Answers7

23

Macros let you write compilers w/o leaving the comfort of your language. DSLs in languages like C# generally amount to a runtime interpreter. Depending on your domain, that might be problematic for performance reasons. Speed does matter, otherwise you'd be coding in an interpreted language and not C#.

Also, macros also let you consider human factors - what is the most user-friendly syntax for a particular DSL? With C# there's not much you can do about the syntax.

So Lisp allows you participate in language design w/o sacrificing a path towards efficiency. And while these issues might not matter to you, they are extremely important as they underlie the foundations of all useful production-oriented programming languages. In C# this foundational element is exposed to you in a very limited form at best.

The importance of macros is not lost on other languages - Template Haskell, camlp4 come to mind. But yet again it's an issue of usability - Lisp macros to date are likely the most user-friendly yet powerful implementation of compile-time transformation.

So in sum what people generally do w/ languages like C# is build DSILs (Domain Specific Interpreted Languages). Lisp gives you the opportunity to build something much more radical, DSPs (Domain Specific Paradigms). Racket (formerly PLT-Scheme) is particularly inspiring in this regard - they have a lazy language (Lazy Racket), a typed one (Typed Racket), Prolog, and Datalog all embedded idiomatically via macros. All these paradigms provide powerful solutions to large classes of problems - problems not best solved by imperative programming or even FP paradigms.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 3
    What I find interesting about that argument is that I've never run into an opportunity (or just completely overlooked it in my ignorance) for implementing a DSL/DSIL. Could you elaborate on the power of DSLs? (For both a developer and a user.) This sounds like it might be the _big thing_ I'm really missing. –  Feb 20 '11 at 18:15
  • 15
    @Jonathan Mitchem, you're already using a lot of external DSLs - configuration files, build scripts, ORM configurations, visual code generators (e.g., winforms editor), XAML, parser generators, etc. Embedded DSLs are even better, as you don't need a separate build tool for them, and they're served within a single language. And you must be familiar with at least two of the embedded DSLs - regular expressions and SQL. – SK-logic Feb 21 '11 at 18:09
  • Wow, ok. I just never thought of those as DSLs. Now, I've intentionally strayed away from a lot of that, on the basis of "anything I can do with c#, I'm going to stick with c# for". I like sticking with one tool with a consistent behavior, personally. But I do understand the value of DSLs in these examples. –  Feb 21 '11 at 22:23
  • 2
    @Jonathan Mitchem, the problem with any given tool is that is won't necessarirly be fit for a given purpose. You have to choose different tools for different tasks. And the power of any meta-language, including Lisp, is that you don't have to fully switch to another tool, as you can grow your own domain-specific tools on top of your main language. I suggest that you take a look at Nemerle, it would be easier to understand it for a C# developer, and its macros are almost as powerful as Lisp's. – SK-logic Feb 22 '11 at 11:21
  • 3
    "I like sticking with one tool..." Properly crafted DSL's in Lisp never hide the full power of Lisp, so you still have one tool. They just add to the vocabulary in a way that makes coding for a particular domain more "natural", i.e. with better abstractions and overall organization. –  Feb 22 '11 at 17:28
15

Almost everything that was originally available only in Lisps has thankfully migrated into a lot of other modern languages. The biggest exception is the "code is data" philosophy, and macros.

Code is Data (and Macros) - Maybe I haven't "gotten" macros, but I haven't seen a single example where the idea of a macro couldn't be implemented as a function

Yeah, macros are hard to grok. Metaprogramming in general is hard to grok. If you saw any macro that could be implemented as a function, then the programmer was doing it wrong. Macros should only be used when a function won't work.

The "hello world" example of a macro is to write "if" in terms of cond. If you're really interested to learn the power of macros, try defining a "my-if" in clojure, using functions and watch how it breaks. I don't mean to be mysterious, I've just never seen anyone start to understand macros by explanation alone, but this slideshow is a pretty good intro: http://www.slideshare.net/pcalcado/lisp-macros-in-20-minutes-featuring-clojure-presentation

I've had small projects where I saved literally hundreds/thousands of lines of code using macros (compared to what it would have taken in Java). Much of Clojure is implemented in Clojure, which is only possible because of the macro system.

mblinn
  • 271
  • 1
  • 3
  • 3
    Without touching any code, I ran through the scenario of implementing an "if" without macros in my head, both in Clojure and C#. It effectively (or literally) can't be done. I admit that's neat. But I'm missing the connection of how that's useful to me. What can I do with macros (besides write "if") that I can't do with clever functional or OO-design? "How could this help me" isn't a valid criteria for evaluating a language, but it is for evaluating whether I'll use it. (I really _want_ a Lisp to be worth switching to, but it hasn't hit the point of becoming a viable alternative.) –  Feb 20 '11 at 16:40
  • Here's an example from another Stack Overflow question. http://stackoverflow.com/questions/5035086/clojure-generating-functions-from-template – mblinn Feb 20 '11 at 16:44
  • In that case, the macro saves a bunch of boilerplate code. The macros that I write tend to be of that variety. Like I mentioned, most of Clojure is implemented in Clojure. It's hard to imagine the implications of that. It means you can implement almost any language feature you need when you need to. You've probably heard these arguments before, and probably better stated. I known none of them convinced me. What convinced me was the first time I ran into a situation where I realized I could write one macro, and ended up saving hundreds of lines of code... – mblinn Feb 20 '11 at 16:51
  • 1
    It's going to take me a bit of time to grok that example. For me, "saving boilerplate code" usually invokes the term "refactoring", which is part of why the explanations haven't convinced me. I have a solution that works in every instance I've applied it. I think then the question is how do you identify cases where you can use a macro? –  Feb 20 '11 at 17:09
  • Macros enable you to refactor out code that must be change pre-compile. For example, class definitions that have the same structure. –  Feb 20 '11 at 17:36
  • If you've ever had to implement `INotifyPropertyChanged` or `DependencyProperty`, you would know that you can't refactor out all boilerplate. Macros would be a great way to simplify these in C#. – Gabe Feb 20 '11 at 18:15
  • Hm. "class Name { }" is pretty compact. As far as refactoring, there's inheritance to handle many cases, and extension methods (akin to defmethod, as far as I can tell) to handle the rest. I'm really not trying to be difficult here; I'm just searching for some brain-bending example that once I get my head around it, I think "oh, that's why this is a big deal, wow!" –  Feb 20 '11 at 18:20
  • 1
    I agree that macros are worthwhile, but it isn't true that a my-if requires them, it can be written as a higher-order function (see a CL example below). You only really need macros if you want to walk the code or place it in a new lexical context. `(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))` –  Feb 21 '11 at 00:33
  • 1
    Basically with a function you have a choice to accept either quoted code, which you can walk but it's missing its lexical context because you're walking and executing it in the lexical context of your function. Or you can accept closures, which keep their lexical context but you can only call, not walk them or add something to their lexical context. To do both you need a macro, which can walk and wrap the code before its expansion will be placed into the lexical context of the macro call. –  Feb 21 '11 at 00:49
  • @Jonathan Mitchem It's hard to identify a case were should can use a macro. That's actually not a question you ask when you're programming in a lisp. What happens is you identify a pattern, and then you modify the language to fit that pattern. That may, or may not, require using a macro. I think one of the reason that the my-if example is so popular is because lisp lets you define language constructs that are as powerful as the ones that come with the language, and "if" is a construct that any programmer understands. – mblinn Feb 21 '11 at 03:56
  • @Rörd right, but a macro lets you do so without passing in either quoted code or a closure... – mblinn Feb 21 '11 at 03:59
  • @mblinn @Rörd: I'm having some trouble getting a Clojure environment set up with an IDE, but I'm going to give that a chance. It seems I _really_ need to get my head around this macro thing and see why it's so important. The idea of maintaining lexical context with a macro makes a bit of sense to me, versus with a function. It's not something I could explain to the average C# dev though, unfortunately. Let me play for a few days. Thanks for the feedback. –  Feb 21 '11 at 17:31
  • 7
    @Jonathan Mitchem, LINQ syntax is a nice example of what can be done with macros, but had to be implemented in a language core instead, as the language does not support any decent metaprogramming. – SK-logic Feb 21 '11 at 18:13
  • 1
    Also see the SO question [What can you do with Lisp macros that you can't do with first-class functions?](http://stackoverflow.com/q/4980520/211232) – WReach Feb 21 '11 at 18:17
  • 1
    A look at [Incanter](https://github.com/liebke/incanter) or [Enlive](https://github.com/cgrand/enlive) or [Conjure](https://github.com/macourtney/Conjure) or [Moustache](https://github.com/cgrand/moustache) should all be pretty clear examples of how macros stand apart. –  Feb 22 '11 at 17:39
  • I wonder what is going on in a language where such a simple thing like *if* can't be defined as a function, as it is stated here? In Haskell, for example, this would be just *myif True t e = t; myif False t e = e;* – Ingo Apr 10 '11 at 01:44
  • @ingo Haskell is "saved" by lazy evaluation. Try implementing that in "Haskell with eager evaluation" (not taht such a beast exist, but it'll give you an insight into where the problem lies). – Vatine Apr 10 '11 at 09:24
  • @Vatine - I see, but doesn't that mean that a lisp programmer can't see the semantics of some piece of code without looking at the implementation? – Ingo Apr 10 '11 at 09:42
  • 1
    @Ingo: Werllll... It's bordering on a chore to override the 978 symbols that form the core of Common Lisp (not difficult, per se, just requires that you create a package that doesn't import anything from package COMMON-LISP). So, most of teh time, anything outside the language core is not entirely obvious. But, as things can mutate state without clearly indicating so, it's not really an extra burden. – Vatine Apr 11 '11 at 08:17
  • "Code as data" is a very bad idea, and I'm glad that more languages haven't picked it up. Just look at SQL. Every time you see in the news about some site getting hacked and divulging millions of users' private information due to a SQL injection attack, it's because some programmer failed to properly separate code from data. – Mason Wheeler Jun 18 '12 at 18:17
  • 1
    @MasonWheeler I don't see how lisp's 'code as data' can make code injection easier. Actually, I believe it makes injection much more difficult. Consider this: `"SELECT * FROM users WHERE username = " + user_name + ";"` is dangerous because we *evaluating user input*. But in lisps it could look like `(select users-table (where (= :username user-name)))`. Lisp will run this code `(= :username user-name)` for each username in db checking equality to `user-name` string value. *`user-name` does not evaluate.* – defhlt Aug 07 '12 at 17:09
  • @MasonWheeler I mean if SQL had S-expresion syntax it will treat this data as code `(= username user-name)` and turn it in something like `(filter (fn [username] ~where-arg) usernames)` and then `(filter (fn [username] (= username "whatever user entered as his username")) users)`. So what ever user will put as username it will be treated as string, not S-expression. – defhlt Aug 07 '12 at 17:34
  • @ArtemIce: Then that's not treating data and code as equivalent. It's treating data and code as completely distinct things that play by different rules, which is the correct way to do it. – Mason Wheeler Aug 07 '12 at 17:39
  • @MasonWheeler So to recap our little discussion I would say in lisps "code is (structured like) data" but "not all data is code". – defhlt Aug 07 '12 at 17:58
  • In the simple example of reimplementing 'if', you already impose something on the client interface of that code. Thus, there is additional abstraction that can't take place. If this were the way 'if' was implemented, I would call it through a macro so my code wouldn't have to thunk everything manually. The hassle avoided grows at scale, but macros also provide efficiency benefits at the expense of clarity. IMO, the clarity of reading expanded code is overblown, or people wouldn't use C over assembly. Macros let you to make those tradeoffs yourself without being at the mercy of MS. – gtrak Mar 23 '13 at 15:37
14

I'm not an expert in Common Lisp, but I know both C# and Clojure reasonably well so hopefully this is a useful perspective.

  • Simple syntax => power - Lisps are about the simplest syntax that could possible work. S-expressions are all you need. Once you've grokked "(function-call arg1 arg2 arg3)" then you've basically got it. The rest is just picking the right function calls and arguments. Seems almost trivially simple, but the fact is that this simplicity is what gives Lisps so much strength and flexibility, and in particular enable the meta-programming capabilities that Lisp is famous for. e.g. if I want a macro to call several different functions on the same arguments I just do something like the following (not this is not just runtime function application - it's a macro that generates compiled code as if you had written out all the individual function calls by hand):
(defmacro print-many [functions args]  
  `(do   
     ~@(map   
        (fn [function] `(println (~function ~@args)))   
        functions)))

(print-many [+ - * /] [10 7 2])  
19  
1  
140  
5/7
  • As a result of the incredibly simple syntax, Lisp's "code is data" philosphy is much more powerful than anything you can do with function composition / templates / generics etc. It's more than just DSLs, it's code-generation and manipulation, at compile time, as a fundamental feature of the language. If you try to get these kind of capabilities in a non-homoiconic language like C# or Java, you'll eventually end up reinventing Lisp, badly. Hence the famous Greenspuns Tenth Rule : "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp". If you've ever found yourself inventing a turing-complete templating / flow control language within your application then you probably know what I mean.....

  • Concurrency is a unique strength of Clojure (even relative to other Lisps), but if you believe that the future of programming will mean having to write applications that scale to highly multi-core machines, then you really really should look into this - it's pretty groundbreaking. Clojure STM (Software Transactional Memory) works because Clojure embraces immutability and lock-free concurrency constructs based on a novel separation of identity and state (see Rich Hickey's excellent video on identity and state). It would be very painful to graft this sort of concurrency capability onto a language / runtime environment where a significant proportion of the APIs work on mutable objects.

  • Functional programming - Lisps emphasise programming with higher order functions. You're right that it can be emulated in OO objects with closures / function objects etc. but the difference is that the entire language and standard library is designed to help you write code in this way. For example, Clojure sequences are lazy, so can be infinitely long unlike your ArrayLists or HashMaps in C#/Java. This makes some algorithms much easier to express, e.g. the following implements an infinite list of fibonacci numbers:

    (def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))

  • Dynamic typing - Lisps tend to be at the "dynamic" end of the functional programming language spectrum (as opposed to langaages like Haskell with a very strong and beautiful conception of static types). This has both advantages and disadvantages, but I'd argue that dynamic languages tend to favour programming productivity because of greater flexibility. This dynamic typing has a minor runtime performance cost, but if that bothers you you can always add type hints to your code to get static typing. Basically - you get convenience by default, and performance if you are willing to do a bit of extra work to get it.

  • Interactive development - actually I think you can get some sort of REPL for C# 4.0, but in Lisp it's standard practice rather than a novelty. You don't have to do a compile / build / test cycle at all - you write your code directly in the running environment, and only later copy it into your source files. It teaches you a very different way to code, where you see results immediately and refine your solution iteratively.

A few quick comments on the things you see as "missing":

  • As you say, Clojure has namespaces. In fact, they are dynamic namespaces: you can update them at runtime - this provides some surprising benefits for interactive development. I've had lots of fun redefining functions under the nose of a running thread, or hacking a live data structure....
  • Compile / design time support - not really relevant if you're coding at a REPL - you just do it and see the result directly. Having said that, Clojure is starting to get some improved IDE support, e.g. the CounterClockwise plugin for Eclipse.
  • GUI development - Yes, you'll have to learn a new API, but that's always going to be the case when switching languages.... good news is that the Java APIs aren't that hard and there are now some interesting Clojure-specific wrappers / examples that look pretty easy to use. See this question on Clojure GUI development for some nice examples
  • GUI debugging: this works with the GUI debugger in Eclipse assuming you use CounterClockwise, or Enclojure if you use Netbeans. Having said that, I don't use this capability - I find interactive debugging at the REPL more productive.

I personally find the advantages of Clojure / Lisp pretty compelling, and I'm not planning on going back to C#/Java (beyond what I need to borrow all the useful libraries!).

However, it did take a few months for me to really get my head around it all. If you're genuinely interested in learning Lisp / Clojure as an enlightening learning experience, there is no substitute for diving in and forcing yourself to implement a few things.....

mikera
  • 20,617
  • 5
  • 75
  • 80
  • 1
    Thanks for the great enumeration of ideas. I actually do use lazy sequences a lot via the IEnumerable<> construct in C# (and haven't touched an ArrayList since 2005 or so) but I understand the idea. Clojure's concurrency primitives are extremely impressive. I was doing grid computing in C# a few years back -- concurrency across many multi-core machines -- and I still believe that's the future. Probably the biggest idea that I've gotten from everyone is "you really have to just dig in and experience it, it can't really be explained". So, I'm going to keep digging and keep experiencing. –  Feb 21 '11 at 22:30
  • 1
    Cool, glad to have helped - and that's absolutely the right spirit!! – mikera Feb 21 '11 at 23:49
10

C# has got a lot of features, but the mix feels different. That some language has a feature still does not mean that it is easy to use, paradigmatic and well integrated with the rest of the language.

Common Lisp has this mix:

  • s-expressions as a data syntax
  • code as data leads to macros and other techniques of symbolic computation
  • dynamic language allows runtime modifications (late binding, MOP, ...)
  • strongly typed, dynamically typed
  • interactive use with incremental compiler or interpreter
  • Evaluator as core execution engine
  • managed memory with Garbage Collection
  • Hybrid language with heavy use of imperative, functional and object-oriented programming. Other paradigms can be added (rules, logic, constraints, ...).

Now, other languages, like C#, have that, too. But often not with the same emphasis.

C# has

  • C-like syntax
  • mostly imperative object-oriented, with some FP features
  • statically typed
  • mostly batch use with a batch compiler
  • managed memory with Garbage Collection

It does not help that C# has for example code manipulation features, if they are not widely used as in Lisp. In Lisp you won't find many software that does not make heavy use of macros in all kinds of simple and complex ways. Using code manipulation is really a big feature in Lisp. Emulating some uses is possible in many languages, but it does not make it a central feature as in Lisp. See books like 'On Lisp' or 'Practical Common Lisp' for examples.

Same for interactive development. If you develop a large CAD system, most of the development will be interactive from the editor and the REPL - directly into the running CAD application. You can emulate much of that in C# or other languages - often by implementing an extension language. For Lisp it would be stupid to restart the CAD application under development for adding changes. The CAD system would then also contain an enhanced Lisp that would have added language features (in form of macros and symbolic descriptions) for describing CAD objects and their relationships (part-of, contained, ...). One can do similar things in C#, but in Lisp this is the default development style.

If you develop complex software using an interactive development process or your software has a substantial symbolic part (meta programming, other paradigms, computer algebra, music composition, planning, ...), then Lisp may be for you.

If you work in the Windows environment (I don't), then C# has an advantage, since it is a main development language of Microsoft. Microsoft does not support any form Lisp.

You write:

  • Namespaces. Even with static methods, I like to tie them to a "class" to categorize their context (Clojure seems to have this, CL doesn't seem to.)

Common Lisp does not attach namespaces to classes. Namespaces are provided by symbol packages and are independent of classes. You need to reorient your approach to object-oriented programming if you use CLOS.

  • Great compile and design-time support the type system allows me to determine "correctness" of the datastructures I pass around anything misspelled is underlined realtime; I don't have to wait until runtime to know code improvements (such as using an FP approach instead of an imperative one) are autosuggested

Use the Lisp compiler. It informs you about unknown variables, might have style recommendations (depending on the compiler used), gives efficiency hints, ... The compiler is a keystroke away.

  • GUI development tools: WinForms and WPF (I know Clojure has access to the Java GUI libraries, but they're entirely foreign to me.)

The commercial Lisps like LispWorks and Allegro CL offer similar things under Windows.

  • GUI Debugging tools: breakpoints, step-in, step-over, value inspectors (text, xml, custom), watches, debug-by-thread, conditional breakpoints, call-stack window with the ability to jump to the code at any level in the stack (To be fair, my stint with Emacs+Slime seemed to provide some of this, but I'm partial to the VS GUI-driven approach)

The commercial Lisps like LispWorks and Allegro CL offer all of that under Windows.

Rainer Joswig
  • 2,190
  • 11
  • 17
  • 1
    My criticism isn't really of Lisps. I find them to be quite capable. I'm looking for what can a Lisp do/give-me that I don't already do. I fully understand that most C# devs don't use many of the "new" language features. I think the larger point is that I do, and honestly, outside of the GUI, I almost write in a 100% FP style. FP was a conceptual leap, but worth it. However, switching to a Lisp from where I am seems to provide little benefit. I'm hoping to find that there's a reason I should keep giving it a chance. –  Feb 20 '11 at 16:58
  • @Jonathan Mitchem: C# does not really look like an FP language, even if the language has some features supporting it and a few libraries might use those. If you want to do FP programming in the Microsoft eco-system, then F# might be for you. –  Feb 20 '11 at 17:06
  • @Rainer No, it doesn't look like an FP language, but it can do it, and fairly compactly at that. And when I want/need imperative code, or OO, I can do that too. (slightly off-topic: I don't really consider F# a serious language worth digging into, I'd rather get my head around monads in Haskell. But when I was a C++ dev and C# came out, I didn't consider it a "real" language either [and back then it wasn't, imho]) –  Feb 20 '11 at 17:15
  • @Rainer Here's an example of FP in C# http://jmitchem.livejournal.com/#post-jmitchem-69978 also http://jmitchem.livejournal.com/?skip=10#post-jmitchem-66618 –  Feb 20 '11 at 17:30
  • @Jonathan Mitchem: why should I look? I wrote that C# supports some FP features. It just added to an imperative object-oriented language and does not make it idiomatic, as in, say, Lisp, Scheme and especially not compared to SML, F#, Haskell or other mainly functional languages. My point is: some FP is possible in C#, but it is not a core feature. –  Feb 20 '11 at 17:43
  • @Jonathan Mitchem: if you look at your second example. That's a really simple use of FP. It calls a bunch of functions which return values and at one point it calls a function with an anonymous function. That's fine. This level of FP programming has been provided by the very first Lisp in the 1960something. –  Feb 20 '11 at 17:49
  • @Rainer: Yes, it is a simple example, but for most C# devs, it's a new way to solve the problem. The first example isn't quite as simple. Automating generation of memoization is another example I have on there somewhere. The question is still _what can I do in a Lisp_ that I can't do with C#? Why should I use it? There are things that are idiomatic in a Lisp that aren't in C#, and vice versa. That's not the point. What are compelling reasons to seriously consider switching to a Lisp as a primary development language (at home, at least)? –  Feb 20 '11 at 18:11
  • @Jonathan Mitchem: See my post above. What is unclear? –  Feb 20 '11 at 18:19
  • @Jonathan Mitchem: For idiomatic Lisp programming and code, read Peter Norvig's book 'Paradigms of Artificial Intelligence Programming'. For a basic practical introduction see Peter Seibel's book 'Practical Common Lisp'. For advanced OO-programming in Lisp see the book 'The Art of the Meta-object Protocol'. For some in-depth introduction into macro use read 'On Lisp' by Paul Graham. Reading the manual of some Lisp also helps. As I said, Lisp favors a symbolic and interactive programming style. If that is not important or even not useful for you, then Lisp is probably not helpful for you. –  Feb 20 '11 at 18:25
  • @Rainer: The only "important" thing that seems to be something I can't do in C# by default is "interactive development", just the "code -> compile -> run -> interactive debug -> stop -> repeat" model. Other than that (which I admit is very significant, and mentioned in the original question... but has been partially implemented in C# in the past), I don't see what I get from moving to a Lisp. If you weren't aware, C# does have dynamic typing, late binding, garbage collection, and is multi-paradigm. It just doesn't have symbolic manipulation or interactive development. –  Feb 20 '11 at 18:29
  • @Jonathan Mitchem: I wrote about that above. It's just that many of these features are like wheels bolted on a boat. But that does not make it a particular typical car. If you are not sure what you can do with macros and other symbolic programming techniques, then I gave you some pointers to books which are explaining this in more detail. If you look at C# it actually does some of that. But in Lisp it is pervasive and easy in comparison. –  Feb 20 '11 at 19:00
5

Rainier Joswig said it the best.

For me, the power of Lisp can't be understood by just looking at a list of Lisp features. For Lisp, and Common Lisp in particular, the whole is greater than the sum of the parts. It's not just the REPL plus s-expressions plus macros plus multi-method dispatch plus closures plus packages plus CLOS plus restarts plus X, Y and Z. It's the whole shebang all ingeniously integrated. It's the day-to-day experience which is very very different than the day-to-day experience of other programming languages.

Lisp looks different, it is used differently, one approaches programming in a different fashion when using it. It is elegant, complete, big and seamless. It is not without its own blemishes and peccadilloes. There are certainly comparisons to other languages that might be made where it might not come out ahead. But in no way is Lisp just language X plus REPL and macros, or language Y plus multi-method dispatch and restarts.

3
Code is Data (and Macros) - Maybe I haven't "gotten" macros, but I haven't seen a single example where the idea of a macro couldn't be implemented as a function; it doesn't change the "language", but I'm not sure that's a strength

Typically, a macro should be used for something that is not expressible as a function call. I don't know if there is a switch-like conditional in C# (similar to what exists in C, as switch (form) { case ... }), but let us assume it does and has close to the same restrictions (requiring an integral type).

Now, let us further assume that you'd like something that looks like that, but dispatches on strings. In lisp (well, specifically in Common Lisp and probably in others), that is "just" a macro away and if you restrict the user to having constant strings (probably not arduous) to match against, you can (compile-time) compute a minimal sequence of tests to determine what case matches (or use a hash table, storing closures, maybe).

While it may be possible to express that as a function (comparison string, a list of cases and closures to execute), it would probably not look like "normal code" and that is where lisp macros have a definite win. You've probably used quite a few macros or special forms taht are macro-like, like defun, defmacro, setf, cond, loop and many more.

Vatine
  • 4,251
  • 21
  • 20
2

This is the best explanatory article I've found that takes the reader from the surface of Lisp advocacy to show some of the deeper implications of the concepts being used:

http://www.defmacro.org/ramblings/lisp.html

I recall reading elsewhere an idea like this: Other languages have adopted various features of Lisp; garbage collection, dynamic typing, anonymous functions, closures to produce a better C++/C/Algol. However to get the full power of Lisp you need all of its essential features, at which point you have another dialect of Lisp, a distinct family of languages that has been around in one form or another for over 50 years.

spacebat
  • 21
  • 2