Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

TL;DR use it only when you need parametric polymorphism

> If a function has parameters with those types, and the function code doesn’t make any particular assumptions about the element types, then it may be useful to use a type parameter.

In some languages when a type variable is universally quantified the language simply doesn't allow you to make assumptions about these types. This is of course the right thing to do.

> Another case where type parameters can be useful is when different types need to implement some common method, and the implementations for the different types all look the same.

Looking the same is a clear indication that your code isn't making assumptions about the type. So this is just a restatement of the above point.

Overall this is very good advice. At the risk of sounding elitist, I think this is something everyone should already know, regardless of whether they use Go or not. Of course the article is aimed at beginners, so there's nothing wrong with that, but at HN we can just use the proper terminology such as parametric polymorphism or a universally quantified type variable.



I think it's also aimed at the C++ crowd where generics = static dispatch and interfaces = dynamic dispatch and people try to eek out the extra performance.


Keep in mind that C++ doesn't provide for parametric polymorphism, one of the more important uses of generics.

Parametric polymorphism gives you guarantees about your program for free. C++'s templates can't do that. In C++ even if the types look generic, your code can make assumptions about the types.

Eg your generic code from some type 'a' to some type 'a' can implement the identify function for most types, but implement cosine when given a float.

In something like Haskell (or Java, I think?) that sneakiness is not possible without clearly telling the user in the type.

See eg https://bartoszmilewski.com/2014/09/22/parametricity-money-f...


> C++'s templates can't do that. In C++ even if the types look generic, your code can make assumptions about the types.

If you want an assertion that a class pretends to implement certain semantics (a.k.a. implements an interface) then you can have a specialized struct that has to be declared for every class used with your template. Either the user provides it and guarantees that their identity() function implements the required semantics of identity() or he doesn't and the code fails to compile. You could also check explicitly if the template parameter type inherits from a certain interface type, but that tends to exclude a lot of types that do not generally implement any interface explicitly, cant remember an IIdentityProviderFactoryBuilderManagementJavaJXBeanObserverInterface on int for example.


Going via the interface route that you described doesn't rescue C++ here.

The thing that parametric polymorphism gives you is that the type system can ensure that your code is not doing anything with your values but pass them around. And you don't need to read nor trust any implementation code for that.

For what you describe you could still implement cosine for float, and 'not' for bools, and identity for char.

We don't want the class to implement certain semantics. We'd want an assurance that certain semantics are _not_ implemented (in some sense). We want an assurance that the code is forced to treat the value as an opaque black box.


Isn’t it being (have been) remedied by C++’s constraints?


C++ 20 Concepts are duck typed. Inertia means it isn't practical to ship something with the semantics of say, Rust's Traits.

Your own concepts could (but most will not) choose to abuse the duck typing to require a "marker" e.g. the natural way to define Gunfighter might be to say it has a draw() function, and we could imagine instead adding a marker named "is_a_Gunfighter" to every Gunfighter. However the built-in concepts intentionally do not do that.

Instead C++ documents a "model" for the standard concepts and explains that it is, as so often, the programmer's responsibility to ensure that everywhere their code need a concept they use things which correctly model the concept, the compiler will only do duck typing.

So, in Rust an f32 isn't Ord (the totally ordered Trait) because well, NaN != NaN. So if you ask Rust's default slice sort to sort a slice of f32s that won't compile - what if the slice has a NaN in it? You will need to either remember that sorting IEEE floating point numbers isn't generally a reasonable thing to do, or, explicitly write your deterministic compare function (maybe as a lambda) and call sort_by() with your comparison. Now it's obvious whose fault it is when your function blows up for NaN.

In C++ the float type is in fact std::totally_ordered and it's your fault if you ever end up with a NaN somewhere and it matters.




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

Search: