Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Functional programming in C (2013) (lucabolognese.wordpress.com)
144 points by vasili111 on July 19, 2016 | hide | past | favorite | 59 comments


This is an interesting idea. For one of the first year programming courses here at UWaterloo, we start by teaching students Racket. To ease them into C, we use a subset we refer to as "functional C."[0] This is really just C with a few restrictions. The main ones are that all "variables" and parameters must be const and all branches of an if statement must return a value.

While these restrictions may seem silly to the HN crowd, it leads to an interesting way of thinking about solving problems.

[0] https://www.student.cs.uwaterloo.ca/~cs136/handouts/03-funct...


Interesting! On the other side of the world, those are precisely the main two restrictions we follow in JavaScript in our React projects right now in order to make our components more functional and predictable. It's amazing how much maintainability mileage you can get out of a few simple rules.


> and all branches of an if statement must return a value.

Is indeed a bit strange. I guess I can see the reasoning, though.

"const by default" doesn't seem all that unreasonable to me - isn't that one of the major driving forces we're seeing in "new" languages - that based on years of experience, "accidental" mutability should be avoided -- mutability should not be the default?

How does this subsetting of C affect the error messages the students are (most) likely to see? Does -Wall typically catch treating a const as mutable? (I assume so, I'm just curious about this, as I'll soon be teaching a beginning programming course - and a sub-set of C might be an interesting option).


We are pretty liberal with the warnings. -Wall will catch trying to mutate const variables. We also use address sanitizer[0] with clang which helps a lot with common memory errors. All this makes the code run (relatively) quite slow but since anything they're testing should complete in a few seconds regardless, it's not a problem for us in practice.

[0] http://clang.llvm.org/docs/AddressSanitizer.html


I'll be that guy who mentions Rust.

It's linkable with C. It has first-class support for discriminated unions. Very powerful automatic cleanup. Library of minimal-overhead common data structures. Plus immutability, and a few more things heavily inspired by functional languages.

So instead of a bit odd, not-quite-portable C macros I wholeheartedly recommend using a bit odd, not-quite-portable Rust instead.


Seconded, I think Rust is an excellent choice for writing new code in large C code bases. The compiler catches so many memory bugs, I think it's a worth using. I think it'll help more people contribute to open source as well. They're less likely to add new bugs or security risks.


Ugly and unportable code. I can't see practically any benefit.

  #ifdef __GNUC__

      value       =   c->kind == Volvo    ? ({
                                              struct Volvo* it = &c->Volvo;
                                              itoa(it->x, temp, 10);
                                            })
                    : c->kind == Ferrari  ?   (void)c->Ferrari.model, c->Ferrari.brand
                    : c->kind == Fiat     ?   c->Fiat.model
                                          :   union_fail("Not a valid car type");

      testCar(c, value);

  #endif // __GNUC__
  }


Agreed, "functional" code in C is when you write pure functions with no side effects and nest them like `f(g(h(x)))`. Everything else is an abomination.


I love this style. I accidentally "discovered" that pure functions were good through trial and error in my younger days, before I'd looked into functional programming. However, while I'm not necessarily a gigantic fan of functional languages, I think functional principles should be taught to everyone.


Macros can clean this up a little. Still, all C is ugly.


C can be beautiful if you use it in the way it was intended. Code that mimics OO or functional patterns from other languages is certainly not.


Oh god, flashbacks to Gtk+, and it's "objected oriented C". C is the easiest to bind to, and anyway, C++ is just syntactic sugar! Now, let's define class by creating a struct that will hold your vtable and static properties, and another that will hold your instance data...

Such bullshit.


Gtk+ is nice as long as you don't have to define your own classes with GObject . But in that case, there is Vala which compiles to C which makes more sense. To bad this project doesn't get more traction, it's actually well designed and the generated C code is quite readable. I personally prefer Vala to D from a design perspective.


I actually liked Gtk+ when I used it. It's layout managers were easy to use (if Java AWT like), and I rather liked playing with Glade and being able to use scripting languages to build apps.

Alas, the greater GNOME attitudes that began to copy the worst aspects of Windows after the fateful (and correct) Sun Microsystems of user experience study in the late 90s of GNOME 1.x [0], which came back with "You literally have five different clocks, including one named 'Another Clock' [1], are asking people to identify video cards by chipset, and have odd settings that are explained solely by acronyms. Get your house in order." to then mean "No options at all!", and the bad copying of the worst ideas of windows to the bad copies of the worst ideas of Apple (e.g. undiscoverable keypresses and file choosers without directory path inputs) turned me off to the whole mess.

[0] https://web.archive.org/web/20010725052737/http://developer.... [1] http://www.linuxselfhelp.com/gnome/users-guide/clock-applets...


Was Gtk+ a descendant of things like Xt Intrinsics? I seem to remember having to work with "object oriented C" in the mid '90s and not liking it at all.


I've looked somewhat at both, and my impression is that Gtk+ tried to do a similar thing as the Xt Intrinsics, but didn't do it in a really better way.

The problem with both is that you still have to write a lot of boilerplate by hand. Of the Xt Intrinsics I know that the things you could do with it were pretty advanced for their time, just too much annoying work.

What the Gtk+ people should have done is design some nice notation for objects and generate the Xt Intrinsics object boilerplate from that. Or in fact that should have been done soon after creating Xt.


The way it was intended? You mean by K&R? Seems like you are making a subjective judgement because I hardly doubt the majority of C hackers use C "as it was intended" in 1973.

I'll postfix this by saying I actually don't consider the code snippet as ugly but that's because I like C. Most people consider C ugly because they use stuff like Python or C++ which hides some details. Macros can hide details too, they are just difficult to use. Therefore, for most people C will always be "ugly", but then again, beauty is in the eye of the beholder.


C pet peeve: typedef structures for_incredibly_esoteric_type_t.


I've noticed a lot of people like to end their types in _t, maybe because it looks cool, but POSIX actually says all those names are reserved for the system. Oh well.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...


I too hate typedef except for integer/FP types where the new name would carry semantics, like, for example, `pid_t` or `time_t`.

`point_t` instead of `struct point`, `color_t` instead of `enum color` or worse, `pint` instead of `int *` is stinky because it hides the actual operations which are only applicable to a struct, an enum, or a pointer, respectively.


Also worthwhile mentioning is functionalC [0].

The blog mentioned on the Github page is down but the relevant URLs can be accessed from the wayback machine at [1] and [2].

GCC also supports nested functions [3] (as a GNU C extension).

Unrelated, but [4] specifies a pure C implementation of Go channels and libmill [5] is a library that introduces Go-style concurrency to C.

[0] https://github.com/cioc/functionalC

[1] https://web.archive.org/web/20160406001106/http://blog.charl...

[2] https://web.archive.org/web/20160304120857/http://blog.charl...

[3] http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html

[4] https://github.com/tylertreat/chan

[5] https://github.com/sustrik/libmill


I'll piggyback on your comment to add another functional version of C to the discussion, Single Assignment C.

http://www.sac-home.org

http://en.wikipedia.org/wiki/Single_Assignment_C


Related: Here is a really good blog post by John Carmack after forcing himself to code functionally in C++ for a month:

http://www.gamasutra.com/view/news/169296/Indepth_Functional...

Some really choice quotes in there.


And here's one by Jonathan Blow as well (references Carmack's post as well) http://number-none.com/blow/blog/programming/2014/09/26/carm...


I don't like the approach here, and the fact that it's gcc-specific. Many years ago I wrote a small macro library for discriminated unions/sums which I think is much cleaner and looks like actual C [1].

With some fancier variable macros + C11, you can even make closures in C viable [2].

[1] https://github.com/naasking/libsum

[2] Incomplete, but both curried and uncurried closures are possible: https://github.com/naasking/cclosures


I was initially hoping this would be about the book "Functional C" (Hartl & Muller, 1997). https://www.amazon.com/dp/0201419505/


Nice experiment, but anyone writing such code in production should get his fingers chopped.


    $ chgrp sane_c_devs /usr/bin/gcc
    $ chmod 0770 /usr/bin/gcc
Of course, the following scenario is not impossible ;)

    $ getent group sane_c_devs | cut -d: -f4 | tr , "\n" | wc -l
    0


Who can write such code will find a way to type without fingers.


This is as bad an idea as 'OO' C.

having had to deal with the grief that was a late-90s attempt at OO C.... stay away from this.

Tooling is designed for idioms of the language. Attempting to be 'clever' means you have to throw away all the benefits of that tooling as well as reducing your maintainability and confusing the optimiser.


Plenty of successful projects use some form of 'OO' C. The Linux kernel is probably one of the most successful.

I'm not saying all C programmers should use OO techniques, and I'm not saying all OO programmers should reach for C, but I don't think blanket statements like "OO C is a bad idea" and "stay away from [OO C]" are useful without context (and with context they are almost meaningless).


The linux kernel is an extremely mild form of OO. Mild to the extent that is effectively idiomatic C code.

What I'm referring to is the manual creation of virtual pointer tables and dereferencing everything through it. This completely defeats the optimiser as it can't see thru the pointers. The case I'm referring to was a complete WTF in hindsight.


What is the difference between what you are calling virtual pointer tables and what the kernel does in a lot of places?

http://lxr.free-electrons.com/source/fs/ext4/symlink.c#L93


Its the extent to which it is done.

virtual pointer tables are an age-old 'C' mechanism of providing runtime configuration for drivers and such like.

the WTF project, took that idea and had virtual pointers for everything methods, data, superclasses, etc.

Every line consisted of something like the following:

obj->vptr->methodA(obj, objB->vptr->methodB());

it very rapidly becomes a readability and maintainability nightmare.


Sorry, I'm still not following your argument.


dereferencing the vptr in the Linux device tables tends to happen once per (say) function.

the WTF project would have '->vptr' and worse '->super->vptr->method(), maybe 20 or 30 times per function.

every call in the codebase or data reference would be via a '->vptr'.

That really makes a difference.... its unreadable. literally.


So based on one project's misuse of a technique (OO in C) you dismissed it as "bad" idea, "stay away from this", it's "attempting to be clever", and the rest?

I think that's why I'm not following you. You've dismissed OO in C in its entirety, and advised others to do the same, based on your experience in one project which misused/overused the feature.


no.

The level to which it is used in Linux is effectively idiomatic.

The project I'm referring to used the technique properly. Its just a bad technique.


You've said it is a bad technique no matter how it is used, and you have said it is an ok technique if used at some levels (like the Kernel) but not others (like the WTF project).

So you can see how I'm confused about what your opinion really is, I hope.


I did the following experiment a while ago (map and flatmap in C):

https://gist.github.com/machuidel/d7cc099ddc4970c6ddf4

By adding more abstractions it should even be possible to support semigroups, monoids etc.

Of course this was just for fun.


In the same spirit, I wrote some functionally-flavored extensions for GLib a few months back. https://github.com/djcb/gxlib, taking a bit of inspiration from Rust and SRFI-1.

It's fun to get some of the functional flavor, while still being very near the bare pointers.


I wonder how the "discriminated union" thing is better than an enum + a structure for each type + a structure with a field for the discriminator (the enum) and an union of all structures. It certainly is much more readable, and idiomatic. And this doesn't add any benefits that I can see.


I was wondering that same thing. He's hiding a bunch of stuff behind his lutils.h macros, to make it "literate".

Using preprocessor macros to try to create new language constructs is pretty much always Bad News(tm). Almost as bad as using C++ operator overloading to completely change the semantics of an operator. (Bitwise or? Yeah, that's some sort of weirdo automatic function nesting thing now, but only here, and 6 lines down.)


That's exactly what it's doing. There's no benefit to using these macros except that it's shorter and maybe (?) therefore clearer.


It's certainly not clearer if we have to guess.


Guess what?


Sorry, I hope nobody minds a humorous posting (a quote from meet the parents):

Dina Byrnes: I had no idea you could milk a cat!

Greg Focker: Oh, you can milk just about anything with nipples.

Jack Byrnes: [He reacts] I have nipples, Greg, could you milk me?


> I hope nobody minds a humorous posting

Not at all. Please go ahead and post it.


Okay here goes:

> your mom


I so much adore the fact that you can do all paradigms with C relatively easily.


I'm confused... what do you mean 'easily'?


(2013)


I know things can go downhill quickly when I read things like this:

>There is one irritating thing about C as a viable programming language.

Given its history, I don't see where this guy comes from that he can declare whether C is a viable programming language or not. Especially when his real complaint is not about C at all.

>Microsoft’s compiler support is not good.

While some may complain that I'm making a minor quibble, my point is, when you make statements like this, your credibility falls a notch or twelve and I have to look at the rest of the article with suspicion.


Viable in this case means for FP, I'd guess. MS's C compiler support has been crap. In 2013 they didn't have C99 support, right? Just like he says. Even now it seems their C support is driven by the C++ requirement.


On Windows one compiles C99 code as C++, that's all.


C99 and C++ aren't compatible, unfortunately.

For example, C99 designated initialisers aren't in C++ at all, which is a shame as they're fantastically useful:

    struct foo {
      int i;
      double j;
    };

    struct foo f = { .j=9.1, i=4 };
See http://stackoverflow.com/questions/18731707/why-does-c11-not....


Since VS2015 MSVC should have most of C99 support already. So there are no reasons to avoid C99 usage nowadays at all.


VS2015 MSVC supports C++14, which requires C99 library support, hence why Microsoft updated the support.

Likewise MSVC will support C11 libraries when they get around to be fully C++17 compliant.

For any newer C standard the official Microsoft position is to use clang with the Visual C++ backend, called C2 which Microsoft is turning into a backend to be shared between clang, Visual C++ and .NET Native.


Moreover, now you can install just compiler/build tools without Visual Studio: https://blogs.msdn.microsoft.com/vcblog/2016/03/31/announcin...




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

Search: