Functional Programming in C# vs LISP [closed]

2020-05-20 01:31发布

问题:

What are the primary differences between LISP and C# with regards to functional programming? In specific, if a LISP programmer was to switch to using C#, what are the features they are most likely to miss?

回答1:

Doing functional programming in C# is technically possible (well, any language that has function pointers or delegates equivalent can be "functional") -- but C# gets very very painful if you try to do much.

Off the top of my head, in no particular order:

  • Type inference
    • Only exists for locals now
    • Should apply to pretty much everything
    • The #1 problem I have with C# is this. Particularly when you declare a local function... Func<...> = ouch.
  • Full first class functions
    • Delegates aren't the answer, since they aren't structually equivalent. There's no canonical type to represent a function of a certain type. Ex: What is "increment"? Is it a Func? Is it a Converter? Is it something else? This in turn makes inference more complicated.
  • Automatic generalization
    • Sucks to have to calculate and specify all the generic type parameters and their constraints
  • Better support for immutability
    • Make it trivial to declare simple data types
    • Copy-with-modify type stuff (var x = oldX { SomeField = newVal })
  • Tuples C# 7
  • Discriminated unions (sum types)
  • Pattern matching C# 7
    • Makes tuples and sum types much more valuable
    • Allows more expression-oriented code
  • General monad syntax
    • Makes things like async code much easier to write C# 5
    • After you've nested 2+ layers of BeginXXX/EndXXX, it gets quite ugly.
  • Easy syntax for function blocks, so you don't end up with closing lines like "});});"

Edit: One more:

  • Function composition

    • Right now it's painful to do much of any sort of function composition. Currying, chaining, etc. LINQ doesn't get as hurt here because extension methods take the first parameter like an instance method.
  • C# should emit tail.call too. Not needed, the JIT will add tail calls itself as appropriate.

Items in bold have been addressed since this answer was written.



回答2:

Support for immutability, primarily.

It should be easy to create an immutable type and verify that it's immutable. It should be easy to use an immutable type - support like object and collection initializers, but for the immutable case.

After that: better type inference capabilities, tuples, pattern matching, and supporting libraries (again, immutable lists etc).

EDIT: I should say that I'm not a functional programmer, but I'm a C# programmer trying to learn more functional ways. I'm currently helping out (in a small way) with a functional programming book, so I'm learning lots of stuff there.

However, you will hopefully be pleased with LINQ. It does make life much easier for working with sequences of data.



回答3:

If you're asking a Lisper as opposed to an ML-family functional programmer, you won't hear anything about tuples or inference. They will say one thing, smugly: MACROS.



回答4:

Per your question, as a Lisper, what I miss when programming in Java (sorry, I don't use C#):

  1. Good syntax: s-expressions.
  2. Dynamic typing: no complex type declarations.
  3. Closures.

C# addresses (2) and (3) with var and =>, respectively, as I understand it. So my main hang-up is curlies and semi-colons, along with verbose, confusing generic type declarations.


Per your title, as a functional programmer (Haskell), I miss what all those other guys said: immutability, full type inference, tuples, pattern matching, easier generics because no subclassing, strongly typed libraries.

Also, as a Haskell programmer, I still don't like curlies and semi-colons. :)



回答5:

To expand somewhat on Jon's point, the real value for me in functional programming languages isn't what they've got, but what they're missing.

Most of the "big" languages these days have (or can be tricked into having) the basic functional programming primitives like recursion, higher order functions, etc. So if you really want to, you can write in a functional programming style in most languages (hell, if you have tail-call optimisation in your C compiler, you can pretty much write functional-style code in C).

On the other hand, what mainstream languages also have is a wealth of things that you don't have in a pure functional programming language, and those things can really "break" the functional model, and make it impossible to reason about your functional-style program in the same way as you would about a pure-functional language. Mutable local and global variables, all of the paraphenalia around mutable state concurrency (traditional multi-threaded programming), even iteration if mishandled, all make your functional-style code non-functional (as it were).

Even though you might be able to restrain yourself from misusing the features in your mainstream programming language, it's pretty unlikely that everyone who touches the code over time would be able to restrain themselves, so you can't make assumptions about the functionality of your codebase without really thorough code reviews. Some of the "non-functional" problems are quite subtle, too, so people who want to write functional code can slip up.

The best way to make sure you're being functional is to learn and use a functional programming language. I've managed to learn Erlang to the point that I'm now writing a game server in about two weeks of my very limited spare time, so it's not that much of a challenge -- I believe that functional programming languages, once you wrap your head around the basic concepts, are easier to learn because they're so much more spartan.



回答6:

A little creativity combined with Linq (which basically is a functional language embedded in C# as becomes more obvious when watching Dr. Erik Meijers great video series on Haskell) will allow you to make your code look more functional.

Here an example without using Linq:

Haskell:

        minThree :: Int -> Int -> Int -> Int
        minThree x y z 
          | x <= y && x <=z   = x
          | y <=z             = y                      
          | otherwise         = z

C#:

    int MinThree(int x, int y, int z)
    {
        return
              x <= y && x <= z    ? x
            : y <= z              ? y
            : z;

    }

Just don't let Resharper Code Cleanup run over your code as it will make it look like this:

    int MinThree(int x, int y, int z)
    {
        return
            x <= y && x <= z
                ? x
                : y <= z
                      ? y
                      : z;
    }

:(



回答7:

I am, and I do. That is, I use Lisp at home and C# at work and I do miss Lisp when in C#. The question is a little funny because I don't consider (Common) Lisp any more "functional" than C# or Ruby or any other multi-paradigm language.

In general, C#'s functional features are becoming pretty decent, to the point where they're probably Good Enough, if a bit awkward. (Common Lisp's support for functional programming sure isn't perfect, either!) But Common Lisp is a multi-paradigm language; C# doesn't (and may never) match Lisp on syntactic abstraction, code generation, method dispatch flexibility, and so on.

If you're looking for an excuse to avoid learning Lisp because you think C# is just as powerful, sorry. :-)



回答8:

In addition to Jon's answer

  1. Tuples - Tuples are possible with a library but it lacks the intrinsic syntax that most functional languages have.
  2. Options - Pretty much the same as tuples
  3. Deep Type Inference Support - C# added basic type inference support in VS 2008 but it's limited to locals inside method bodies. Most functional languages, ala F#, have deep type inference support and it really makes the language more readable IMHO

Eric on why C# doesn't have deep inference: http://blogs.msdn.com/ericlippert/archive/2009/01/26/why-no-var-on-fields.aspx

  1. Tail recursion: C# has basic tail recursion support but it is not ingrained into the language. See my blog post on where C# tail recursion and continuations break down

http://blogs.msdn.com/jaredpar/archive/2008/11/10/comparing-continuations-in-f-and-c.aspx



回答9:

Multiple return values. "out" and "ref" is unreadable compared to returning tuples.

Better support for immutability, just like Jon said. Right now immutability on classes are possible due to human discipline.

But the most lacking feature is a solid collection framework designed around immutability.

In my current project I anticipated immutability would be a big win, and it was, but the collection framework is a horrible match.

For example, say you want to add an employee to a company, like

public Company AddEmployee(Employee employee)
{
    var newEmployeeList = new List(EmployeeList); 
    newEmployeeList.Add(employee);
    return new Company(newEmployeeList.AsReadOnly(), ....)
}

just isn't very performant or nice to implement.

At one point, I even considered making my own collections starting with a simple immutable Cons class. :)

On the other hand, Linq worked out very nice for us. I really like the methods it introduces. I found the special Linq syntax not in my taste.

EDIT: I've too low rank to comment so I'll reply to Jon's comment here. Jon, I decided it was a bad idea due to maintainability. The people that will maintain this project over the next decade probably don't have much Lisp/functional programming exposure. That reminds me of another thing. I wonder how many months of maintenance it will take before my immutable objects become mutable. (Disclaimer, IT business being something of a gold rush era at times I'm painfully aware that many developer don't even get what features like immutable and closures are.)