This are merely instanceof switches. Generics mean that you write
fun(a,b): return a+b
and if the a + b is doable, it will be done. You don't need to specify the list of types that accepts this syntax. The difference between this and duck typing is that you can also specify interfaces (or traits in c++) that will say that this type is quackable so
fun(a <? implements Quackable>): a.quack()
is reusable. What is the difference between this and simple interface implementation? It took me some time to find this example in the narrowest version possible.
class <T has trait Number> Complex(a T, b T):
Complex<T> operator+(Complex<T> other): return new Complex(this.a + other.a, this.b+other.b)
Complex<T> operator-(Complex<T> other): return new Complex(this.a - other.a, this.b-other.b)
Complex<T> operator*(Complex<T> other):
return Complex(this.a * other.a - this.b * other.b, this.a * other.b + this.b * other.a)
The generic renders this code reusable, so if only you can create a new type, let's say vector, that supports +,-, and multiply you can have complex algebra on those vectors
At least in C++ there's also the matter that the type switching happens at compile time, not runtime. You get a new instance of the function for each type that it's called with.
That's why C++ libraries with generic interfaces need to have their full implementations in the header or else have explicit instantiations for known supported types. If you compile the library first and the project later, there's no way of knowing at library-compile-time which instantiations of the generic will later be needed, and like, the types might not even exist yet at that point.
One of my pet-hates is fellow developers who call an implementation 'generic', but when you peek inside, there's just if-statements that (at best) cover the already-known input types.
Usually I point to Generics as an example of what "generic" actually means: You peek inside List<T>, it doesn't know about your type, it does the right thing anyway.
> If I understand correctly, generics here are “type agnostic” functions in a strongly typed language?
They are not.
They are applicable to a well-defined range of sets of input types, instead of a single specific type combination, and produce a well-defined output type for each input type combination.
> It seems strange to put in so much effort for type checking then only to throw it overboard by implementing something that ignores type.
Generics do not ignore types. That's kind of the whole point.
I'm assuming you meant statically/dynamically type checked languages.
Generic functions to not ignore types. An `inThere::a list -> a -> bool` very much enforces that the list passed in, as well as the element have the same type. With a sufficiently powerful type system, this allows for statically checked code that's not much less flexible than dynamically checked code.
Observing current developments in Python, but also Rust gives me the impression that dynamically typed languages were more a reaction to the very weak type systems languages like C or Java provided back in the day. A lot of Python code has very concrete or rather simple generic types for example - Protocols, Unions, First-class functions and Type parameters handle a lot. The tools to express these types better existed in e.g. Caml or Haskell, but weren't mainstream yet.
I somewhat disagree. FTA: “If the callsite is some_function(2);, the compiler resolves T as i32 and selects the corresponding branch, returning the value incremented by one. […] The important point is that the decision is driven entirely by type information, not by runtime inspection”
Having said that, even ignoring that this requires all implementations to be in a single source file (yes, you probably could use m4 or #include or whatever it’s called in this language) I do not find this syntax elegant.
Also, one thing that it doesn’t seem to support is generating compiler errors when calling a function with a type that isn’t supported.
I saw your programming language on reddit and now here too. To me first looking at it, I think its built straight for parallelism, Didn't know you were on hackernews too, interesting stuff and I will try to keep an eye out hopefully for this language but what are some stuff where you think it can be really useful for?
You implemented type-checking. For a project this ambitious, I am surprised here.
“Generics” should mean that the compiler or interpreter will generate new code paths for a function or structure based on usage in the calling code.
If I call tragmorgify(int), tragmorgify(float), or tragmorgify(CustomNumberType), the expectation is that tragmorgify(T: IsANumber) tragmorgifies things that are number-like in the same way.
For a compiled language this usually means monomorphization, or generating a function for each occurring tuple of args types. For an interpreted language it usually means duck-typing.
This is not a bad language feature per se, but also not what engineers want from generics. I would never write code like your example. The pattern of explicit type-checking itself is a well-known codesmell.
There is not a good usecase for adding 2.0 to a float input but 1 to an integer input. That makes your function, which should advertise a contract about what it does, a liar ;)
p0w3n3d|2 months ago
mikepurvis|2 months ago
That's why C++ libraries with generic interfaces need to have their full implementations in the header or else have explicit instantiations for known supported types. If you compile the library first and the project later, there's no way of knowing at library-compile-time which instantiations of the generic will later be needed, and like, the types might not even exist yet at that point.
mrkeen|2 months ago
One of my pet-hates is fellow developers who call an implementation 'generic', but when you peek inside, there's just if-statements that (at best) cover the already-known input types.
Usually I point to Generics as an example of what "generic" actually means: You peek inside List<T>, it doesn't know about your type, it does the right thing anyway.
Philip-J-Fry|2 months ago
Your list_contains function should be able to just do a == comparison regardless of whether it's an int or a string.
This is effectively no different than adding a parameter to one of your non-"generic" functions and just swapping behaviour based on that?
Towaway69|2 months ago
Why not just use a weakly typed language and add type checking were needed?
It seems strange to put in so much effort for type checking then only to throw it overboard by implementing something that ignores type.
dragonwriter|2 months ago
They are not.
They are applicable to a well-defined range of sets of input types, instead of a single specific type combination, and produce a well-defined output type for each input type combination.
> It seems strange to put in so much effort for type checking then only to throw it overboard by implementing something that ignores type.
Generics do not ignore types. That's kind of the whole point.
tetha|2 months ago
Generic functions to not ignore types. An `inThere::a list -> a -> bool` very much enforces that the list passed in, as well as the element have the same type. With a sufficiently powerful type system, this allows for statically checked code that's not much less flexible than dynamically checked code.
Observing current developments in Python, but also Rust gives me the impression that dynamically typed languages were more a reaction to the very weak type systems languages like C or Java provided back in the day. A lot of Python code has very concrete or rather simple generic types for example - Protocols, Unions, First-class functions and Type parameters handle a lot. The tools to express these types better existed in e.g. Caml or Haskell, but weren't mainstream yet.
one-punch|2 months ago
This is different from ‘parametric polymorphism’, which is what people call generics.
Someone|2 months ago
Given that, this isn’t that different from C generics (https://en.cppreference.com/w/c/language/generic.html), and people call that generics, too.
Having said that, even ignoring that this requires all implementations to be in a single source file (yes, you probably could use m4 or #include or whatever it’s called in this language) I do not find this syntax elegant.
Also, one thing that it doesn’t seem to support is generating compiler errors when calling a function with a type that isn’t supported.
Imustaskforhelp|2 months ago
miellaby|2 months ago
poly2it|2 months ago
tliltocatl|2 months ago
b33j0r|2 months ago
“Generics” should mean that the compiler or interpreter will generate new code paths for a function or structure based on usage in the calling code.
If I call tragmorgify(int), tragmorgify(float), or tragmorgify(CustomNumberType), the expectation is that tragmorgify(T: IsANumber) tragmorgifies things that are number-like in the same way.
For a compiled language this usually means monomorphization, or generating a function for each occurring tuple of args types. For an interpreted language it usually means duck-typing.
This is not a bad language feature per se, but also not what engineers want from generics. I would never write code like your example. The pattern of explicit type-checking itself is a well-known codesmell.
There is not a good usecase for adding 2.0 to a float input but 1 to an integer input. That makes your function, which should advertise a contract about what it does, a liar ;)