1. 15
  1.  

  2. 6

    Though it is certainly the case that C++ will usually outperform the pants off of Python, and save costs hardware wise, one has to factor in other costs as well. Will time to market be slower? Will prototyping new features take longer? Will hiring developers be more difficult, or more costly? Are there pieces of the software that don’t require the same performance characteristics (and which scripting is appropriate)? Etc, etc. This post was extremely light on questions like that, and justified performance based on a potentially sketchy microbenchmark.

    1. 1

      Will time to market be slower? Will prototyping new features take longer? Will hiring developers be more difficult, or more costly? Are there pieces of the software that don’t require the same performance characteristics (and which scripting is appropriate)? Etc, etc.

      IMHO, thinking all of those factors while starting startup is very time-consuming since we don’t know if the idea is work. Though I agree if the context is a running a startup, all those factors should be highly considered.

      1. 1

        For the kind of task they are doing, I wonder if Python wrappers round C++ libraries at time critical points would serve well.

        1. 2

          Yup. That’s kind of what I’m getting at. Or, maybe even the opposite. Python can be embedded, so maybe there are parts which could be scripted–the web stuff seems likely. But, I’m not a domain expert, and all that jazz…

      2. 4

        Manual memory management is the most popular misconception of C++. Since C++11, it is now recommended to use std::sharedptr or std::uniqueptr for automatic memory management.

        This exemplifies the biggest problem with C++: every new version introduces new features, but there’s no effort to deprecate and remove the bad ways of doing things.

        Comparing with the numbers on https://www.techempower.com/benchmarks/#section=data-r11&hw=peak&test=json a factor of 40 for a basic JSON test seems a bit off but too implausible. The thing is it’s unlikely to be a factor of 40 so much as an upper bound. Python could handle 40,000 requests/second/node. I’ll bet this service never ends up handling more than 40,000 requests/second/node.

        More importantly though, Python is not the only alternative to C++. The techempower benchmarks show my own preferred language (Scala) at 95% of C++ performance. Lua, Clojure, Ruby, Crystal and Go all have options that are within 50%. I think everyone agrees that pure Python isn’t an option for CPU-intensive code (though 95% or 99% Python can be very competitive), but there are languages that give you the best of both worlds.

        1. 3

          I am taking the lead on the technical side and I am most comfortable with C++ so decided to build our OLAP engine with it.

          In context of starting a startup, this is a very good decision.

          1. 2

            Weird article and comments.

            The guy used C++. It’s not the coolest language, but who cares? I’m not sure why he has to justify it to the Internet, and why people are taking the bait.

            1. 2

              C++ is not a dynamic language but modern C++ (C++11/14) does have type inference.

              No, C++ does not have type inference. Nor does Java or C#. These languages have type deduction. What’s the difference and why is this person on the internet being a crab about it? Because inference requires looking at how the value is used and inferring what type it must be, hence the name. Deduction is using the type information that is already present in the expression and just not making you write it again.

              Inference:

              (* the (+) operator is defined only for int's,
                  so x must be an int and f must return an int *)
              let f x = x + 1
              

              Deduction:

              // 1 has type int, so x must be an int
              // because that is the type of the expression it equals
              auto x = 1
              
              1. 11

                Your distinction between “type inference” and “type deduction” sounds interesting and sometimes useful, but I don’t think your definitions are in wide enough use for you to call the article’s usage incorrect. When I make searches including the phrase “type deduction”, the top results only use that term as a synonym for type inference. The Wikipedia article on type inference doesn’t make any distinction. And when I have read descriptions of this feature in C#, C++, Scala, and Kotlin, I have only seen the term “inference”, never “deduction”.

                If you still want to change people’s usage of “type inference”, you should first acknowledge to the reader that you are suggesting a change to the current meaning, not correcting the meaning.

                1. 8

                  …but I don’t think your definitions are in wide enough use for you to call the article’s usage incorrect.

                  For the record, I’ve been loosely following (but not studying) type inference in programming languages for nearly 13 years, did a PhD in programming languages, and the comment above is the first time I’ve ever heard of the term “type deduction” in contract to “type inference”. I see the distinction but let’s face it, it’s subtle.

                  1. 4

                    I agree that this is not a widely-used term, but I disagree that the distinction is subtle. It makes a pretty big difference whether the types are fully inferred or inferred in some places from explicit declarations elsewhere.

                2. 2

                  I don’t understand the difference. Say that operator+ is overloaded for several types in C++. Then auto x = somestring + "bar"; and auto x = someint + 2; would both look at both sides of operator+, try to find an overloaded declaration, and then look at the return type to decide what type to give to x. What’s the difference between function signature matching and type inference?

                  1. 3

                    In both of the C++ cases, the type of somestring and someint are known by the compiler, it is deducing the type of x by simply matching with the type of the expression someint + 1. In the Ocaml example, the type of x is not known, but inferred from the usage that x must be an int, which means the type of x + 1 is also inferred. It’s the difference between having all of the input information for an expression and asking what the type of the result is versus only having the operators in an expression and determining what the types must be in order the expression to be correct.

                  2. 1

                    I don’t see how these really differ at all.

                    let x = 1

                    uses the same exact deduction rule as auto x = 1 in C++. C++’s algorithm may have fewer typing/inference rules, with which it can make deductions, and even fewer places, syntactically, where it can actually choose to apply one of them.

                    But, all that means is that C++’s algorithm isn’t based on Hindley-Milner, and is instead C++’s Type Inference algorithm. There can be many algorithms to deduce types, after all.

                    1. 1

                      In the example you gave, the end result is the same. However in the example I gave of type inference, the expression is x + 1 where the type of x is not specified. The type system has to infer the type of x given the (+) operator. There is no equivalent example in C++ because in x + 1 in C++, the type of x is always known beforehand.

                      In C++, C#, Java and (I think) Go, they are using information the compiler has been calculating this whole time: the type of the expression on the right hand side of the =, but now, instead of checking if the type of matches what the developer has explicitly stated the type is, the compiler just assigns the type to the variable.

                      I am claiming that these two things are fundamentally different in a significant way, not just how they get to the answer but what they are capable of doing, such that they should not be called the same thing.

                      1. 1

                        In C++, C#, Java and (I think) Go, they are using information the compiler has been calculating this whole time: the type of the expression on the right hand side of the =, but now, instead of checking if the type of matches what the developer has explicitly stated the type is, the compiler just assigns the type to the variable. I am claiming that these two things are fundamentally different in a significant way, not just how they get to the answer but what they are capable of doing, such that they should not be called the same thing.

                        I don’t get what you mean by “has been calculating this whole time.” Especially as it relates to OCaml, in which there are explicit types due to atomic values, primitives and explicitly declared types in imported modules and interfaces, signatures. (e.g. 1 can’t be a float, 1.0 can’t be an int. +. is float -> float -> float …). The incremental context that C++ builds is fundamentally no different–the rules sometimes differ of course: 1 could be int | float, 1.0 could be int | float. Formal parameters have been tagged already, etc. But, in all cases you have a priori knowledge.

                        In regards to “instead of checking if the type of matches what the developer has explicitly stated the type is, the compiler just assigns the type to the variable”, I don’t get your point. This is what type unification is. If you don’t know the type of the LHS of an assignment the first time it is assigned, the type is the type of the value of the RHS. If you know that the LHS is an int already, then it is an error to assign it to a RHS which computes a value of type string.

                        But, of course, the rules I just stated above are dependent on the language. Fundamentally, the process of type checking, and type inference is exactly the same for all languages except for:

                        1. The set of rules
                        2. The algorithm for picking the most appropriate rule.

                        (Note: I wrote more, but I’m pretty sure it’s irrelevant to the discussion, and I’m possibly missing some nuance in your point anyway)

                        EDIT: On Second thought, I think it might be useful.

                        One such rule for C++ might pertain to VarDecl’s in the AST. A VarDecl has the form TypeTag Identifier, and it’s affect on the type context is that TypeContext[Identifier] = TypeTag. This is no less valid a rule than TypeContext[+] = Function(int, int, int) in OCaml.

                        1. 1

                          This is what type unification is

                          And C++ and friends do not have unification.

                          Again, go back to my example of type inference: x + 1. There is no local information specifying what type x is, the type checker figures it out because of how the developer uses x. In this case, with (+), which has the type int -> int -> int, therefore x has to be of type int. There is no equivalent in C++, C# or Java. In the expression x + 1, the type of x must already be known and specified.

                          I don’t get what you mean by “has been calculating this whole time.”

                          Given: T x = expr, the compiler, this whole time, has been evaluating the type of expr and verifying that it has type T. This is why, even in pre-C++11, std::string x = 1.0 fails to compile due to a type mismatch. What C++, Java, and C# have done is simply made x the type of expr, since it has already been calculating that type. This is fundamentally different than what ML’s and Haskell do, which refine the type of a value based on what operations are performed on it.

                          1. 1

                            I have never heard of a compiler that type checks while it is parsing, or even builds up a type context while its parsing, and so, I still do not get the distinction between “this whole time”.

                            Both sets of languages do this in a separate step. Both sets of languages have rules which they apply. Both sets of languages have an algorithm for applying said rules (as you pointed out OCaml has type unification, where as C++ does not). Both sets of languages provide some level of proof that the program is coreect, from a type standpoint (with OCaml obviously going much farther here).

                            The only difference that you’ve pointed out here, is simply that OCaml uses unification, which is just part of the algorithm for applying the rules. Yes, the algorithms are different, but that doesn’t make one less inferency than the other.

                            1. 1

                              By “this whole time” I mean “since the compilers for these languages have existed”. In order for the C++ compiler to error out on the following code: std::string x = 1.0, it has to know the type of the left-hand side of the = and the right hand side and make sure they are the same type. All Java, C#, and C++ have added to their language is instead of checking the types match, if you use auto or similar, it just gives the left hand side of the = the type of the right hand side.

                              The argument I am making is roughly equivalent to a “gun” and a “canon” being different things. They both shoot things out of a barrel, but they are sufficiently different that they should have different names.

                              1. 1

                                We’ve been considering simple expressions and statements. What happens when multiple statements / expressions are involved?

                                In C++:

                                auto x = *some_expression*  //  TE[x] = Tof(*some_expression*)
                                auto y = 1 + x //  TE[y] = Tof(1 + x) = Tof(1), Tof(*some_expression*)
                                

                                How, then, is this any different than in OCaml? (admittedly, my OCaml is extremely rusty)

                                let x = *some_expression* in
                                    let y = 1 + x in
                                       y
                                
                                TE[x] = Tof(*some_expression*)
                                TE[y] = Tof(1 + x). (** though, in this case, x must be an int because of a priori information **)
                                

                                If TE[x] was float, the program wouldn’t compile. It didn’t try to unify 1 + x first. It tried to unify 1 + x:float which fails. Of course, in the C++ version, if TE[x] = float, then it will also simplify the TE[1], TE[*some expression*], to float since 1 is compatible with 1.0, under C++.

                                1. 1

                                  Your first example is the same as the auto x = 1, on the first line the type of x is known because the type of some_expression is known. Then the type of y can be known because x is known. Where they diverge is this:

                                  let f x = x + 1
                                  

                                  The type of x is not specified, but it is inferred that it must be an int because (+) is used on it. This is not possible in C++, Java, or C# because all of the input types must always be known, unlike in Ocaml.

                                  1. 2

                                    The distinction is silly, with this example, because the type of x is known. The type of + is known in advance.

                                    There’s no magic here! In all cases you are using previously known information to assign a type.

                                    Edit: Whether or not you assign the type via unification or a simple deduction rule is irrelevant.

                                    1. 1

                                      I’m not sure why you are claiming it is irrelevant, since this is the exact thing that C++, C#, and Java are explicitly incapable of. They require all type information available and can only determine the type of a resulting expression when all input type information for that expression is already known. Type inference determines the type of values based on the operations in the expression. This is a huge difference. The following is impossible in C++:

                                      int foo(x) { return x + 1; }
                                      

                                      Whether or not your think this distinction is meaningful enough to warrant calling them different things, it is completely wrong to claim the value of x is known. It is inferrable, but that is the exact point of the algorithm. In the C++ case, the value of x must be known beforehand, it is simply assigning a type to the left hand side of an = where previously it was comparing the types on each side of the =.

                                      1. 2

                                        it is completely wrong to claim the value of x is known. It is inferrable, but that is the exact point of the algorithm.

                                        We’re not talking about values, but types.

                                        In the program int x, the type of x isn’t known by the compiler until it infers from running an algorithm on a set of rules that the intention was that x is type int.

                                        In the program auto y = x + 1, the type of y can be inferred by utilizing the fact that x is of type int (by previous inference), and the function int operator+(int, int) can be looked up and its return type used as the inferred type of y. Just how did the algorithm know that int operator+(int, int) had return type int when given int, int? It inferred it from the method signature, in the same way we inferred x above.

                                        You’re probably angry that I chose to use the word infer for such a trivial explicitly typed statement such as int x. What is explicitly typed, exactly? It’s an abstraction for a trivial type inference rule.

                                        It’s all about the rules, an algorithm for selecting and applying those rules, and the prior information (context) you’ve been given / figured out before.

                                        That’s all any type system is, and all I’ll say about this.

                                        1. 1

                                          We’re not talking about values, but types.

                                          Whoops, yes, of course, that was just a typo on my part.

                                          You’re probably angry that I chose to use the word infer for such a trivial explicitly typed statement such as int x. What is explicitly typed, exactly? It’s an abstraction for a trivial type inference rule.

                                          The difference is the distinction between inference and deduction.

                                          • Inference: the process of deriving the strict logical consequences of assumed premises.
                                          • Deduction: process of reasoning in which a conclusion follows necessarily from the premises presented, so that the conclusion cannot be false if the premises are true.

                                          In the C++ case, which I am calling type deduction, the type of an auto value must be equal to the type of the expression being assigned to it. In the Ocaml case I have presented, the type of x is the consequence of assumed premises (the type of the operations), and either of them could be wrong (which is what makes debugging type errors somewhat painful in Ocaml and friends).

                                          I do not see how I am making a less significant distinction between type deduction and type inference than the very words themselves. To me, it also seems clear that the C++, Java and C# solution is clearly deduction. Given x + 1, and x is an int (because we know because we have to know this), then x + 1 is also an int. In the inference case, x needs to be an int to make sense as well, but if someone calls f with a float, who is wrong? It’s all about assuming things and trying to make sense of them. This seems completely in-line with the dictionary definition of deduction and inference.

                                          1. 1

                                            If we’re getting our dictionaries out (I have slightly different definitions):

                                            inference: The act or process of deriving logical conclusions from premises known or assumed to be true.

                                            deduction: The process of reasoning in which a conclusion follows necessarily from the stated premises; inference by reasoning from the general to the specific.

                                            These are very similiar definitions.

                                            Both:

                                            • result in conclusions
                                            • that are derived or reasoned about via assumed / stated, true premises.

                                            Derive and reasoning might lead us astray though.

                                            derive: To arrive at by reasoning; deduce or infer:

                                            reasoning: the act or process of drawing conclusions from facts, evidence, etc

                                            So, reasoning and derivation are basically the same.

                                            Well, what is evidence?

                                            evidence: Something indicative; an indication or set of indications

                                            And, indications? Something that serves to indicate; a sign:

                                            So, in inference, we either assume, or know something to be true. We have facts and evidence. In deduction, we use stated premises (facts), and reasoning (which uses facts and indications (indications aren’t facts, necessarily, let’s call them assumptions)).

                                            We have facts and evidence in both case and draw conclusions from it. In other words, by definition, they are the same.

                                            If you think about it. Your definition of inference can’t be true. In OCaml, atomics such as 1, and 1.0 have known types (edit: previously values. ugh typos). They aren’t assumed to be int, or float. So you also have facts, not just assumptions.

                                            1. 1

                                              Given that we have come to the point of pulling out dictionaries, I don’t think there is any hope in coming to an agreement. My point, in the end, is that there is a sufficiently large distinction between what the type system can figure out on its own in something like Ocaml versus C++ to warrant calling them different things. I still don’t know if you believe they are equivalent or different, and if you think they are differnet if you think they are sufficiently different to be called different things and you just disagree with my name choices.