Brendan wrote:You're right - I didn't understand it because I didn't want to. I probably could've wasted several days learning Ada, or wasted several hours only learning the pieces of Ada you've used, and after wasting more time than it's worth I could've understood your code, but I chose not to.
Well, I see two options. Either you could understand the code if you wanted without spending hours of research on Ada, and you're just playing the fool to escape a discussion specifically about generics. Then you're not really interested in the discussion and further attempts to have it are futile.
Or you really aren't capable of reading this code. I would expect of anyone who takes part in a discussion about programming languages in general that he has seen a few languages (probably including at least one Pascal-style language) and can recognise patterns like argument lists. If you're not capable of this, you're not really qualified for the topic, so further attempts to have a meaningful discussion are futile under this assumption as well.
But because I can't resist feeding trolls, here's a heavily commented version (that you probably still won't want to understand).
Code: Select all
-- The following block describes the types and functions that must be provided to specialise the procedure
generic
-- T can be any type
type T is private;
-- TArray must be an array with elements of the same type
-- Its indices can be any range of numbers within the type Natural
type TArray is Array(Natural range <>) of T;
-- An operator > must exist that takes two operands a and b of type T
-- and that returns a boolean.
with function ">" (a: T; b: T) return boolean is <>;
-- This defines the generic procedure, named "sort" that takes a TArray argument
-- that it can read from and write to (i.e. passed by reference), and that doesn't have
-- a return value (which is the difference from a function).
procedure sort(x: in out TArray);
Code: Select all
-- StructSort is the specialisation of the generic procedure "sort", with the following
-- specialisations that were required by the declaration of the generic procedure:
-- * The generic type T is specialised as the concrete type TestStruct
-- * The generic type TArray is specialised as the concrede type StructArray
-- * The operation ">" is implemented by the StructCompare function
-- All of these match the type requirements made in the generic block of "sort"
procedure StructSort is new sort(T => TestStruct, TArray => StructArray,
">" => StructCompare);
For the purpose of finding/fixing bugs; you should be able to tell the compiler to "expand generics only", so that you can examine the resulting functions it generated (in the same way that most compilers can be asked to pre-process only, so you can examine the code after pre-processing).
Most compilers don't have an "expand function calls only" mode either. Who said that the compiler has to generate three instances of the sort procedure? Why can't it just statically check the types and then use the same code internally for all three instances, with a function pointer for the compare operation?
Generics are
not stupid code generation/macro expansion, they are an abstraction element of programming languages like functions. Abstractions generally make it harder to see what's going on under the hood, but they do reduce complexity on the source code level of the high level language.
Exactly - generics are like a preprocessor macro that create functions, except that (unlike preprocessor macros) it doesn't break type checking completely. Of course it still breaks type checking partially - e.g. accidentally passing the wrong type of arguments will cause a new function to be generated instead of a compile-time error.
Care to give an example? Assume we have another type BrendanStruct, how are you going to accidentally pass it to the wrong function or generate a new function, where a compile time error should have occured? Feel free to use non-Ada pseudo code as long as you keep the fundamental mode of operation intact.
For example, for your Ada sorting example, how many specialisations did you have to write? To avoid that you could've used a function pointer to a comparator (I hear that's a popular third argument) and then wrote 3 different comparators; or you could've overloaded the "less than" operator and used 3 different pieces of code for comparisons that way.
A specialisation is a declaration "I want to use function X with type T under the name Y". That's hardly comparable to writing an implementation.
I did use three different comparators, because obviously there isn't a single working comparator for integers, string and TestStructs. But I didn't have to tinker around with function pointers. I could indeed have used a function pointer as an argument to sort() instead of requiring the operator ">" in the generic block, and thanks to generics it would even be a type-safe function pointer, but why would I want to?
In order to get fully rid of generics, I wouldn't only have to remove the comparator from the generic section, but also the type T. And then I wouldn't have type safety any more.
Look, this is sorting without generics, C does it:
Code: Select all
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
It uses untyped pointers, passes the element size and a function pointer for the comparison explicitly on each invokation, whereas generics move it to the specialisation. (It also passes the array size because C has, I guess you would say less complex, arrays that don't know their size.) Do you really think that having the complexity on each caller, without any type checks, would make the code better?
By not using generics, you end up with more code, but all the code is simple and in one place.
No. You end up with a string sorting code all in one place. And you end up with integer sorting code in one place. And with struct sorting code in one place. But certainly not with all sorting code in one place.
In my book, have three implementations for sorting (especially once it gets a bit cleverer than bubble sort) add to more complexity than having one implementation for sorting and three trivial specialisation declarations for different data types.
I would've assumed you'd need to create a whole new "sort_threaded" generic thing (with a bunch of new specialisations) so the compiler could figure out which sort you want in each case.
That's what I meant. I would implement sort_threaded() and then specialise that one for StrSort() instead of the old sort().
Think of it in terms of "levels of complexity". Something like "0*5+1" is very simple for people to deal with. Something like "x*5+1" is a little harder for people because now they have to think about ranges of values rather than just one set of values (e.g. they need to think about whether some value/s of 'x' can cause an overflow). Converting that into a function makes it slightly more complex because now they have to care about all possible values of 'x' (rather than only caring about their own values of 'x'). Converting that into a generic/template increases complexity again - instead of caring about all possible values of 'x' they have to care about all possible values for all possible types of 'x'.
Indeed. So once you've got rid of generics, the next thing to get rid of are functions, right? And then counting loops, which could just be unrolled. Sequential code is so much easier to follow, right? Or let's just get rid of all jumps and loops, because Turing completeness is overrated anyway. Let's make all programs "0*5+1".
Of course you aren't looking at language complexity at all - you're only looking at "amount of typing". For some reason you think less code makes things simpler.
I think abstraction can simplify things. You seem to think that abstraction is always uselessly added complexity.
Now, if you assume generics makes things 3 times as complicated and also makes things use 3 times less code
Then your assumption is wrong. Generics avoid code duplication and having to manage code duplication is an important reason why a project gets complex.
Basically; amount of code isn't very important while language complexity is; so anything that reduces the amount of code and increases language complexity is bad idea (unless you're deliberately trying to make it hard for people to write code - e.g. to increase university enrolments or increase your own job security).
So you're arguing against functions once again...