The Unreasonable Effectiveness of C (2013)
damienkatz.net
damienkatz.net
And the followup discussed here: https://news.ycombinator.com/item?id=5075370
I repost because I'm becoming more and more frustrated with C, and wondered if others feel the same. Not because it's too low-level, rather because it seems to be trying (with limited success) to move to being a higher level language. There are times when I really do want a "thin layer over assembly", and C seems to have evolved to a place where that is no longer possible. I explain my distress more completely in the comments of Yann's article here: http://fastcompression.blogspot.com/2014/11/portability-woes...
Properties that some language lawyers cooked up back in the stone age to keep all compilers on all architectures compliant without further effort for developers, which is typical design by committee cruft to get people to buy in on the standard.
C has not radically changed since the 1989 ANSI C standard. The library is bigger and there are some bells and whistles in there like designated initializers, structure literals, complex numbers and (now optional again) variable-length local arrays.
The trick of abusing C as a higher level assembler that are undefined behavior today were already undefined behavior already in 1980-something, and before that there was no standard at all, other than the K&R1 book.
C can work around this using restrict keyword though.
Concrete example, doubly-linked list deletion, bad:
victim->prev->next = victim->next;
victim->next->prev = victim->prev;
Here, the compiler doesn't know whether the assignment to victim->prev->next clobbers victim->next, which is evaluated again in the second line. If you write it like this, you eliminate that concern: node *prev = victim->prev, *next = victim->next;
next->prev = prev;
prev->next = next;
The assignments here do not interfere; the first line assigns to a "prev" structure member, and the next line doesn't access any such thing; it uses the "prev" local variable which isn't a structure member.Caching in local variables is preferrable to using some C99 "restrict" voodoo that introduces undefined behavior.
In the case of the above linked list, where are you even going to stick "restrict"? There is only one incoming pointer, victim. Making that "restrict" doesn't help. You need it in the actual structure declaration on the next and prev pointers. Yuck, what?
I'm also not referring to syntax --- I think many of the syntactical improvements are fantastic and have no downside. And the reliability of modern compilers is much better, and by-and-large the optimizations produce smaller and faster code. But what I think we're losing is the transparency of being able to reason about code execution by looking at source code. It's still the norm for academic papers to offer source for two approaches to a problem, and use execution time to prove that one is faster than the other because "it has more operations" or "it has more memory accesses", without ever looking at the generated assembling and noticing that one approach has been vectorized and unrolled, and the other has substituted a completely different algorithm that drops all the conditionals because the compiler realized they were never being exercised.
And I don't think it's simply a matter of programmers exploiting undefined behavior without properly considering the consequences. The inclusion of the standard library into the spec for the language creates "spooky actions at a distance" that never existed before, where passing a variable to "print_debug(a)" allows the compiler to remove the later check for "if (a == NULL)" if print_debug() is observed to pass the variable to printf() and if whole program optimization is being used. Whereas if print_debug() is in a compiled shared library, this won't happen, and the null-check will work as intended. I see the reasons compiler writers want this flexibility, but it sure makes development and debugging much harder than it used to be.
Some of them were in obscure GCC extensions, like notably computed goto. The compiler started generating code involving branches to a common instruction that does the actual indirect jump, instead of just doing that indirect jump.
It's easy to understand how a performance regression affecting such a feature can slip through, if the behavior is otherwise correct.
It doesn't mean that the GCC sky is falling, as all the trolling in that newsgroup would have you believe.
These are multiple well-respected CS researchers and extremely experienced C programmers saying they can no longer use C (not just GCC) in the manner they once did because they believe it's no longer a possible to make the language work the way they want it to. You appear to be an expert C programmer as well, and don't agree. Disagreement is fine, but I think you would be wrong to completely discount what they are saying.
I think it's a matter of what level of optimization you are aiming for. I'm mostly working on integer compression, and find that I am no longer able to use C to generate code that maximizes the performance of modern processors. Ertl's work on threaded dispatch has led to large improvement in the implementation of modern interpreters (http://bugs.python.org/issue4753). As a tool, C is stronger if it can take advantage of techniques such as this, and there don't appear to be any other languages higher than assembly where tip-top performance is possible. I may be just a cranky anonymous troll, but Ertl should be considered a canary telling us that we are in danger of losing the ability to make similar optimizations.
Yes, if C were still a "thin layer over assembly", then cc -O1 and -O3 would not generate such different object code. :)
In fact, such an effort was started at AT&T back in 2001. The language was named Cyclone [1]. It met its 1.0 release in 2006. It has since been abandoned. Which is a damn shame, because it looked like it had real potential.
Is C a primitive language? Yes. In fact, it's almost stupidly simple compared to most modern languages. But its greatest weakness also proves to be its greatest strength. The main cognitive overhead from using C is in its manual memory management model and certain gotchas in standard libraries. It being bare bones as it is (but not too much) also makes it surprisingly pleasant at times. I get the feeling that plenty of modern languages try too hard to be novel and, for all the freedom you get from not having to worry about anything below program logic itself, you still end up expending a lot of effort into complicated high-level patterns that are frequently interspersed with arcane PL theory.
But that's not even the main reason why something like Cyclone is much needed. No. It's that we've pretty much perfected a ton of our APIs, like POSIX, to work with C. It simply feels most natural to work with POSIX in plain C. FFIs and bindings vary, some of them like Nim's actually feel quite clumsy (otherwise Nim makes it absolutely trivial to create FFIs, which is great).
Finally, refactoring 30 years worth of code into Cyclone sounds far more realistic than rewriting the world in Rust.
[1] https://en.wikipedia.org/wiki/Cyclone_%28programming_languag...
I can't say I agree with anything you say though... So only memory safety is C's problem? Not its type system that's very weak in every aspect (not just memory safety)? Not its missing module system? Not its ambiguous syntax?
You also confuse "simple" with "primitive" and perhaps "familiar". C surely is primitive, but how is it simple? Simple to use? No. Simple to implement? Not really. Has a simple mapping to the underlying hardware? That stopped being true three decades ago.
I've seen this claim repeated a few times in the thread and I still have no idea what it means.
For medium levels of optimisation it's usually very simple to predict what assembly code will be generated by C compilers. On many platforms the operators often match directly to a instruction (e.g. arithmetic and bitwise operations) or short patterns of instructions (the control flow primitives and conditional execution, equality testing, function calling, etc). At a higher level, compiled C functions look pretty much exactly like manually written assembly language procedures and structs are just blocks of memory with clear order and offsets. It's also very simple to interface C and assembly code without any hacks on either side.
Now, how much safety you can demand before the language turns into a unicorn is another question and would be interesting to explore.
Its basically C with Go like syntax, and a package system instead of headers. I aim to fully interop with existing C code and have no runtime requirements beyond what C has.
I'm not trying to go crazy with extra features over what C has though. An important part of what will make it work, is perfect interop with existing C code.
Yes I understand memory safety issues, but there is always a need for machine independent assembly languages. which C essentially is.
For the future, some of my ideas include:
a form of combined data types that require a type-switch to disambiguate (and is checked by the compiler), like algebraic data types. (essentially sugar over tagged unions)
It may also have a form of defer blocks similar to Go defer statements. (sugar over the C idiom goto cleanup;)
A final thing I considered was a built in reference counted pointer type. Just to partially alleviate memory management, but without resorting to a garbage collected run time.
I haven't considered the standard library much beyond the fact that I would G to have its own that is inspired by the good parts of other languages. At first it would have to rely on C libraries.
G won't be able to support every way that the Go standard library works. But if you need everything Go has, then you probably want to use Go to begin with.
E.g. while I applaud the idea, I found many aspects of your example code snippets ugly and un-C-like. This is not to say that I'm worthy of judging your work, but that a) so much of it is a matter of taste rather than objective thought and b) humans can adapt to literally anything, given time to get used to it.
> I always have it in the back of my head that I want to make a slightly better C. Just to clean up some of the rough edges and fix some of the more egregious problems.
I'm always mulling over that. "Maybe just namespaces, better macros, function overloading and member functions (eg this->) would work." Yet I'm sure Bjarne Stroustrup was thinking the same thing when he started on "C with Classes." The power to add features must be so overwhelmingly corruptive. So justifiable in each and every case, in the moment. And then you just can't stop. "Closures would be helpful. I'd love to write generic containers. Inheritance would save so much typing.", and the next thing you know, you're working on virtual inheritance, concepts and user-defined literals. :/
In my opinion, C really does need just a bit more to be great, but it would need someone with a will of steel to stop and leave things be after that.
Caseless switch is a great idea, especially no-fallthrough by default. Consider extending it with substituion, eg "case > 7:", "case >= 13 && < 16:", or "switch longvariablename as N { case N > 13 && N < 16:". Return types after function parameters are nice, consider saying "function foo(params) returns type" instead.
Tuples always feel important but in practice I tend to not like them. Variable definitions are a bit clunky, and inconsistent with struct variable definitions. Consider ctors/dtors and/or inline member initialization. I would recommend not to compress keywords: func -> function, const -> constant, etc. "type name struct" would feel better and more consistent as "class name". The C. prefix feels like it'd be great if you only use a very small number of C APIs, and very annoying if you used a lot of them.
The boldest and coolest part is building this directly on LLVM. My thought was always to transform the IL into C (trying my best to preserve line numbers and variable names), and then let GCC or Clang build that.
My ideas were unified function call syntax (never any functions inside classes), subclasses (declare a class inside another class, but the child class has direct access to the parent class' scope), namespace-aware multi-line macros (trailing \ is grotesque), and if implementing a backend instead of a translator, zero undefined behaviors in the language (do what people expect on eg signed integer overflow instead of GCC doing whatever is fastest yet obviously wrong. Aim for hardware that is actually used today instead of worrying about hypothetical PDP-11 programs, so no 9-bit chars.)
I think if you emit C code, it is much harder to get good debug information, which is important.
I am also thinking hard how to approach macros/preprocessor. For now there is none, but a powerful system could make all the difference.
macro function square(integer value) {
return value * value;
}
integer x = square(foo() + bar()); //foo() and bar() each only called once
//square(integer) will exist as an exported function symbol
macro inline twice(statement operation) {
operation;
operation;
}
twice(foo()); //invokes foo() two times
//twice() will not generate an actual function
macro class vector(type T) {
T* pool;
integer capacity;
integer size;
function append(type U) { ... }
...
}
vector(integer) powersOfTwo{2, 4, 8, 16, 32, 64};
//any vector(type) will generate all the class functions for itI think you can annotate the generated code with certain preprocessor instructions to tell the compiler about the real source lines in order to get proper debug information. I think lex/yacc or flex/bison use that. Don't know if it's a gcc extension.
C11 has _Generic, but it's pretty much an abomination: http://www.robertgamble.net/2012/01/c11-generic-selections.h...
Since it's a superset of C, why not just use C++ to program C-style code, using only the C++ features you want? C++ is a multi-paradigm language; you don't have to write object-oriented code if you don't want.
It's also really hard to control the feature-set of C++ you want. The second you start pulling in code libraries, you find yourself needing to use the parts you don't want. Not in your own code directly, but in your interactions with their library code. Catching exceptions, needing RTTI, etc.
Symbol exporting is also a really sore point in C++. The symbol names C++ compilers spit out are just terrifying. With a C language, it's trivial to write bindings for other languages like C# and Python.
But for what it's worth, I still program almost exclusively in C++.
Uh ... no? CFront did that when it was just "C with Classes", but nowadays with C++14? That would be absolutely terrifying.
One of its features is: "a C-generating back end, which can be used to generate C code for C++ programs"
source: https://www.edg.com/index.php?location=c_frontend
In fact, the Itanium C++ ABI (which has no relation to the architecture) specifically uses valid C identifiers to mangle C++ names to support products like EDG.
source: http://mentorembedded.github.io/cxx-abi/abi.html#mangling-ty...
This is not true. Using debuggers and crash dumps to reason about your running program is not fast or interactive at all. It doesn't matter how good the tools are. They interrupt the workflow.
Working in a REPL on the other hand, is real interaction with your program. You speak the language to the program and the program responds in the same language. This is fast.
Says you. Using core dumps is a different kind of interactive. Rather than testing out what a program would do or could do (as in a REPL), you're iterating on theories of what it did do. It definitely takes getting used to, but I'm often much faster debugging (even non-fatal problems) from a core file than iterating with a step-through debugger or rerunning the program.
It also helps a lot when the tools within the core dump debugger are good -- you should be able to quickly dump what's relevant to your program (not just threads and stacks and individual objects).
Would Scala be mainstream? OCaml or Haskell probably not.
I'm thinking fuckall, but it's always good to know.
https://www.fpcomplete.com/business/resources/case-studies/
http://engineering.silk.co/post/31920990633/why-we-use-haske...
http://bos.github.io/strange-loop-2011/talk/talk.html#%281%2...
http://www.quora.com/What-startups-use-Haskell-for-productio...
http://www.quora.com/To-what-extent-do-people-use-Haskell-at...
http://www.quora.com/What-is-the-largest-commercial-program-...
http://janrain.com/blog/haskell-janrain/
http://www.haskellers.com/jobs
And I've noticed "haskell" at least being listed in job postings more lately:
http://www.dice.com/job/results?caller=basic&q=haskell&x=all...
Such genuine curiosity.
We could debate whether it's wise to move on from C (I think in many situations it is), whether it's likely there will be more industry adoption of $MY_FAVORITE_LANGUAGE, but that's a different debate.
Also, if we keep favoring C (or Java, or whatever) because they are mainstream, then other languages will never become mainstream, and we will be stuck forever coding in horrible languages. Who wants that?
- Joe Armstrong
One could argue, easily, this is true for many of the popular garbage collected languages. It is certainly why I do not care for them. It is also why I do not care for some of the popular OS's, or most of the popular "software solutions". It is the unwanted delivery of the kitchen sink when all I want is a glass of water.
The OP says he's frustrated with C "because it seems to be trying (with limited success) to move to being a higher level language. There are times when I really do want a "thin layer over assembly", and C seems to have evolved to a place where that is no longer possible."
"seems to have evolved"
Some of my favorite programs that continue to work reliably, year after year, are written in what I guess some would call "primitive," K&R-like C and with little need for "the" standard libraries. I am not an expert C programmer, but I have no complaints when reading and editing this "primitive" C. I have seen others complain about it in forums.
One of my favorite source code comments is in the netcat source where the author says he's going to write the program as if he's writing in assembly. In my mind C should be just a thin layer over assembly. Essentially, to me, it should be a kind of "shorthand" for writing assembly. (That implies the author should think as if he's writing assembly. And C just takes away the drudgery of actually typing out all those lines of assembly instructions.) Now, that is just my opinion. I am not an expert C programmer. I know nothing.
There's the thing.
The reason that some problems can simply be solved more efficiently (on current hardware architectures) in C than in other languages is that there's a difference between what is "provably safe" and what is "provably safe to a compiler". That's the whole point of Rust's unsafe{}; not to write code that's actually unsafe on purpose, but to say, "this code is safe, even though you can't prove it" (indeed, perhaps unsafe blocks ought to be renamed to safe blocks; that would also alleviate the problem of opposite meanings of unsafe blocks and unsafe functions).
That is to say, it's not true that there are data structures impossible to write in a provably safe way without automatic GC, since whatever automatic GC can do the programmer can do too (although, admittedly, at some point one begins to blur the line between GC and manual freeing, such as with reference counting). It's just that it's a lot easier for the compiler to prove that GC-managed code is safe.
I'm not sure that being forced to have the compiler prove that your code is safe rather than being able to say, "no, I proved it myself with more advanced techniques" is a good thing. Admittedly, most programs written today in so-called "safe" languages are probably beyond the scope of their programmers' abilities to prove safe, but then again, a lot of those programs turn out to be horribly unsafe even with the compiler's help!
> perhaps unsafe blocks ought to be renamed to safe blocks
We have had long and heated discussions on this topic, actually. My personal position is that the code inside such a block has fundamental tension: the compiler must assume that it is safe, while the programmer must in all cases admit the possibility that it is unsafe. Given that keywords should be optimized for the reader rather than the compiler, I prefer `unsafe` to `safe`.However, that's not to say that there doesn't exist a better word entirely. Perhaps `unchecked` would suffice...
I would argue that it is better than C for its safety features alone. I hope in the future we look back at this decade with its never ending stream of buffer overflow vulnerabilities with disbelief that we could use something so insecure for something so vital.
Somehow along the way UNIX spread into the enterprise and brought C along.
I've always liked to think of myself as a programming polyglot, able to pick up and reason about nearly any standard language. I'm not sure how Rust is doing it, but the more I read and learn about it, the more confused I am. The syntax is just so bizarre and counter-intuitive to me. And yet I seem to be the only person in the world who doesn't "get" it. It's as if the language is being explicitly developed to be my kryptonite.
I think in Haskell's case, it's just doing too much with too little. Like how they can implement quicksort in three short lines of code, and to me it's just so much to unroll in my head at once; but it's hard to be sure for obvious reasons.
1. Forget everything you know and follow the types.
Really? What part of the syntax in particular?
impl<'a> AnyRefExt<'a> for &'a Header {
#[inline]
fn as_ref<T: 'static>(self) -> Option<&'a T> {
trait UncheckedAnyRefExt<'a> {
unsafe fn as_ref_unchecked<T: 'static>(self) -> &'a T;
}
//I am guessing the second line is special because Rust can't deduce "to" should be a TraitObject type?
let raw = require_single_field!(raw);
let to: TraitObject = transmute_copy(&self);
//is this adding header and 'static together?
pub trait HeaderMarker<OutputType: Header + 'static> {
//I have zero clue what this is doing
//I can't even fathom a guess
match *self {
Past => write!(f.buf, "0"),
ExpiresDate(ref tm) => tm.fmt_header(f),
}
//so _raw is a reference to a vector of bytes inside [] and it might possibly return whatever a box is of headers?
fn parse_header(_raw: &[Vec<u8>]) -> Option<Box<Header>> {
//is there really a language keyword called macro_rules! ?
macro_rules! impl_header_from_fromstr {
//what in the world is ($ty:ty)?
($ty:ty) => (impl Header for $ty {
fn parse_header(raw: &[Vec<u8>]) -> Option<$ty> {
//who thought "h.is::<H>()" was a good way to test a type? Why not "h.is(H)?" Why the => ?
//what is this even returning? a tuple of false and None?
Typed(ref h) if h.is::<H>() => {
(false, None)
},
//... why not UTF-8? Because "{}"?
format_args!(format_but_not_utf8, "{}", HeaderShowAdapter(h))
The names don't make any sense to me at all. What the hell is an AnyRefExt? How does an UncheckedAnyRefExt differ? What in the world is the match thing doing? Why the asterisk on self? Why all the apostrophes and colons and dollar signs and exclamation points? Why is there both -> and =>? How does an &' differ from a '?I could probably learn the language with a huge time investment. But for something aiming to fill the role of systems programming, it's just so incredibly different from everything else. It almost feels like they are going out of their way to make it as different as possible.
I know, this is all syntax stuff. C++ template meta-programming syntax is pretty insane, too, and took me a while to pick up. Yet when I attempt to read up on the syntax and the reasoning, it becomes even more confusing. I don't expect them to write a C++ clone, I understand this is their own language. But just a tiny modicum of reflection that people have spent 15+ years learning the syntaxes and behaviors of C-like languages would be so greatly appreciated. I mean, you see how Haskell is doing, and a lot of people blame that on the absolutely alien syntax to anything programmers are used to.
Yet, with Rust, everyone else with C-family backgrounds just seem to look at this, and go, "oh yeah that makes sense." Boggles my mind.
> I am guessing the second line is special because Rust
> can't deduce "to" should be a TraitObject type?
`transmute` and its kin are stdlib functions used to completely subvert the type system by casting any one type to any other type of the same size in memory. Their use is highly discouraged since they're so easy to misuse. You're correct in that Rust cannot deduce what `to` is supposed to be on this line (because by definition `transmute` is freeform and there is no context to infer the resultant type), hence requiring an explicit type annotation. > is this adding header and 'static together?
Within trait bounds, `+` allows you to specify multiple bounds for a generic type. So it's not adding, but it's taking the union. :) > so _raw is a reference to a vector of bytes inside []
> and it might possibly return whatever a box is of
> headers?
`_raw` is a reference to an array of vectors of bytes (in this case, a reference to an array of various binary data which has already been read into byte vectors). `parse_headers` will either return a heap-allocated header (`Box` is just a bog-standard thing allocated on the heap), or, if it fails to parse for whatever reason, it will return a value that clearly indicates that an error occurred. > is there really a language keyword called macro_rules! ?
Nope, it's just a macro (that just so happens to be used to define other macros). > who thought "h.is::<H>()" was a good way to test a
> type? Why not "h.is(H)?"
Type parameters are usually inferred, so you don't want them mucking up your actual parameter list. In C++ it would look like `is<H>()`, which is much nicer, except that this requires conflating parsing with type checking (see also http://en.wikipedia.org/wiki/The_lexer_hack). Rust thinks this is gross, so it arbitrarily uses `::` to denote the beginning a type parameter list in that context. It's gross, it sucks, it's literally the worst thing about Rust's syntax and 100% of people you survey will agree with you. But fortunately you rarely need to use it because type inference works so well. But inference can't here because Chris is trying to show off Rust's `Any` type, which is a library type that can be used to emulate dynamic typing, which requires a type switch to actually use the type for anything. > what is this even returning? a tuple of false and None?
Correct! > why not UTF-8? Because "{}"?
Beats me, this line is goofy. :) `format_args!` is an implementation detail for string formatting in Rust, it's unheard of to use it directly. > What the hell is an AnyRefExt?
It's an implementation detail that was copied-and-pasted out of the stdlib specifically to facilitate this code snippet. We typically don't bother to fuss over the identifiers of things that users should never need to worry about. :) > How does an UncheckedAnyRefExt differ?
You'd have to ask Chris Morgan, Rust has no control over what users name their own types! > What in the world is the match thing doing?
It's just a beefed-up switch statement, ubiquitous throughout Rust code as it is used for working with enums (tagged unions). See http://doc.rust-lang.org/guide.html#match > Why the asterisk on self?
The asterisk is the dereference operator, the same as C. > Why all the apostrophes and colons and dollar signs and
> exclamation points?
Apostrophes: ML-inspired syntax to syntactically distinguish lifetime parameters. You see one, it means you're looking at a lifetime.Colons: you'll have to be more specific, colons are literally every programming language designer's favorite symbol.
Dollar signs: only ever used while defining macros, which you should rarely ever read and even more rarely use.
Exclamation points: used to denote that you're using a macro and that what looks like a function call is actually going to expand to arbitrary code at compile time.
> Why is there both -> and =>?
`->` is used on functions and denotes the return type of the function. That's all it's used for. `=>` is used within `match`: `foo => bar` in Rust is the same as `case foo: bar` in C. That's all it's used for. > How does an &' differ from a '?
An apostrophe just indicates a lifetime, which all references have (though they're inferred in the overwhelming majority of cases).Many of the above things would have been made clear by reading the tutorial (http://doc.rust-lang.org/guide.html). I encourage you to do so if you'd like to learn more! Feel free to also hop onto our IRC channel at irc.mozilla.org to ask any questions at all, even ones that you think are stupid or obvious. :)
> you have chosen the worst possible piece of Rust code for someone who has never looked at the language before
That's my luck :P
I was in the middle of turning my HTTP message library from a series of "setHeaderName(value)" functions into just a single "setHeader(name, value)" and was told it had some innovative ideas on how to handle HTTP headers. And I have no doubt it does ... if I could make any sense of it.
Really glad to hear a lot of the vague names were library-specific, the really evil stuff shouldn't ever be used (though I'd argue it shouldn't be exposed outside of the stdlib if true), you definitely cleared up -> and => beautifully, I think the excess colons are a type annotation (used to 'type name' and not 'name: [type]'), and although I still have some questions (around ' and & specifics), I'll read up on your guide instead. Thanks again!
Different compared to: most programming languages? Systems programming languages? Just C/++?
> I mean, you see how Haskell is doing, and a lot of people blame that on the absolutely alien syntax to anything programmers are used to.
Its syntax fits how it operates. Should Haskell have -() at the end of functions, like C-like and ML? No, since Haskell uses partial application and currying - the ()-stuff would likely end up as cruft. A function of three arguments in Haskell is not passing in three values as a tuple: it is a function that takes one argument and returns a function - that function is a function that takes one argument and returns a function... how is that clear when you write it as `f(x,y,z)? Not to mention if you partially apply it.
Can you write Haskell code as a sequence of declarations and expressions? Arguably no, since it has non-strict evaluation; having a list of declarations and expressions would be misleading, since they can really be evaluated in any order.
What can be contested is the significant layout (though it is optional) and user-definable operators. Though I think Haskell code would become really crufty without operators, since they sometimes make expressions more clear than a series of prefix-oriented functions.
> is this adding header and 'static together?
Have you written any Java code? There is basically the same thing, except they use & instead of + to build unions of type bounds.
And about all the * and & stuff: That's pointer stuff. It is very similar to how it works in C/C++. I think it's just a bit simpler in Rust (you are required to use & to pass something by reference where in C++ you don't see at the call site if something is passed by value or by reference).
About Box: Terms like "auto boxing" are known from other languages (Java), so this is not a new terminology invented by Rust.
-> for defining the return type of a function is also not an invention of Rust. I don't know many functional languages but Haskell uses it, and C++11 uses it! So coming from C++ you should know that. ;)
If the Rust compiler is stopping you from doing something, there's good chance you really shouldn't be doing it.
I think Rust does a lot of things nicely, but I don't expect it to replace C simply because it adds a lot of complexity that people don't really want to deal with.
Incorrect.
"By using theorem proving and strict type checking, the compiler can detect and prove that its implemented functions are not susceptible to bugs such as division by zero, memory leaks, buffer overflow, and other forms of memory corruption by verifying pointer arithmetic and reference counting before the program compiles."[0]
0: http://en.wikipedia.org/wiki/ATS_%28programming_language%29
Rust makes sure you are checking those things. Some of those checks are only at compile time and have no runtime cost. Others have a runtime cost but in the vast majority of situations the 'performance hit' will be negligible. Anywhere it is important you would rearrange your code to move the checking out of any critical path.
Note that it's still the same thing you'd have to be doing in C anyway to be both correct and fast. Rust just makes sure you don't forget the correct bit.
http://doc.rust-lang.org/nightly/intro.html#safety-<em>and</...
Regarding complexity and being cumbersome, Rust increases compile time dependency, but reduces debugging complexity.
Personally I would rather spend a bit more time at compile time thinking about memory ownership than spending hours (or even days/weeks) tracking down memory problems.
I wonder if this statement is true since the invention of Go. A very fast compile was one of the design goals of Go, and its authors could afford luxuries (esp. In terms of memory use and object file size) that were unthinkable in the days of C. For example, IIRC object files contain the compiled code of their source plus the top hierarchy of modules called.
People have mentioned that changing a .h file in a large C project could trigger a lengthy recompile. AFAIK, Go minimizes this problem, so if the hype is correct there should be lots of projects where similar situations will be handled faster in Go.
My own empirical experience is very limited. I can confirm that Go compiles much faster than Java for my code, but that will surprise no one and it doesn't help us compare with C.
Does anybody have more tangible data regarding Go's alleged speed demon nature esp. as compared to C when compiled with e.g. VS or gcc?
First, I think the speed of C relies heavily on the compiler. C is not fast, not if I wrote the compiler. But GCC is very fast, and ICC at least as much.
I was a little confused by the Erlang wariness, but the reasoning seemed sound
> At Couchbase we recently spent easily 2+ man/months dealing with a crash in the Erlang VM. ... ... it was a race condition bug in core Erlang. We only found the problem via code inspection of Erlang.
That does not sound like a fun time, but once the bug is fixed, what's the big deal? Is there good reason to believe more bugs will come? It wasn't my ass that got bitten but I'd forgive Linux even if it had a race condition bug. (or if it crashes touching 1000 files in /etc)
I think C has been so successful because it made really good decisions about what to do for you. It eliminated a lot of those busy-work programming tasks without taking away your fundamental control of the machine. Automatic array bounds checking, garbage collection, ... they would have been unthinkable for the original goals of C. The stupid warts are annoying but I think C made fundamentally sound choices of what to do automatically and what to leave up to the programmer.
We all talk about modular, abstract code, but back in the day "modular" meant "use the same calling convention for every int(int, int) function", not "dependency injection". From that perspective, C must have felt like a huge leap in modularity and abstraction from assembler.
> That some of the most heavily used and reliable software in the world is built on C is proof that the flaws are overblown, and easy to detect and fix.
Easy to detect? Multiple times per year we find out about a security bug in Linux or Windows that has existed since the 90s.
Because of the existence of C++, mainstream C language development more or less stopped. ANSI C, ISO C, C99, and C11 don't differ by much.
(At one time, I was promoting an approach to make C memory-safe in a way that allowed mixing safe and unsafe modules to allow a gradual transition and rewriting of old code. See (http://animats.com/papers/languages/safearraysforc43.pdf). It's technically feasible but politically hopeless. I'm now pinning my hopes on Rust and hoping they don't screw up.)
I used to prototype things with Python by the method of sheer experimentation. When I found a suitable design, I'd rewrite it "properly" in Python and if that wasn't fast enough I'd think a bit and rewrite it in C. But that's happening less and less often these days.
Because C costs a bit more to write it makes me think ahead. And I've (once again) found that while prototyping in Python is fast it's even faster to think and then just write C. I write some boilerplate C and start adding the data structures... this could take a "long" time but once I've figured it out, writing the actual C code is a breeze. All the mental work has already been done and once the data structures are right, the implementation just follows.
Curiously, this doesn't exactly happen with Python. Because you can make a Python program run almost immediately, there's nothing forcing you to the productive idleness. In C, you'll have to write a bit of code first until you can get something meaningful up and running: this is the hatching time for what you really want to write.
I'm not sure why I haven't observed this with other languages. I suppose I would need to write hefty amounts of Java until I could get a program running somewhat, but this process of forced productive idleness mostly happens in C. Maybe it's because there are no layer beneath C and assembly and you intuitively know the tradeoffs and only a negligible delta of what you write and what the machine will actually do. Maybe it's because C feels so physical. I don't know. But so far C is the only language that gives me that process in a native, intuitive way, constantly telling me that "you must not rush, because you can't rush anyway".
Edit: also, not sure why you were downvoted, since your question at least deserves a reasonable answer.
Drive-by downvoting is what doesn't contribute anything in this case.
Perhaps if someone actually makes a detailed post on that subject then the post can be undownvoted. But at the moment there is no contribution, and the drive-by downvoting sorts it to the bottom, which is a good thing.
I'll do it.
@aosmith: It's interesting, because Go's creators explicitly aimed it at the "systems" niche, which is what C was created to do, but C programmers don't seem to have picked up the baton the way Pike/etc hoped.
I think it comes down to a handful of things, in no particular order. YMMV, of course.
(1) Control. C gives you minute control over memory, Go of course does not. That's very satisfying and useful to many C programmers. Also, it's often helpful to have a good idea what your code is mapping to in terms of assembly instructions.
(2) Speed. Go still isn't anywhere near as fast as C in many, many cases.
(3) Portability. Lots of C programmers need (or get satisfaction from) the ability to compile C on a zillion platforms. Go supports only a few.
(4) Garbage collection. (Sorta a revisiting of point #1). Many C programmers don't want GC in their code. Now, while I wouldn't mind if my copy of 'less' used GC, some programmers would. I know I don't want GC freezing my game. So this is an issue, not one that will ever go away.
(5) Libraries. Go's made strides but can't touch C's library selection. Of course, you can use C libs from Go, but that has its own complications, and if the author already prefers C, why pick Go?
(6) Newness. In the context of systems languages, Go is still very new. Nobody's built an entire userspace out of it, ya know? Not that that's necessary, but certainly when the Unix programmer sits down and selects C, s/he knows anything can be built with it. It's long since passed into respectability. Go's on its way, but isn't there yet.
I'm sure there are a few other things. Don't think there's a way to run a runtime-less Go. As Damien mentioned, C has a bevy of tools, Go's selection is much more limited. I'm sure others can think of some more.
I think you're taking downvotes far too seriously. I might even call caring about karma a reddit attitude ;) And as far as illumination, I think you have it backwards. Posts that actually have illuminating answers should be brought to the top, so that many people can learn from them. Posts that only have a chance of getting such an answer should lie further down. There's probably also a positive correlation between people likely to form a detailed illuminating response and people that read further down the page on a particular subject.
Other than C and C++, Go is one of the best languages for controlling memory. Not freeing, of course, but making it very clear where there are allocations, where there are copies, exactly how much memory is being used by any particular data structure.
Portability - you can run binaries created by gccgo in many many places beyond amd64/x86/arm. No, certainly not as many as C... but probably as many as anyone would ever want to support. So unless you're targeting a specific embedded application, then Go is probably fine.
GC is getting a big push in Go and in 6 months they're going to make hard real time guarantees about the GC... i.e., no more than 10ms pause every 50ms, and the GC will be hybrid stop-the-world/concurrent, so some memory may be collected while other code continues to run.
Otherwise, yes, you're right, there are (of course) some limitations to using Go over C.
However, there are some huge benefits to using Go over C as well... such as memory safety. There will never be a buffer overflow in your code. Also, concurrent code will be a thousand times more readable and easier to reason about.
That's not to say that C isn't the right choice sometimes... I just think the places where C is the best choice are becoming more and more rare, and not just due to Go, but Rust as well.
Better than language like Pascal, Ada...?
You won't get to hide from the annoying stuff in C (memory allocation, string manipulation, etc.), but the problem space (a simple game) is right there. It's also something that many, many people have done before, so it'll be easy to start.
> non-compiled language
C isn't trying to fit in that space. regardless, C code takes far less time to parse, optimise, and convert into machine code than all the languages i know of.
The biggest hold-up in the C world is rampant abuse of #include files.
true.
> Nope. Pascal is still king here.
for some values of "king" perhaps - perhaps it's faster to compile, but no arrays of varying lengths in a function call? and if i'm not mistake, a function must be defined before use..? pascal is incredibly restrictive, and perhaps that's why it's so fast.
I'm not going to argue that Pascal is somehow superior to C, it's far more limited, but there's lessons here to be learned on how to make a compiler rip through code.
Well, let's not change the goalpost - your initial assertion was that C has faster compile times. Then chances are that something's got to give, whether that is in the grammar, declaration order, etc.
When I learned C, after a few years of Pascal dialects C just felt like a stone age language.
It's possible to make slow Pascal compilers. But Borland put a lot of effort into that, and it really set a very high standard.
Most modern C compilers have used an integrated preprocessor for quite some time, and Clang/LLVM can emit machine code directly without going through a separate assembler.
For example, in a recent script I deferred loading the requests module and startup improved a few tenths of a second. Rinse, repeat.
Semi-relatedly, the downvoting around here has gotten completely out of control. It's sad, what's happened to HN.
And, go is built by some of the same people who built C for some of the same purposes (plus some new ones, like concurrency and networks), so it's arguably a "better C", and probably prioritizes some of the same goals. Simplicity and small surface core language area, in particular, but removes some of the dangerous aspects of C (which might be a problem for something performance-oriented, since well-written C is still faster than well-written go for most tasks, and may always be).
But, as you note, if you don't have to build, the cycle is even faster. I still consider a compile phase a slowdown over dynamic languages that don't compile.
Then again, so many people have introduced "compile" like stages into their dynamic process (SASS, LESS, test coverage checks, etc.). It may be difficult to completely get away from a compile like stage in any app of significant size.
Anyway, a large project in go is faster to compile than a large project in C, in my experience, but my experience is exceedingly limited.
He[2] is making a game, from scratch, in (mostly) C (but with a little bit of C++ niceties thrown in), and streaming[3] the entire process for an hourish, Monday -> Friday. It's intended to be a tutorial in both learning C and learning from-scratch game development (where from scratch means using GDI and like, parsing your own wav files).
It's really cool to watch, he's an entertaining guy, afaict is a pretty decent teacher (I know zero C, but C-style languages, and I _think_ I'm keeping up), so I recommend checking it out.
[1] http://handmadehero.org/ [2] https://twitter.com/cmuratori [3] http://www.twitch.tv/handmade_hero
The good news is that with VS 2015 you will be able to use Clang for compiling C code, so hopefully a complete C99/C11 implementation on Windows at some point.
Clang support is only planned as compile target for Android and eventually iOS.
Check going native videos from Connect.
I know they plan to officially support only Android and iOS. However, even today I can use Clang to create Windows executables from the command line. Once the support for Clang will be included in VS, it is only a question of time until someone will swap the Clang from the Android NDK with a Clang compiled for Windows.
Sadly, Microsoft don't see value in projects like FFmpeg, at least not to the point of making the compilation process with Visual Studio less painful.
I praise Microsoft for leading the way to eradicate C from our computers.
The point about the tooling I'm not so sure about. There's a lot of languages out there with well developed tools. And the tools tend to address the pain points: you have GC profiling in .NET, and you have Valgrind-type tools in c++.
This is called Go. Brought to you by the same people who gave you C.
Robert Griesemer, Rob Pike and Ken Thompson started
sketching the goals for a new language on the white board
on September 21, 2007. Within a few days the goals had
settled into a plan to do something and a fair idea of what
it would be. Design continued part-time in parallel with
unrelated work. By January 2008, Ken had started work on a
compiler with which to explore ideas; it generated C code
as its output. By mid-year the language had become a
full-time project and had settled enough to attempt a
production compiler.Gives the indication that C++ is faster in practice only when your algorithms are so complex that writing them in C starts to become tedious.
And frankly C++ can never match C until they introduce restrict. Only with C99 C was able to match Fortran. C++ does not have that luxury yet.
Also, I frequently see those types of algorithms that benefit C++. It's not just an STL thing.
Maybe the biggest reason why C++ is slower in practice is because the programmers tend to feel that using features unique to C++ almost mandatory. Even though technically the pure C program also compiles fine in C++.
I thought it was Fortran.
But the fundamental flaw is that in many cases a DSl that compiles to something lower level is the way to Go.
The important thing is that the C++ class overloads operators and mathematical functions, and the C class doesn't.
Example: http://coliru.stacked-crooked.com/view?id=0d295064bd367eca
Source: http://en.cppreference.com/w/c/numeric/complex
Have you tried that? Of course Microsoft refuses to implement C99, so you could only try it on non-Microsoft C implementations.
Just saying.
Especially since system code is the place where safety matters most.
and it's not like higher level languages are significantly better in this regard.
The last time I accidentally executed injected code due to a free error or buggy bounds check in the non-C parts of Python, or Ruby, or Lisp, or Erlang was ... well, never.
And no, the fact that C underlies all these languages isn't a testimony to the adequacy of its security features.
if C was so insecure, you would expect that interpreting python would be just as "insecure" as writing C.
Look, I know lots of people like you think that C isn't that hard to get secure code from. But if you were correct, we wouldn't be living in the security cesspool of memory bugs we are now.
Now, what is the combined LOC for all Python code that uses the CPython interpreter (and those LOC are in a higher-level language).
Now compare that to the amount of C code that that would amount to if all of that code was written (or, tried to be written) in C instead.
1) A lot of "Python" security vulnerabilities are caused by "C"-problems (overflow errors, etc). [1] and [2] are a couple from this year.
2) You'd expect interpreting python to be more secure than C because the interpreter acts as a filter over input reducing the attack surface, meaning that potentially vulnerable paths are minimised are better tested than if an entire program was in C.
However programs written in C may or may not be insecure.
Of those programs you would expect a compiler or interpreter, that has been tested in the wild on millions of programs, and so you are more likely to find the security bugs. Also we hope these programs are written by very talented engineers.
On the other hand Joe Enterprise hacking up a greenfield web service for the first time in C ... more likely to have security bugs.
If Joe Enterprise moves from C to Java. Less likely to have security bugs.
If memory safety ever becomes a practically solved problem (e.g. somebody's running an OpenBSD-style count of how many days since a new memory bug popped up and it gets past, I don't know, hate to be greedy, how about 3?), then I'll be merrily banging away on the other aspects.
Don't get me wrong -- the Linux kernel is a magnificent piece of engineering, and an excellent choice for hosting a mainstream server, I run a number of such myself -- but let's not kid ourselves about its security limitations.
Sometimes I think the ebola crisis is created by a GCC developer. "I'm sorry for those people, but I was entitled to it, that guy there dereferenced a wild pointer, I could have cured cancer instead, but no".
No, C is not at fault. The programmers are at fault. But it is prudent to ask, when some of the world's foremost programmers produce an apparently limitless number of CVEs as they do, whether they could use some help from the language. If the answer is yes, we should recognize that such help cannot come from C.
uKernel in C , smaller codebase.
Drivers in (everything, possibly Ocaml/Lua/Common Lisp/C++)
Os in everything.
The Linux kernel is not written in C but in a DSL that there is not yet a compiler to generate C. This is the same like Enlightment Object System and GObject.
moreover drivers should be written in a DSl language (Termite-like) that supports concurrency and provided by spec writters as an appendix.
The driver is two parts. The OS adaptation Layer and a Formal description of modus Operandis.
Consider USB, normally the spec provides an implementtion i a DSL that "compiles" to the prose.
One can mechanically check for invariants and properties and compile to an OS package.
I think it is possible. Packages for example could describe transports
e.g. module BT uses an Ocaml-like interface for transport that can have various implementations like USB transport.
But all could be written in the same DSL.
Of course Kernel developers are allergic to memory management. Rust/Ocaml/D are the anti-histamine.
And the endless supply of ignorant C/C++ programmers (of which I am not suggesting Damien is a member) who profess to management that they can easily write secure code does not help.
Of course it's possible. If your issue is memory checking you can use mudflap or valgrind.
> Surely that makes it pretty damn poor at what it does?
No, it surely does not.
> Especially since system code is the place where safety matters most.
And yet somehow they manage. The security issues you find in system code are not things that would be helped by range checking.
It might be theoretically possible but in practice nobody has managed to do so with any non-trivial application.
And in fact if someone used valgrind and fixed any errors (which plenty of people have done successfully) that completely disproves him.
He should retract his false statement due to evidence against it.
People have unquestionably used Valgrind to find errors, in trivial and nontrivial applications. The question here is whether the result is "safe code". It may be, but the parent does not seem to be speaking to that question, hence my attempt at clarification.
> ... the result is "safe code". It may be, but the parent does not seem to be speaking to that question ...
Here we differ. The ability to successfully run valgrind (and presumably remove all errors) does actually speak to the question, and it answers that the result is in fact safe code.
It is not necessary for the compiler to assure safe code, it's perfectly possible to do it with an external tool. And as a result C is a safe language as long as you use both tools.
Any further errors will not be fixed by any supposed "safe" language either.
Imagining that another language will make a difference is a mistake. It will not.
Only if you can be sure you have touched all the interesting portions of input-space. Ideally your tests are doing this, but wherever your tests miss aren't covered by valgrind or mudflap but are covered by a change of language. That's a part of what's meant by safe. It's not that it's impossible to write correct code in an unsafe language. It's that it's difficult to impossible to verify that you have written correct code. I'm very willing to include available tools in that calculation, but valgrind and mudflap (while tremendously useful) cannot give guarantees.
I see this kind of statement a lot, and it simply does not hold up to even a cursory look. Where are the awkward backflips of syntax, special compiler or VM knowledge, or version specific optimizations here?
http://benchmarksgame.alioth.debian.org/u32/benchmark.php?te...
http://benchmarksgame.alioth.debian.org/u32/benchmark.php?te...
Why are there so many java web frameworks/libraries/servers that offer performance close to C?
http://www.techempower.com/benchmarks/#section=data-r9
>Critically important to developer efficiency and productivity is the "build, run, debug" cycle.
This is a pretty bizarre claim. Sure, having an equally archaic and primitive process but 10 times slower is obviously worse (C++), but that doesn't mean having an archaic and primitive process is good if it is fast. I rarely ever run my code, much less debug it. Freeing myself from that horrible way of working was one of the greatest things about working in a language with a useful type system.
I'm not sure what you mean by this, because it sounds horribly wrong.
Consider the halting problem. It's pretty common knowledge that given a program and some input there's no general algorithm that tells you if that program halts when the programming language is turing complete. However, we know that every simply typed lambda calculus program halts if it passes the type checker, so did we defeat the halting problem? No we cheated because simply typed lambda calculus isn't turing complete.
The problem with type theory is that sometimes it excludes good programs that we want to run just because we don't know how to prove that the program is good (or maybe it's even unprovable). So we try to develop new type theories that will allow us to prove more and more good programs are in fact good.
1. Types absolutely can capture (trivially often) invariants which can never be satisfactorily covered by unit tests. This is the difference between proof and evidence.
2. Unit tests are far easier to write for many kinds of program behavior. Even though you can encode essentially anything you want into sufficiently powerful type systems, their proofs can be too challenging to practically build.
3. Type checking works very differently from testing. It's not red-green-refactor. It's more like "describe your problem in types and then slowly discover the only possible program which is correct". The feedback cycle here is very fast, but sometimes more inhospitable. Major research focus is on making this better, though. I'd say some of the most advanced HCI work is being done in dependently typed proof systems... but it's targeted only at people who understand and use dependently typed proof systems to begin with.
4. Unit tests often duplicate functionality which is trivially and automatically inferred by a type system. This is why, for certain invariants, types are "free" and unit tests are both incomplete and a big violation of DRY.
So, essentially, the unit tests which get written for you automatically are the trivial ones which are inferable properties. A dynlang usually does not have sufficient structure to enable this inference so you have to cover it with unit tests or contracts. More advanced, un-inferable properties can be handled and checked on compilation as well, but typically it takes some effort to build this stuff. Interestingly, though, you usually do it with heavy automation driven by the (partial) inference that the compiler provides—it becomes a conversation between you and the compiler as you develop the program which satisfies the type specification you gave.
Type correctness does not mean your program does the right thing.
More specifically, it's quite certain that types are capable of solidly dispatching some kinds of errors and tests a different kind. In other words, they are orthogonally optimized and combine well.
If, for example, I give one of my Haskell functions the type "f: [a] -> a", I know that it a) does not have side effects, and b) must pick some element from the list to hand me back, i.e. it cannot just construct or cast "something" to an 'a' and give me that. (There is a caveat around explicit "unsafeXXX" usage, but that's mostly beside the point.)
Both of those properties constrain behavior and are impossible to prove using unit testing.
Or, for that matter, how can you tell the function doesn't return a completely new 'a' from hardcoded values?
f: forall a . [a] -> a
which would make it more explicit that the function has to work for any given 'a'.[1] ... which I think I should have actually specified. The function type given does not preclude non-termination in Haskell. (You can specify such things in Idris/Coq/Agda, but the effort of implementing f can be much higher if you have to prove totality.)
Nonetheless, unit tests aren't a panacea. If all you do is just run your unit tests, watch them pass and proclaim "Well, everything works! No need to do any acceptance, integration, system, fuzz or other form of testing! Not even gonna run it, let's just ship!", then that's quite stupid, to put it mildly.
page 263 "Programming Languages: Application and Interpretation" Shriram Krishnamurthi 2003
>There is one type system (with types such as int, double and even function types) at the level of the program
Yes, that is the only type system.
>and a different type system, defined solely by lengths of bitstrings, at the level of execution
That is not a type system. By definition type systems are at the program level. Data classification occurs at the level of execution, and such a system exists in virtually every language whether it has a type system or not.
http://papl.cs.brown.edu/2014/index.html
17.5 Types Versus Safety
If you have ever used the phrases “strong typing” or “weak typing”, define them.
That’s what I thought. But thank you for playing.
Indeed, the phrases are not only poorly defined, they are also wrong, because the problem is not with the “strength” of the type checker but rather with the nature of the run-time system that backs them.
I think the thing that people are so excited about for recent type systems is that they have some basis in type theory. So maybe we can call these type systems "mathematically typed" or something along those lines?
C has some unfortunate integer conversion rules and a style that often encourages the use of (unsafe) void pointers. Perhaps more importantly, the only way to implement polymorphism is with casting or unions, both of which are very much unsafe.
Still, you can write safe code in C, at least once you write an object system. Use structs rather than typedefs, avoid casts, use only memory-safe things, write wrapper functions to allow you to do allocation and deallocation in a safe way. It's actually possible to reach a similar safety level to something like Haskell - but it will require a lot of tedious hand coding because the language doesn't have the abstraction facilities that would let you write these helpers generically. Eventually you end up greenspunning.
> Nonetheless, unit tests aren't a panacea. If all you do is just run your unit tests, watch them pass and proclaim "Well, everything works! No need to do any acceptance, integration, system, fuzz or other form of testing! Not even gonna run it, let's just ship!", then that's quite stupid, to put it mildly.
Sure. But I think it's reasonable for your day-to-day code loop to be edit / run unit tests, and to do the fancier testing or manual testing maybe a few times a week.
In a language like Haskell (actually I use Scala) you can get to the same point with compilation. It won't just happen without working at it, you need to think about what invariants are important and then encode them into your types. And you wouldn't want to release/deploy without executing the program or running some tests. But you can get to the point where in day-to-day coding an edit/compile cycle is enough.
page 263 "Programming Languages: Application and Interpretation" Shriram Krishnamurthi 2003
The term was defined by Barbara Liskov in 1974: "whenever an object is passed from a calling function to a called function, its type must be compatible with the type declared in the called function". Very clear, very simple, no nonsensical or meaningless problems at all.
Type safety is discussed in "Types and Programming Languages" http://books.google.com/books?id=ti6zoAC9Ph8C
"Strongly typed" is not.
All the links in the comment were showing off Java.
I am.
>and is assuming that because the compiler accepts his code it is therefore correct
I am not.
>of course this is a fallacy
Just as fallacious as assuming your code is correct because you tested it or ran it.
Theoretically you could embed enough analysis in there to have theories about the actual code you're running, too, but I don't think as many analysts care about theorem proving—they're more of an algebraicist's toy.
To some degree, if you can't write code without running it, it's a sign that you don't really understand the code you are writing.
Most mathematicians have never "run their code"
Oh, for heaven's sake. You don't really think that, do you?
The future of Rust and systems programming is very bright indeed.
...Huh, and the author thinks C doesn't? :)
(Well, except the VM part.)
"What Every C Programmer Should Know About Undefined Behavior" springs to mind: I believe it's been discussed several times in HN.
And then you follow with 2 links that are nowhere near idiomatic Java. Plus they are microbenchmarks (which he also mentions).
>Plus they are microbenchmarks (which he also mentions).
Notice how I linked to the web benchmarks too? Your complaint was addressed in the post you are complaining about.
Perfectly normal code that treats Java like C.
>Notice how I linked to the web benchmarks too?
This does not negate the fact that you also linked to these microbenchmarks, which was what I addressed.
Plus, the web benchmarks you linked to are also microbenchmarks. They are not measurements of a real life application -- it's mostly getting the framework to print "Hello World", or to do a few database calls, etc.
Again, put forth an argument. If all you can do is make unsubstantiated claims, then you might as well just keep them to yourself. You aren't furthering a discussion.
>This does not negate the fact that you also linked to these microbenchmarks
No, it negates your fallacious point. The claim that it only applies to microbenchmarks. The web benchmarks are not microbenchmarks, therefore it clearly does not only apply to microbenchmarks. Are you being deliberately dishonest?
>Plus, the web benchmarks you linked to are also microbenchmarks
If you consider an entire web server and framework to be a microbenchmark, then you are clearly being intellectually dishonest. This is not the forum for that kind of garbage, take it to reddit.
a) Idiomatic Haskell takes ~25 minutes
b) Monadic Haskell takes 11 seconds
c) C++ takes 3 seconds
d) C takes 1 seconds (5x less memory than b))
If you look at all the micro-benchmarks, C uses far less memory than Java or Haskell and the memory is the bottleneck in the most cases.
This is the reason why HPC is FORTRAN, C and C++.
Reference: Computational Topology via Functional Programming: A Baseline Analysis
1. I don't think Monadic == Imperative...
2. Can you post the Haskell code?
I know that's not true because I do it on a regular basis. For problems that require writing imperative code you end up writing imperative code. This is not a problem, writing imperative code in haskell is far nice than writing it in C.
>Monadic Haskell takes 11 seconds
"Monadic Haskell" is meaningless when making performance claims. If you want help fixing your code, you need to post it. And idiomatic haskell uses monads all the time. Is this another case of the common "I am pretending that deliberately doing things the slowest way possible is the definition of idiomatic" trolling?
>C takes 1 seconds
If your C++ is taking 3x as long as your C you are doing something wrong.
To clarify, I did not write the code (mine is the C version), the results are from the paper. Read it, beat it and post results.
The way you can escape from this performance trap is to (as usual) write explicit state passing instead of direct recursion. GHC can inline and fuse these away much more nicely and often arrives at a very tight, C-like inner loop.
I'm not sure if I call this bending over backwards though since Haskell style has always been to avoid explicit recursion and this kind of state-passing transform can be hidden behind the same set of combinators you're used to.
No, he gives reference.
>Do you know haskell?
Can you support your claims? Surely the burden on proof is on "Haskell is just as fast".
>No, he gives reference.
Why are you speaking to what someone else knows? Your desire to see your own words exceeds your ability to string together words in a constructive manner.
>Can you support your claims? Surely the burden on proof is on "Haskell is just as fast".
I made no such claim. Your dishonesty is appalling.
Those were aimed at me.
And these were aimed at at the parent:
"you don't actually know", "are just repeating the claims made by the authors", "do you know haskell?", "trolling", "you are doing something wrong".
Plus condescending responses like "if you want you code fixed" and "I'm not going to buy a book just so I can spend the time writing code for you to dismiss" -- when given a reference by the parent. I only intervened because I didn't like your constant condescending tone.
Are you actually here to discuss, or you just came to insult people? If it's the second, maybe try Reddit?
And they were questions.
>Are you actually here to discuss
Yes, that is what questions are for. Why are you here? You don't seem to have any intention of trying to contribute in a productive manner.
Then don't make them all be passive agressive.
https://news.ycombinator.com/item?id=8671889
If you avoid recursion and use explicit state passing and create an API with combinators hiding the state passing plumbing you get very fast Haskell.
I believe many people never get to the "write combinators to hide plumbing" part and assume to get very fast Haskell they have to duplicate the explicit state passing everywhere.
Specially the fact that C is fast because of 30 years of compiler research, tends to the ignored by C zealots.
Lets not forget that 30 years ago C compilers used to suck really bad, and most of us that were coding back then were using Assembly for real men work.
C, Pascal dialects, Modula-2, Algol dialects, PL/I and many other candidates to systems programming were all equally bad in code quality.
But of course, C guys like that new generations think that C was the first systems programming language and its compilers always produced fast code.
What a shitty, shitty quote.