Hacker News new | past | comments | ask | show | jobs | submit login
Hey, C Is a Functional Language Too (atomicobject.com)
94 points by haileys on Nov 2, 2012 | hide | past | favorite | 74 comments



This is essentially the technique used to implement the meat of Chicken Scheme, too. [1][2][3] It's a pretty neat idea, but not a new one.

1: http://en.wikipedia.org/wiki/Chicken_Scheme

2: http://wiki.call-cc.org/chicken-compilation-process

3: The homepage is called www.call-cc.org :P


...and pigs can fly, if you throw them fast enough.

This is just an encoding of a Turing-complete language into another, I don't see what's been demonstrated here.


All programming languages and all programming styles can be similarly dismissed as encoding one Turing-complete form into another. Why pay attention to any of them?

This article is clearly someone having fun with their language. It isn't a serious claim about C being functional, something which the author states explicitly if you read to the end.


http://news.ycombinator.com/item?id=4693920

"everything is turing complete" is a perfect example of the kind of middle-brow dismissal i understood pg to be talking about.


I'm not sure this is even Turing complete considering the limitation he mentions:

>The main limitation is that you need to know the size of the return value.


It's still Turing complete (though that's not saying much really). You can always grow the stack more :P


I don't think that solves the problem. Whatever fixed stack size you choose, you still cannot write a program that determines the size of the lists at runtime. A Turing complete system would have to be able to do that.

The only way I see is to allocate (almost) the entire machine memory to the stack, create one giant array in that chunk of memory and then put all lists in that single array. That's tantamount to reimplementing malloc on the stack.


Any physical machine cannot be Turing complete because it has a finite amount of RAM.


Sure, but we're talking about whether or not this particular style of C can be Turing complete and I'm not sure it is, even on a machine with an infinite amout of RAM.


A machine with an infinite amount of RAM can have an infinite stack size.


C arrays that live on the stack have to be created with a constant size (at least before C99). It's a limitation of the language regardless of any machine or stack size.


If we're going to grant that a machine can have infinite ram, surely we can grant that a spec of C can have infinite stack allocated arrays... :)


That's already been granted in this argument. The question now is, given an infinite stack, is this programming style Turing-complete?


Address space is not limited by RAM. The only concern is fixed vs expandable width address.


That is a purely theoretical concern, not a practical blocker against any program anyone would ever run.


A program that determines the number of items it needs to store in a list only at runtime doesn't seem that theoretical to me.


And if it was LISP doing this, people would be gushing all over it...


I would say the aspect that defines a functional programming language is the support of higher order functions. I.e. functions that can take functions as arguments and more importantly can return functions as return value.


As jeremyjh already pointed out, this results in a pretty weak definition. You end up with JavaScript, Ruby, Python, and Objective-C all being lumped as "functional languages", and if you squint a little bit, you can put Java in there too (anonymous inner classes). I think C, C++ and C# could be as well, but I don't know them well enough.

So I don't think it's very productive to use a definition like this, but not because I want to be divisive, or "move the goal posts" or because I'm trying to be elitist here. It's because when a colleague asks you "what's functional programming?" I think it's much more helpful to describe the kind of programming that is encouraged in Haskell and Clojure than to just say, "well it's just map, filter, and fold in JavaScript".


Or we could be more precise and acknowledge there are several related features in the realm of "functional" and a language may have some but not all of them, and not make it into a fan club battle.

Same goes for OO, which has something like 9 well-known defining features.


I don't think my comment was making anything into a "fan club battle". I was specifically advocating the kind of precision you describe, because I think calling any language with higher-order functions a "functional programming language" results in very muddy waters.

The scenario I described wasn't hypothetical. When a colleague asked our whole office "what's functional programming?" I listened while someone told him that it was just map, filter, and fold. He'd used these in JavaScript already, and was prepared to accept that as an answer. I didn't think that was sufficient, so chimed in and offered some alternative features from other languages that I think are as important as higher order functions, mostly related to minimizing mutable state with managed references and persistent data structures. I think he appreciated my contribution, but I honestly don't know for sure. Maybe it just created more questions.


Rather than saying that higher-order functions is the defining feature of functional programming, I would say that it's an essential feature to support its object of defining programs in terms of (mathematical) function application rather than procedural state changes.


Does any higher order function application require state changes to implement as a specific lower order instance.

For example, an instance map is just a cons and a recursive call. No state change needed. You can say something about exposing unwanted details in the cons, but that is a hairier point of contention.


I'm not that old but I remember that, back in the days, functional meant just that you are able to use plain functions (without objects) and pass them around. By that definition, C is functional language, as scheme or lisp or javascript and many others.

I don't know why, but some years ago, "functional programmers" started to change and twist the definition of functional language step by step, until all those languages fell off and only Haskel remained as "functional enough".


I don't see much use in a definition of "functional language" that includes nearly every computer language. We already have a term for that.


I agree that "nearly every computer language" can be considered functional, as "nearly every computer language" can be considered imperative.

What I do not agree is when "functional programmers" claim that language is not "functional" just because it is not "pure functional" or Haskel alike.


well it's primary use seems to be providing a moving goal post for the functional side during the occasional OO vs Functional skirmishes.


That is required for parity with the OO side. Cf. Alan Kay on OO.


quite. Smalltalk is OO and Haskell is Functional are about the only (currently) accepted facts about the definitions of OO and Functional.


I always thought the fundamental difference between a functional and imperative language is that a functional language is built up from expressions, whereas an imperative one is built up from statements.


I think you at mixing words that look similar. It is a large problem in this sort of discussion:

Pure functions. (Only Haskell and friends are strictly pure, as is SQL if you squint.)

Higher order functions. (Java 7 doesn't allow you to pass around a callable function without embedding a link language with reflection or using one-method Runnable.)

First class functions. (Basic, SQL do not have this.)

Generic functions. (C does not have this. C++ does. Java does.)

These are three orthogonal properties a Lang age may have some or all of.


Doesn't function pointers enable passing functions around in C?


Yes, but you can't combine function pointers to create and return new functions.


Sure you can - just not anonymously. Kind of like how you can have "higher order functions" and "closures" in Java with anonymous classes - roughly equivalent, but awkward and not quite what the language was designed for. C is probably better in this regard than Java, but the lack of managed memory is a major drawback.


Right. SO you can program 'functionally' in those languages, but they are not 'functional programming languages' because they don't support it natively.


"just not anonymously"

Just not depending on anything in the environment either except globals, which is a huge limitation. So writing a function inc by = \x -> x + by becomes unnecessarily difficult for example.


then you just have a void* / fn ptr couple that you pass around..


Yes, but functions aren't truly first-class values; you cannot create new ones on-the-fly (proprietary extensions notwithstanding).


What does proprietary mean in this context? I'm sure it can be copied/implemented by for example gcc if they were interested.


I meant to refer to extensions implemented independently by one compiler or another. Clang and gcc both provide lambdas, but not in the same way, and there's no standard for it. (I could be wrong on both points; this is purely from memory, and could have changed.)


Thanks for your explanation. I had to look and I libblocksruntime which seems to be an open source lib for clang with blocks.


Actually, if you look closely you'll notice that not other language lets you do this either (except through an 'eval' or similar). What I think you actually mean is that this still doesn't let you use closures. But in fact it does! :D You'll have to do a lot of void * casting to make it work though.


C does not provide closures. You can pass around a struct with a function pointer and a data or state pointer though. That will let you achieve the same thing, but it won't be as pretty.


I think that this is an interesting area. We can support just about any style in an existing programming language, but there are often rough edges.

This reminds me of how I used to mimic OO in C years ago. At a certain point you realize that you have to forgo certain things to help the person coming after you avoid making mistake -- often they do not see the ramifications of your conventions.

I see this in Ruby right now when I write in a functional style. The code is often terse yet easy to read, but the lack of lazy evaluation makes it far more memory consumptive than code written in a straightforward procedural style. If you know you are making the tradeoff, it is fine. For many people, it is too subtle.


This is the same as the design of Chicken Scheme's implementation (on top of C).

http://en.wikipedia.org/wiki/Chicken_(Scheme_implementation)...



From that it looks like the caller always has to allocate the right amount of space on the stack to hold the result. What if the result size is known only by the callee?

Denying yourself the use of the heap is also going to make closures rather difficult. You can use this style to pass back a function pointer, but the caller would also need to allocate space for the callee's captured variables. Ick.


This seems like a lot of work just to avoid using the heap...


It would be hilarious to provide this as an answer in a job interview to "how do you reverse a linked list?"


I disagree completely that this is a 'functional subset' of C. I also don't know a lot about this stuff, so tear me up.

A functional language isn't just one that doesn't use malloc/only uses the stack. It must actually not have state, and instead evaluate a single function down to the result. Here, after calling make_lists, the input and the result still both exist as state variables (immutable, yes, but that's not enough)! At the same time, I haven't looked at code for a lisp interpreter, but I feel safe assuming that it allocates some memory somewhere. Functional programming doesn't have anything to do with the underlying implementation. The trick here, if any, is that the "underlying implementation" and the code are in the same language/program.


Quick question: why does he do this?

    int main(int argc, char * argv[]) {
      (void)argc;
      (void)argv;
I've programmed C while in school, but I don't remember ever seeing this and I'm not sure how to google it.


It stops the compiler from complaining about unused variables:

http://stackoverflow.com/questions/8052091/void-cast-of-argc...


Why would you do this instead of declaring it as `int main(void)`?


I use clang with a -Wall and -Wextra, which will complain about all sorts of stuff I generally want to know about, including unused variables (I think gcc does the same?). For the rare cases where I actually want to leave a variable unused I do the above to make the warning go away.


At some point the stack is going to grow which will cause allocation at the OS level. And if we're discussing small embedded systems with fixed stacks, this code is entirely unsafe (non deterministic stack usage may cause stack overruns).

In embedded systems (non-MMU ones) you generally want to avoid repeated dynamic runtime allocation to prevent memory fragmentation.

It's a cute example, but I can't see any scenario where its better or safer than heap use.


> It's a cute example, but I can't see any scenario where its better or safer than heap use.

The author does explicitly point this out: "While I find this style strangely addictive, I don’t think I would advocate its general use."


It looks like someone trying to prove Greenspun right.


Ditto on the addictiveness of continuation-passing style.

However, don't try it when coding with peers who are not used to it; you can be burnt at the stake. Because, even though it makes the code easier to read, to the untrained eye it is just cryptic.


money quote (at the end of the article):

"So my title is misleading. I don’t think C is a functional language. But it’s an awful lot of fun (and sometimes very useful) to use C’s functional subset."


In my opinion, the meat of functional programming languages is that they are expression oriented. C clearly is not an expression oriented language.


it might have also been worthwhile to mention that the stack array declaration:

   int32_t result[my_array_size];
relies on C99 support, which is not necessarily ubiquitous (especially in embedded device work). At the very least, many compilers make you explicitly enable C99 support.


... and why is it not obvious? just curious..


On another satirical but deeply intellectually serious note:

http://conal.net/blog/posts/the-c-language-is-purely-functio...

By Conal Elliot.


No it's not. What makes a language functional is its ability to eliminate tail recursion.


GCC can eliminate tail recursion, so does that make C functional?

I don't think the author is seriously of the belief that C is a functional language. This is just a fun little example of writing C in a functional style.


GCC can even eliminate non-tail recursion, such as with this piece of black magic:

    int factorial(int x) {
       if (x > 1) return x * factorial(x-1);
       else return 1;
    }
will be optimized by GCC to

    int factorial(int x) {
       int result = 1;
       while (x > 1) result *= x--;
       return result;
    }
(http://ridiculousfish.com/blog/posts/will-it-optimize.html)


That's a very weird definition. Eliminating tail calls is fun, and useful. But not all that essential in, say, a lazy language.

Having first-class function values in the first place strikes me as way more important. Purity helps, too.


How do you write a function to sum a list of a billion elements in Haskell?

What would happen without tail call elimination?


With foldl', of course. If you don't have tail recursion elimination, foldl' would have to be provided as a built-in.

Summing up numbers is inherently strict, so you'd want tail call elimination for that. But functional mainstains, like say, map or filter are usually not implemented with tail recursion in Haskell, because that would be too strict and would break on infinite lists.


That's not a very good definition. It means that GHC-flavoured Haskell isn't functional: http://www.haskell.org/haskellwiki/Tail_recursion


I think you might have misinterpreted the contents of that page. As someone who has worked on GHC, I can assure you that it does perform tail call optimisation.


I believe I have. Thank you for correcting me.


Tail-call elimination is a feature of the language environment, not of the language itself.


Not necessarily. The Scheme spec requires tail-call elimination, and I think the same may be true of some other functional-ish languages.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: