I'm not a good programmer. :-[That's not true. :D
Half my code is testing values (all parameters, fields and globals used) and working around unexpected ones.Only if can get around it. For example in case of unexpected structure of external data (files, databases, responses from devices, etc) I raise an exception. Of course, this does not include cases where it can simply be ignored. Also when writing universal modules (used in more than one program).
Apart from digging in the source code, how do you find out all the exceptions a call might raise?Documentation.
In the general case you get libraries with/without documentation. Let's try with TFPHTTPClient.Get?Apart from digging in the source code, how do you find out all the exceptions a call might raise?Documentation.
What about operators?
Let's assume the following operator:
operator /(A,B:String):double; var dA,dB: double; iCode: integer; begin Val(A, dA, iCode); if iCode<>0 then Error; Val(B, dB, iCode); if iCode<>0 then Error; Result := dA/dB; end;
How do you handle the Error above?
What about division by zero?
I use try..except when calling a function/method that throws an exception when something goes wrong. It's one way to flag an error, I guess.Yes, a very poor way of flagging an error. One that is unfortunately all too common in OOP.
I always avoid try..except block, I personally think it is not good to show that 'panic' message to users. In the old DOS days I use IOResult, now I use all the think I can to check if the process may go wrong.What you're doing is the right way of doing it, ensure the entry conditions are as they should be to guarantee the function/procedure can successfully perform its task (whatever it may be.)
I am okay to use try..finally block to free the object/memoryif the program is structured correctly, you don't need any try/finally blocks either.
Maybe not the best, but that it is what I usually do.You're on the right track, from what you posted, you're quite close to doing it the best way.
I try to keep error messages out of lower level functions.That's a good practice. Prevents having error messages being set all over the program instead of in one centralized location where they can be managed.
@SymbolicFrankCPU exceptions included?I use try..except when calling a function/method that throws an exception when something goes wrong. It's one way to flag an error, I guess.Yes, a very poor way of flagging an error. One that is unfortunately all too common in OOP.
Can you elaborate a bit more here?I am okay to use try..finally block to free the object/memoryif the program is structured correctly, you don't need any try/finally blocks either.
@engkinThank you for this explanation. I see that you reduced the range of the type double using MaxFloat, and used two values outside that range to indicate NAIOFloat and NAFloat. Probably for accuracy reasons?
To expand a little, I define:
const NAIOFloat : double = -1.7e+308; // explicit missing value, i/o = 'NaN' NAFloat : double = -1.6e+308; // missing value, i/o = '' MaxFloat : double = 1.5e+308; // use +/- as min-max range { IsNA 'Is Not Available', returns true if number is not valid. } function IsNA(r: double): boolean; begin result := (r = NAFloat) or (r = NAIOFloat); end; { IsNum Uses NAFloat, a very large magnitude negative double whose magnitude is bigger than MaxFloat. Use NAFloat to initialize doubles, and -MaxFloat for smallest double. } function IsNum(r: double): boolean; begin result := (r > NAFloat); end;
So I might return NAFloat for your function to indicate it did not work, and check it with IsNum. I find that NAN is not usable cross-platform. Initialized data that is not input yet is set to NAFloat, data that is explicitly not a number is set to NAIOFloat. A spreadsheet of data values displays '' or 'NaN' for those values.
I always avoid try..except block, I personally think it is not good to show that 'panic' message to users. In the old DOS days I use IOResult, now I use all the think I can to check if the process may go wrong.
CPU exceptions included?In user mode - ring 3 - most CPU exceptions are caused by bugs in the program. An exception (pun intended ;)) are the debug exceptions and those are handled by the OS and routed to the debugger as events. In a bug-free program that only concerns itself with resources it owns, there should not be any exceptions.
Can you elaborate a bit more here?Sure. The most common use of try/finally is to ensure that resources that have been allocated are de-allocated when no longer necessary.
I like this example:This is a fantastic example.
program test; var s: string; begin s := '12345'; s := Copy(s, -1, MAXINT); WriteLn(s); end.
Of course the index and length of the Copy() are invalid, but you still get a result. While many different languages (and different Pascal implementations) would raise an exception, because the result would be incorrect. While strictly true, I like the graceful result.Unless ignored, an exception does not necessarily mean ungraceful result.
That raises another question: does your logic keeps working with partial results? That's why you check your inputs.I like the fact that this question was "raised". Checking your inputs is natural, with or without exceptions. The two are unrelated. You need to check your inputs no matter what.
About returning errors: In C, atoi("bla!") returns 0 (zero). That is bad, because you don't know if an error occurred. You could do: 'BOOL atoi("bla!", ReturnVar)', but that makes for messy code. 'int atoi("bla!", DefaultValue)' is better.atoi has implicit DefaultValue of zero.
And otherwise do raise that exception, and catch it.As you can see, sometimes it is a matter of style, but not always.
atoi has implicit DefaultValue of zero.
Did you say most?CPU exceptions included?In user mode - ring 3 - most CPU exceptions are caused by bugs in the program.
An exception (pun intended ;)) are the debug exceptions and those are handled by the OS and routed to the debugger as events.I see exceptions on top of exceptions.
In a bug-free program that only concerns itself with resources it owns, there should not be any exceptions.A bug-free program is a program that does nothing, whence it has no bugs. Joking aside, apart from small samples, programs are always dealing with resources they do not own, or depend on libraries that do use exceptions. Exceptions are just a tool. Like any tool, they need to be used right.
While this usage is the common one, it is not the only reason. You can use try/finally to signal the end of a process, or change the status, or anything else that you want to make sure is done at the end of a try finally block.Can you elaborate a bit more here?Sure. The most common use of try/finally is to ensure that resources that have been allocated are de-allocated when no longer necessary.
If the variables that hold the handles and/or pointers to those resources are initialized before they are used then a try/finally is unnecessary. For instanceWait, this example is supposed to show how you do not need try/finally?The above function creates a scope and releases whatever resources were obtained once the scope is exited. As long as the variables that hold resource handles/pointers are initialized, it will work fine.
function FileMap (Filename : pchar) : pbyte; // maps into memory the file to dump var FileHandle : THANDLE = 0; FileMapping : THANDLE = 0; p : pbyte = nil; SCOPE : integer; const FUNCTION_ID = 'B10: '; // constants used by CreateFile NO_TEMPLATE_FILE = 0; // constants used by CreateFileMapping NO_MAXIMUM_SIZE_HIGH = 0; // 0 indicates to use the size of the NO_MAXIMUM_SIZE_LOW = 0; // file // constants used by MapViewOfFileEx FILE_OFFSET_HIGH = 0; // file offset to map from FILE_OFFSET_LOW = 0; BEGINNING_TO_END = 0; begin result := nil; // default return value if IsBadStringPtrA(Filename, STRING_MAX_LENGTH) then exit; for SCOPE := 1 to 1 do // trick to create a scope one can break out of begin FileHandle := CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NO_TEMPLATE_FILE); if FileHandle = INVALID_HANDLE_VALUE then begin OutputToDeviceA(FUNCTION_ID, 'unable to open ', Filename); Newline(); break; end; // with the file handle, create a mapping for it FileMapping := CreateFileMappingA(FileHandle, nil, PAGE_READONLY, NO_MAXIMUM_SIZE_HIGH, // use file size NO_MAXIMUM_SIZE_LOW, nil); if (FileMapping = 0) then begin OutputToDeviceA(FUNCTION_ID, 'unable to map ', Filename); Newline(); break; end; p := MapViewOfFileEx(FileMapping, FILE_MAP_READ, FILE_OFFSET_HIGH, // from beginning FILE_OFFSET_LOW, BEGINNING_TO_END, // to end nil); // map anywhere if p = nil then begin OutputToDeviceA(FUNCTION_ID, 'unable to map view of file', Filename); Newline(); break; // the note below applies only when a non nil address is specified to // map at a specific address // NOTE: MapViewOfFileEx fails if it cannot map at the specified address. // in other words, it will NOT attempt to map to a different address // to succeed. Because of this there is no need to check if the // address returned is equal to the address specified. // The documentation states: // Reserved and committed pages are released when the view is unmapped and // the file mapping object is closed. For details, see the UnmapViewOfFile // and CloseHandle functions. // The above means that the file mapping is still valid even after we // close the handles for the file and the file mapping. end; end; if (FileHandle <> INVALID_HANDLE_VALUE) then CloseHandle(FileHandle); if (FileMapping <> 0) then CloseHandle(FileMapping); result := p; end;
That sequence of code is quite common and, I've seen it quite a few times (Jeffrey Richter's examples for instance) where, if something goes wrong, there are if statements that release whatever resources have been obtained so far but, that's a horrible way of doing it because as more resources are obtained, the number of resources released in every if statement increases. That is bug prone, forget to release one and there is a leak. Also, re-using the code is not straightforward because any needed modification may require all those if statements that release resources to be modified.
Using try/finally is very similar to using if statements. The try/finally(ies) are nested to account for every resource obtained.You can replace one with the other, but they are not the same.
The try/finally(ies) are nested to account for every resource obtained.Not necessarily.
if(s) and try/finally are very poor solutions to releasing resources. A little bit of structuring (with a scope) makes the function fully linear with a minimum amount of nesting (usually, 1 nesting level : the scope)Can you show how you do that with the function you showed above?
It is not there to help you spot errors, nor does is prevent you from spotting errors. It does what it is supposed to do: translate any string to number. Zero is the default. If you want to "spot" errors, use a different function.atoi has implicit DefaultValue of zero.
Yes, but that is what prevents you from spotting errors in most cases. MININT would be a much better default. It depends on the strings you try to translate.
It is not there to help you spot errors, nor does is prevent you from spotting errors. It does what it is supposed to do: translate any string to number. Zero is the default. If you want to "spot" errors, use a different function.atoi has implicit DefaultValue of zero.
Yes, but that is what prevents you from spotting errors in most cases. MININT would be a much better default. It depends on the strings you try to translate.
Using the wrong tool and complaining it is not doing what you want is not right.
Did you say most?yes, I did. Among the CPU exceptions are "invalid opcode" exceptions which can happen if there is a bug in the compiler one is using, not in the program. Also, these days, programmers are raising exceptions because they buried themselves in a pile of function/procedure calls and the only way they have left to inform the original caller something went wrong is by raising an exception (extremely poor programming!.)
I see exceptions on top of exceptions.I see lots of those in OOP programs.
apart from small samples, programs are always dealing with resources they do not own,By resources they don't own, I mean resources whose existence they cannot predict. For instance, if a program is attempting to read memory that is in another process (or the same process but, is not managed by the program), the program cannot make any assumptions as to the presence of memory at a particular address. That said, that example with memory is not very good since using Read/WriteProcessMemory eliminates the need for an exception handler. The basic rule is, when the proper operation of a set of instructions cannot be predicted then, exceptions are warranted, otherwise the programmer should write clean code, which means do all the necessary checking before carrying out the function.
or depend on libraries that do use exceptions.That's defines the typical case: a poorly written library spreads its poor design to the program that wants to use it. Apparently, it's too much work these days to return an indication of failure or success as a function result. Using exceptions makes a programmer look "kewl" and knowledgeable. Function results ?... way too prosaic these days.
Exceptions are just a tool. Like any tool, they need to be used right.I agree with that. These days they are grossly abused and misused.
While this usage is the common one, it is not the only reason. You can use try/finally to signal the end of a process, or change the status, or anything else that you want to make sure is done at the end of a try finally block.You're right, it's not the only reason. That was just an example and I picked something quite common for it. I can write much simpler, easier to understand and follow code without using try/finally.
Wait, this example is supposed to show how you do not need try/finally?That's one example. I'd much rather code it like that than use a try/finally construct. It is a lot cleaner. No nested try/finally and when debugging, the execution doesn't jump around into some finally(ies) if something didn't go as expected.
You used a for loop to create a scope so you can use break?Yes, unfortunately. There are no scopes in Pascal. That is not as clean as I'd like it to be but, it's still way better than try/finally(ies.)
You want to use break but avoid try/finally?That's only one of the reasons. Another reason is that, that way, all the resource deallocation is in _one_ place, not scattered all over. In more complex functions, that makes a very noticeable difference, particularly when debugging (the debugger isn't jumping around from one finally to another.)
You avoided using objects in your function. You do realize that you can achieve the same goal using objects, specially when this function get complicated, right?I strongly believe that ANYTHING that can be written using objects can be written in a clearer, simpler, easier to maintain and more efficient way without objects. If there is an exception to that, I am yet to see it.
That's true but, as a programming method, they have some flaws in common.Using try/finally is very similar to using if statements. The try/finally(ies) are nested to account for every resource obtained.You can replace one with the other, but they are not the same.
True but, quite often, they are.The try/finally(ies) are nested to account for every resource obtained.Not necessarily.
The function already uses a scope (unfortunately, with trickery since there is no support for it in the language.) The reason I keep nesting at a minimum is because the lower the level of nesting the easier a construction is to understand.if(s) and try/finally are very poor solutions to releasing resources. A little bit of structuring (with a scope) makes the function fully linear with a minimum amount of nesting (usually, 1 nesting level : the scope)Can you show how you do that with the function you showed above?
Why do you want to keep nesting at minimum?
Among the CPU exceptions are "invalid opcode" exceptions which can happen if there is a bug in the compiler one is using, not in the program.Or using the program on a different CPU that lacks the "invalid opcode"?
Also, these days, programmers are raising exceptions because they buried themselves in a pile of function/procedure calls and the only way they have left to inform the original caller something went wrong is by raising an exception (extremely poor programming!.)Keeping all the code in one function and returning false or any value to indicate the exact error is not much different. Taking either extreme is simply poor programming practice, and does not help readability nor prevents bugs.
You should, it is just a style of programming.I see exceptions on top of exceptions.I see lots of those in OOP programs.
It is hard to imagine an applications that does not deal with the internet, json, networks, databases, acquired data from wired/wireless sources like images or ... etc.apart from small samples, programs are always dealing with resources they do not own,By resources they don't own, I mean resources whose existence they cannot predict. For instance, if a program is attempting to read memory that is in another process (or the same process but, is not managed by the program), the program cannot make any assumptions as to the presence of memory at a particular address. That said, that example with memory is not very good since using Read/WriteProcessMemory eliminates the need for an exception handler. The basic rule is, when the proper operation of a set of instructions cannot be predicted then, exceptions are warranted,
otherwise the programmer should write clean code, which means do all the necessary checking before carrying out the function.This applies when using OOP or not.
I hear you, you prefer to use a library and with each call to check the result: Initialize the library, check. A call, a check. Another call, another check, repeat... , finalize the library, then check.or depend on libraries that do use exceptions.That's defines the typical case: a poorly written library spreads its poor design to the program that wants to use it. Apparently, it's too much work these days to return an indication of failure or success as a function result. Using exceptions makes a programmer look "kewl" and knowledgeable. Function results ?... way too prosaic these days.
Anything could be abused of misused, does not mean we need to avoid it, or consider it poor practice.Exceptions are just a tool. Like any tool, they need to be used right.I agree with that. These days they are grossly abused and misused.
Yes, until you are forced.While this usage is the common one, it is not the only reason. You can use try/finally to signal the end of a process, or change the status, or anything else that you want to make sure is done at the end of a try finally block.You're right, it's not the only reason. That was just an example and I picked something quite common for it. I can write much simpler, easier to understand and follow code without using try/finally.
The debugger, when enough debugging data is available, is supposed to show you exactly where the exception had happened before it takes you anywhere else. But if you don't like try/finally for that reason, then you do not like else either because it jump somewhere far from the line the debugger was on.Wait, this example is supposed to show how you do not need try/finally?That's one example. I'd much rather code it like that than use a try/finally construct. It is a lot cleaner. No nested try/finally and when debugging, the execution doesn't jump around into some finally(ies) if something didn't go as expected.
It is not clean, and not supported by Pascal, but you used it. While Try/Finally is part of the language, the object oriented dialect, and you refuse to use it?You used a for loop to create a scope so you can use break?Yes, unfortunately. There are no scopes in Pascal. That is not as clean as I'd like it to be but, it's still way better than try/finally(ies.)
"Scattered" or not, if deallocation happens in a logical and correct way, and always guaranteed there is no problem. The desire to have all the deallocations in one place came with extra price, at least, the chain of IF THEN. Again, a style of programming.You want to use break but avoid try/finally?That's only one of the reasons. Another reason is that, that way, all the resource deallocation is in _one_ place, not scattered all over. In more complex functions, that makes a very noticeable difference, particularly when debugging (the debugger isn't jumping around from one finally to another.)
Yes, a call to member function of an object like AStringList.Add is actually a call to a function with the first parameter is the object itself: Add(AStringList,... see?You avoided using objects in your function. You do realize that you can achieve the same goal using objects, specially when this function get complicated, right?I strongly believe that ANYTHING that can be written using objects can be written in a clearer, simpler, easier to maintain and more efficient way without objects. If there is an exception to that, I am yet to see it.
That said, if you'd like to rewrite that function using objects and point out the reasons why yours is better than the one I posted, I would be very pleased to see it.I might give it a shot, not sure.
When the level of nesting grows, it is time to consider another function/member function.The function already uses a scope (unfortunately, with trickery since there is no support for it in the language.) The reason I keep nesting at a minimum is because the lower the level of nesting the easier a construction is to understand.if(s) and try/finally are very poor solutions to releasing resources. A little bit of structuring (with a scope) makes the function fully linear with a minimum amount of nesting (usually, 1 nesting level : the scope)Can you show how you do that with the function you showed above?
Why do you want to keep nesting at minimum?
For instance
function FileMap (Filename : pchar) : pbyte; // maps into memory the file to dump var FileHandle : THANDLE = 0; FileMapping : THANDLE = 0; p : pbyte = nil; SCOPE : integer; const FUNCTION_ID = 'B10: '; // constants used by CreateFile NO_TEMPLATE_FILE = 0; // constants used by CreateFileMapping NO_MAXIMUM_SIZE_HIGH = 0; // 0 indicates to use the size of the NO_MAXIMUM_SIZE_LOW = 0; // file // constants used by MapViewOfFileEx FILE_OFFSET_HIGH = 0; // file offset to map from FILE_OFFSET_LOW = 0; BEGINNING_TO_END = 0; begin result := nil; // default return value if IsBadStringPtrA(Filename, STRING_MAX_LENGTH) then exit; for SCOPE := 1 to 1 do // trick to create a scope one can break out of begin FileHandle := CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NO_TEMPLATE_FILE); if FileHandle = INVALID_HANDLE_VALUE then begin OutputToDeviceA(FUNCTION_ID, 'unable to open ', Filename); Newline(); break; end; // with the file handle, create a mapping for it FileMapping := CreateFileMappingA(FileHandle, nil, PAGE_READONLY, NO_MAXIMUM_SIZE_HIGH, // use file size NO_MAXIMUM_SIZE_LOW, nil); if (FileMapping = 0) then begin OutputToDeviceA(FUNCTION_ID, 'unable to map ', Filename); Newline(); break; end; p := MapViewOfFileEx(FileMapping, FILE_MAP_READ, FILE_OFFSET_HIGH, // from beginning FILE_OFFSET_LOW, BEGINNING_TO_END, // to end nil); // map anywhere if p = nil then begin OutputToDeviceA(FUNCTION_ID, 'unable to map view of file', Filename); Newline(); break; // the note below applies only when a non nil address is specified to // map at a specific address // NOTE: MapViewOfFileEx fails if it cannot map at the specified address. // in other words, it will NOT attempt to map to a different address // to succeed. Because of this there is no need to check if the // address returned is equal to the address specified. // The documentation states: // Reserved and committed pages are released when the view is unmapped and // the file mapping object is closed. For details, see the UnmapViewOfFile // and CloseHandle functions. // The above means that the file mapping is still valid even after we // close the handles for the file and the file mapping. end; end; if (FileHandle <> INVALID_HANDLE_VALUE) then CloseHandle(FileHandle); if (FileMapping <> 0) then CloseHandle(FileMapping); result := p; end;
You used a for loop to create a scope so you can use break?Yes, unfortunately. There are no scopes in Pascal. That is not as clean as I'd like it to be but, it's still way better than try/finally(ies.)
Assume you writing cotainer library. Library has function minmax(x) which returns largest value not greater than x. User makes call minmax(10) and container has only one element 12. What returns minmax?
Or using the program on a different CPU that lacks the "invalid opcode"?I'm not sure what you're getting at with that. If a program is functioning correctly and, it is not dealing with resources managed by code that is not its own, there should not be any exceptions.
It is very different and, I believe you know that. An exception is a goto across stack frames. The worst kind of goto there is and, to make exceptions even worse, the final destination can very often only be determined at runtime. When a function returns a value to indicate something went wrong, it unwinds the stack in an orderly fashion (no cross stack gotos there), the final destination can always be determined by simply reading a listing of the code.Also, these days, programmers are raising exceptions because they buried themselves in a pile of function/procedure calls and the only way they have left to inform the original caller something went wrong is by raising an exception (extremely poor programming!.)Keeping all the code in one function and returning false or any value to indicate the exact error is not much different.
Taking either extreme is simply poor programming practice, and does not help readability nor prevents bugs.I don't consider a function returning a value that indicates something went wrong as an extreme, I consider it clean programming, unlike cross stack/scope gotos currently baptized as "exceptions" to make their use sound "kewl".
Using enough functions that return error codes, or objects that raise exceptions carrying error codes and messages, both are simply two styles of programming. You can abuse either one.Yes, spaghetti code is also a programming style, that doesn't make it good. I don't recall seeing a program where I would say the programmer abused functions returning error codes to the caller. I have seen plenty of OOP code that misuse and abuse the use of exceptions.
You should, it is just a style of programming.just like spaghetti code is a "programming style" too.
It is hard to imagine an applications that does not deal with the internet, json, networks, databases, acquired data from wired/wireless sources like images or ... etc.That's true but, I've dealt with the majority of the things in your list and I have not needed exceptions.
Yes but, OOP programmers seldom do it that way. What's seen more often than not is, code that doesn't check for anything and simply wraps statements in exception handlers. When something goes wrong, the exception handler gets to have a look at what happened, if it is clueless as to how to fix the problem, it simply re-raises the exception passing the buck to who knows what code.otherwise the programmer should write clean code, which means do all the necessary checking before carrying out the function.This applies when using OOP or not.
Yes, I definitely prefer that because when something goes wrong, the calling function/procedure is the one that gets to know about the problem (that may not be the case when using exceptions) and the caller can figure out what the proper course of action to take is, not some exception handler who knows where which may not even have been programmed with the proper way of handling a problem from the code that raised the exception.I hear you, you prefer to use a library and with each call to check the result: Initialize the library, check. A call, a check. Another call, another check, repeat... , finalize the library, then check.or depend on libraries that do use exceptions.That's defines the typical case: a poorly written library spreads its poor design to the program that wants to use it. Apparently, it's too much work these days to return an indication of failure or success as a function result. Using exceptions makes a programmer look "kewl" and knowledgeable. Function results ?... way too prosaic these days.
You don't like to include all of that in one block and leave the checking to the library itself? It would keep your code focused on its own part. But again, this sounds like your own style of programming, I have nothing against it. I can imagine how if thisVar=someErrorValue then grows in your code.That's not quite it. It's usually, "if functionreturn = false then <either handle the problem or exit if it cannot or should not>"
I want to let you know that JAVA does have exceptions and you have to catch them, and, unfortunately, FPC can produce JAVA classes, run on JVMs, and interact with other JAVA classes.Fortunately, Java isn't and is rather unlikely to ever be one of my concerns.
In generic terms, as you've stated them above, I fully agree with that. That said, my point is that the entire exception mechanism is very poorly implemented, grossly misused and equally abused.Anything could be abused of misused, does not mean we need to avoid it, or consider it poor practice.Exceptions are just a tool. Like any tool, they need to be used right.I agree with that. These days they are grossly abused and misused.
Yes, until you are forced.I can see being forced to have exception handlers (as you pointed out if the program is using a library that raises exceptions, that leaves no choice) but, I don't see how a programmer could be forced to use try/finally.
The debugger, when enough debugging data is available, is supposed to show you exactly where the exception had happened before it takes you anywhere else. But if you don't like try/finally for that reason, then you do not like else either because it jump somewhere far from the line the debugger was on.True that the debugger will usually be able to show exactly where the exception took place. No argument there. That said, when resources need to be released, the code execution is going to jump around to each and every one of the try/finally(ies) not so in the case of an if statement, it will execute whatever is in the if, that's it, no jumping around.
It is not clean, and not supported by Pascal, but you used it. While Try/Finally is part of the language, the object oriented dialect, and you refuse to use it?I openly admit that I'm not fond of having to "trick" the compiler in order to get a scope. As far as supported by Pascal, I do believe that "for i := 1 to 1 do " is supported. The one problem in that construct (which I wish I didn't have to use) is that if the optimizer ever gets really smart, it will remove the loop and then complain that there is a loose "break" statement. That is a potential problem.
"Scattered" or not, if deallocation happens in a logical and correct way, and always guaranteed there is no problem. The desire to have all the deallocations in one place came with extra price, at least, the chain of IF THEN. Again, a style of programming.Scattered is a problem because the programmer has the burden of mentally assembling all the little "pieces". Definitely less than desirable.
Yes, a call to member function of an object like AStringList.Add is actually a call to a function with the first parameter is the object itself: Add(AStringList,... see?No, that example doesn't reflect the problems with OOP. It isn't simply a matter of syntax. Here is a question for you, why do you think it is customary/standard to prefix object fields with some letter, usually the letter F ?. It isn't to make them look pretty.
It is like adjectives are before/after the nouns in different languages:
English (Brown Horse)
Spanish (Horse Brown)
Which one do you prefer?
I might give it a shot, not sure.I offered only so we could compare specifics instead of staying in the realm of "theory".
When the level of nesting grows, it is time to consider another function/member function.That's not a good solution either because it breaks the logical flow into a bunch of little pieces and the programmer has to go find them to read them, then mentally assemble all that stuff to see what the complete logic is. A programmer should be designing algorithms and data structures not assembling binary jigsaw puzzles.
Now I believe that you have your own style of programming and refuse to change it, not because it protects you from bugs, but simply because you do not want to try something new. Nothing unusual here, but considering other ways as poor or more prone to bugs is not accurate.That is not the case. My programming style changed a lot over the years (Per Brinch Hansen is responsible for the greatest change). Whenever I see a better way of doing something (which I define as something that simplifies the code or helps prevent potential bugs) I take it on the spot. OOP is definitely not one of those things.
I have counter argument for every argument you made, but I think it is time to see a more practical example. Here is your function in one way:I might give it a shot, not sure.I offered only so we could compare specifics instead of staying in the realm of "theory".
I have counter argument for every argument you made, but I think it is time to see a more practical example. Here is your function in one way:I understand and, I very much appreciate discussing specifics instead of theory.
What do you think?I added a few comments in the code but, in addition to those comments...
TFileMapper = class(TFileStream) // piece #2 - I suppose this is where the file is opened.No, this is to give our class all the abilities of TFileStream, and to build on top of it.
constructor Create(AFileName: String); // piece #3 - didn't need that before. Again, it probably allocates memoryThis part is not necessary in our example, and it does not allocate any memory that needs to be freed.
// which doesn't seem to be freed anywhere.
// not comparing, just an observation. The routine I posted didn't give the option to map at an offset.Because I can focus on mapping itself and different possibilities. Like you can pass the offset if you want, and if you do not it takes 0 by default.
// your implementation is going beyond the call of duty... which is perfectly fine.
function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} // piece #4
// I presume the program/caller will have to create an instance of TFileMapper. I am under the impression that wouldIt does not need to be global variable, it is up to you. In fact you can create , use, and free this class without a variable at all!
// be a global variable (that would not be desirable and it was not needed in the non-OOP function)
// vvvv this wasn't necessary before. There was no need to call an ancestor to do a CreateFile.As you can see, it calls the parent class with fmOpenRead or fmShareDenyWrite as a second parameter so you do not have to pass them when you use this class, as it is not relevant for the user of this class to bother with this specific detail.
constructor TFileMapper.Create(AFileName: String);
begin
Inherited Create(AFileName, fmOpenRead or fmShareDenyWrite);
end;
Your comments are welcome.Thank you. Here they are :)
In OOP if you create an object, you are responsible of freeing it. Similar to dealing with pair of functions Open/Close files. Or in our example: CreateFileMappingA then later CloseHandle.The non-OOP function didn't need to allocate any memory (creating an object, allocates memory.) Your usage of objects has imposed unnecessary overhead (create/use/track/free) on the program.
The code I showed above does not leak memory, you can try it.It won't leak memory as long as the object is freed but, the freeing of the object is conspicuously missing as is its creation. The non-OOP version didn't need to, directly or indirectly, allocate memory and didn't need code someplace else to free an unnecessary memory block (the object/class itself.)
It is dealing specifically with mapping the file, not with opening the file. Opening the file is left to the parent class: TFileStream.And that is one of the problems. In the non-OOP version, everything is right there in the function, makes it really easy to understand. The programmer doesn't have to know/guess which of the ancestors opened the file nor wonder where the file handle is coming from.
I don't need to repeat that code,It's not about repetition. The call to CreateFile occurred in only one place in the non-OOP version. The same place where the file mapping and the view are created. All nice and neat in one function. No need to go looking for stuff someplace else and wonder why the File Mapping is closed/freed but the File Handle isn't. Unlike the OOP version, symmetrical and parallel as it should be.
and as an extra bonus, that part related to opening the file is going to work on any major platform FPC runs on: Windows, Linux, and Mac. If I were to port this class to, say, Linux, I just need to worry about MapFile function.That's not a result of OOP programming, it's a result of the RTL. You're giving OOP credit it did not earn.
Is your code multi-platform?No and, it doesn't have to be. The program (not just the function) only applies to Windows therefore, portability in this case is not applicable and, even if it were, it is no thanks to OOP but, to the RTL functions regardless of the paradigm used to implement them.
The file doesn't get opened magically. Based on the classes and methods you've presented, it gives the impression that the inherited create method (from TFileStream) opens the file and, unnecessarily keeps the file handle opened.QuoteTFileMapper = class(TFileStream) // piece #2 - I suppose this is where the file is opened.No, this is to give our class all the abilities of TFileStream, and to build on top of it.
It's a fact that, somewhere memory is being allocated to hold the object and, that memory block has to be eventually freed. That's another thing your program needs to keep track of that the non-OOP version doesn't have to do.Quoteconstructor Create(AFileName: String); // piece #3 - didn't need that before. Again, it probably allocates memoryThis part is not necessary in our example, and it does not allocate any memory that needs to be freed.
// which doesn't seem to be freed anywhere.
Nothing there that cannot be done just as simply without OOP.Quote// not comparing, just an observation. The routine I posted didn't give the option to map at an offset.Because I can focus on mapping itself and different possibilities. Like you can pass the offset if you want, and if you do not it takes 0 by default.
// your implementation is going beyond the call of duty... which is perfectly fine.
function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} // piece #4
I'll give you that it doesn't have to be a global variable but, I find your claim that the class can be created, used and freed without using a variable, "dubious". You got to have a way to refer to the class and, that's usually a variable (specifically, a pointer to the class - though the fact that it is a pointer is hidden by the compiler.)Quote// I presume the program/caller will have to create an instance of TFileMapper. I am under the impression that wouldIt does not need to be global variable, it is up to you. In fact you can create , use, and free this class without a variable at all!
// be a global variable (that would not be desirable and it was not needed in the non-OOP function)
The non-OOP version didn't even need to pass those flags and, it didn't need to call a "parent" to execute one API call. That's what OOP does, break things into tiny pieces, then the programmer has to reassemble all those pieces to figure out what the purpose of all of them put together is.Quote// vvvv this wasn't necessary before. There was no need to call an ancestor to do a CreateFile.As you can see, it calls the parent class with fmOpenRead or fmShareDenyWrite as a second parameter so you do not have to pass them when you use this class, as it is not relevant for the user of this class to bother with this specific detail.
constructor TFileMapper.Create(AFileName: String);
begin
Inherited Create(AFileName, fmOpenRead or fmShareDenyWrite);
end;
To show how easy it is to maintain it, here is a reduced one based on your feedback:
unit uFileMapper_2; {$mode objfpc}{$H+} interface uses Classes, SysUtils; Type { TFileMapper } TFileMapper = class(TFileStream) // It does not allocate memory here. public function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} end; implementation uses Windows; { TFileMapper } function TFileMapper.MapFile(AOffset: QWord): pointer; const // constants used by CreateFileMapping NO_MAXIMUM_SIZE_HIGH = 0; // 0 indicates to use the size of the NO_MAXIMUM_SIZE_LOW = 0; // file // constants used by MapViewOfFileEx BEGINNING_TO_END = 0; var FileMapping: HANDLE; begin Result := nil; // with the file handle, create a mapping for it FileMapping := CreateFileMappingA(Handle, nil, PAGE_READONLY, NO_MAXIMUM_SIZE_HIGH, // use file size NO_MAXIMUM_SIZE_LOW, nil); if FileMapping = 0 then raise Exception.createfmt('File: %s, Line: %s, Unable to map file: %s', [{$I %File%},{$I %Line%},FileName]); Result := MapViewOfFileEx(FileMapping, FILE_MAP_READ, HI(AOffset), // from beginning LO(AOffset), BEGINNING_TO_END, // to end nil); // map anywhere CloseHandle(FileMapping); if Result = nil then raise Exception.createfmt('File: %s, Line: %s, Unable to map view of file: %s', [{$I %File%},{$I %Line%},Filename]); end; end.
That is true, but if it were for just one small function there would not be any need for OOP nor higher level languages. Assembly would be more than enough, that is one. Two, the concern about memory is not relevant any more with the gigantic amount of memory, unless you are working on some small limited embedded system or micro controller.In OOP if you create an object, you are responsible of freeing it. Similar to dealing with pair of functions Open/Close files. Or in our example: CreateFileMappingA then later CloseHandle.The non-OOP function didn't need to allocate any memory (creating an object, allocates memory.) Your usage of objects has imposed unnecessary overhead (create/use/track/free) on the program.
If the class is supposed to get a pointer to a mapped file, and nothing else. It is possible to change MapFile into a class function which does not need an object. It is usage would be more like:The code I showed above does not leak memory, you can try it.It won't leak memory as long as the object is freed but, the freeing of the object is conspicuously missing as is its creation. The non-OOP version didn't need to, directly or indirectly, allocate memory and didn't need code someplace else to free an unnecessary memory block (the object/class itself.)
Seeing everything that is not related to the function of the class (mapping files at this point ) is the problem. The programmer does not guess or wonder, the class and its ancestors are accessible. If you use Lazarus, click on Handle while holding CTRL key and it takes you to where it came from. You do not need to know where exactly the file is getting opened, you just need to know its handle. That is all you need to know to be able to map it. Otherwise, you still don't know how it is getting opened by the system when you call an API, in fact you do not have access to the source code, at best you have access to its assembly, which is, of course, not needed.It is dealing specifically with mapping the file, not with opening the file. Opening the file is left to the parent class: TFileStream.And that is one of the problems. In the non-OOP version, everything is right there in the function, makes it really easy to understand. The programmer doesn't have to know/guess which of the ancestors opened the file nor wonder where the file handle is coming from.
Reusing code is part of, you just need one class that opens files and allows you basic functions related to the open file. You do not need to call CreateFile every time you need to deal with some file.I don't need to repeat that code,It's not about repetition. The call to CreateFile occurred in only one place in the non-OOP version. The same place where the file mapping and the view are created. All nice and neat in one function. No need to go looking for stuff someplace else and wonder why the File Mapping is closed/freed but the File Handle isn't. Unlike the OOP version, symmetrical and parallel as it should be.
I used the RTL class, and I mentioned what I know about the RTL (which applies to the class), not trying to credit it to OOP, but mentioned it as a fact.and as an extra bonus, that part related to opening the file is going to work on any major platform FPC runs on: Windows, Linux, and Mac. If I were to port this class to, say, Linux, I just need to worry about MapFile function.That's not a result of OOP programming, it's a result of the RTL. You're giving OOP credit it did not earn.
I used the RTL, your function did not use the RTL. I credit my class the benefit of using the RTL, see? Re-usability.Is your code multi-platform?No and, it doesn't have to be. The program (not just the function) only applies to Windows therefore, portability in this case is not applicable and, even if it were, it is no thanks to OOP but, to the RTL functions regardless of the paradigm used to implement them.
I just answered this one when I mentioned changing the function into class function, of course with whatever modification needed.The file doesn't get opened magically. Based on the classes and methods you've presented, it gives the impression that the inherited create method (from TFileStream) opens the file and, unnecessarily keeps the file handle opened.QuoteTFileMapper = class(TFileStream) // piece #2 - I suppose this is where the file is opened.No, this is to give our class all the abilities of TFileStream, and to build on top of it.
Again, no need for OOP to run one function. The result of your function is going to be used somehow. Also, the amount of memory is not relevant, leaks are.It's a fact that, somewhere memory is being allocated to hold the object and, that memory block has to be eventually freed. That's another thing your program needs to keep track of that the non-OOP version doesn't have to do.Quoteconstructor Create(AFileName: String); // piece #3 - didn't need that before. Again, it probably allocates memoryThis part is not necessary in our example, and it does not allocate any memory that needs to be freed.
// which doesn't seem to be freed anywhere.
In this case of this example, this is true. But with objects you can keep related functions together. Maybe there is no reason to show the pointer returned by MapFile, and all the functionality could be contained in the class.Nothing there that cannot be done just as simply without OOP.Quote// not comparing, just an observation. The routine I posted didn't give the option to map at an offset.Because I can focus on mapping itself and different possibilities. Like you can pass the offset if you want, and if you do not it takes 0 by default.
// your implementation is going beyond the call of duty... which is perfectly fine.
function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} // piece #4
It is not unusual to use WITH:I'll give you that it doesn't have to be a global variable but, I find your claim that the class can be created, used and freed without using a variable, "dubious". You got to have a way to refer to the class and, that's usually a variable (specifically, a pointer to the class - though the fact that it is a pointer is hidden by the compiler.)Quote// I presume the program/caller will have to create an instance of TFileMapper. I am under the impression that wouldIt does not need to be global variable, it is up to you. In fact you can create , use, and free this class without a variable at all!
// be a global variable (that would not be desirable and it was not needed in the non-OOP function)
If the only duty of your program is to map a file and get a pointer, then do not use OOP. I am sure it does more than mapping a file.The non-OOP version didn't even need to pass those flags and, it didn't need to call a "parent" to execute one API call. That's what OOP does, break things into tiny pieces, then the programmer has to reassemble all those pieces to figure out what the purpose of all of them put together is.Quote// vvvv this wasn't necessary before. There was no need to call an ancestor to do a CreateFile.As you can see, it calls the parent class with fmOpenRead or fmShareDenyWrite as a second parameter so you do not have to pass them when you use this class, as it is not relevant for the user of this class to bother with this specific detail.
constructor TFileMapper.Create(AFileName: String);
begin
Inherited Create(AFileName, fmOpenRead or fmShareDenyWrite);
end;
Yes. Actually TFileStream inherited it from THandleStream.To show how easy it is to maintain it, here is a reduced one based on your feedback:
unit uFileMapper_2; {$mode objfpc}{$H+} interface uses Classes, SysUtils; Type { TFileMapper } TFileMapper = class(TFileStream) // It does not allocate memory here. public function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} end; implementation uses Windows; { TFileMapper } function TFileMapper.MapFile(AOffset: QWord): pointer; const // constants used by CreateFileMapping NO_MAXIMUM_SIZE_HIGH = 0; // 0 indicates to use the size of the NO_MAXIMUM_SIZE_LOW = 0; // file // constants used by MapViewOfFileEx BEGINNING_TO_END = 0; var FileMapping: HANDLE; begin Result := nil; // with the file handle, create a mapping for it FileMapping := CreateFileMappingA(Handle, nil, PAGE_READONLY, NO_MAXIMUM_SIZE_HIGH, // use file size NO_MAXIMUM_SIZE_LOW, nil); if FileMapping = 0 then raise Exception.createfmt('File: %s, Line: %s, Unable to map file: %s', [{$I %File%},{$I %Line%},FileName]); Result := MapViewOfFileEx(FileMapping, FILE_MAP_READ, HI(AOffset), // from beginning LO(AOffset), BEGINNING_TO_END, // to end nil); // map anywhere CloseHandle(FileMapping); if Result = nil then raise Exception.createfmt('File: %s, Line: %s, Unable to map view of file: %s', [{$I %File%},{$I %Line%},Filename]); end; end.
Now that I've gotten to inspect it again, I have a question and a comment (or two, not sure yet)
1. The Handle being used in CreateFileMapping looks like a field inherited from TFileStream. Is this correct ?
2. The non-OOP version could be called without creating classes or anything else. Just pass the filename string and that's it. IOW, no need to create a class, no need to subsequently free the class. It seems your code isn't complete, in order to use it, you have to create objects of that type. The non-OOP version doesn't need to do that, call the function, you're done.You can have similar functions in classes. You can call them without creating a class as I showed above.
and your comments are welcome too :)Thank you, and feel free to ask any question you might have. :)
The amount of memory consumed is a secondary concern. The real concern is that with OOP you have keep track of those objects so they can eventually be freed. That subtracts from the program's simplicity and ease of maintenance.That is true, but if it were for just one small function there would not be any need for OOP nor higher level languages. Assembly would be more than enough, that is one. Two, the concern about memory is not relevant any more with the gigantic amount of memory, unless you are working on some small limited embedded system or micro controller.In OOP if you create an object, you are responsible of freeing it. Similar to dealing with pair of functions Open/Close files. Or in our example: CreateFileMappingA then later CloseHandle.The non-OOP function didn't need to allocate any memory (creating an object, allocates memory.) Your usage of objects has imposed unnecessary overhead (create/use/track/free) on the program.
The code (class or otherwise) is supposed to map a file whose name is passed to it as a parameter and return the pointer to the location where the file is mapped. That's all it's supposed to do. Things it's _not_ supposed to do: implicitly or explicitly allocating unnecessary memory and, leaving open handles that should be closed. Those are two things it's not supposed to do, yet your OOP implementation does both thus complicating the implementation.If the class is supposed to get a pointer to a mapped file, and nothing else. It is possible to change MapFile into a class function which does not need an object. It is usage would be more like:The code I showed above does not leak memory, you can try it.It won't leak memory as long as the object is freed but, the freeing of the object is conspicuously missing as is its creation. The non-OOP version didn't need to, directly or indirectly, allocate memory and didn't need code someplace else to free an unnecessary memory block (the object/class itself.)
ptr := TFileMapper.MapFile(SomeFileName);
But I am sure you need to do some more work with this pointer. And that is why I built this class on top of TFileStream.You're right, it does plenty of work with that pointer, that's why there is a function to obtain it. What there isn't are, 1. more than one function (class/object/whatever) which is unnecessary. 2. unnecessary memory blocks allocated that eventually have to be freed and, 3. handle(s) that are left open when they should have been closed because they are not needed.
In the non-OOP implementation, I don't need to go looking around for where the file handle is. I don't need to depend on Lazarus (or any other tool) to show me where the file handle is (it's right there as a local variable.) The programmer needs to know where the handle is because, he/she has to ensure that handle is freed. In the non-OOP version ensuring that is trivial, in the OOP version, it isn't. You are trying to obviate the fact that after the file is mapped you are left with unfinished tasks that must eventually be done, such as freeing objects and closing handles. The non-OOP version does not burden the programmer with "pending" tasks that could be forgotten, thereby become a problem.Seeing everything that is not related to the function of the class (mapping files at this point ) is the problem. The programmer does not guess or wonder, the class and its ancestors are accessible. If you use Lazarus, click on Handle while holding CTRL key and it takes you to where it came from. You do not need to know where exactly the file is getting opened, you just need to know its handle. That is all you need to know to be able to map it. Otherwise, you still don't know how it is getting opened by the system when you call an API, in fact you do not have access to the source code, at best you have access to its assembly, which is, of course, not needed.It is dealing specifically with mapping the file, not with opening the file. Opening the file is left to the parent class: TFileStream.And that is one of the problems. In the non-OOP version, everything is right there in the function, makes it really easy to understand. The programmer doesn't have to know/guess which of the ancestors opened the file nor wonder where the file handle is coming from.
If I need to map another file, I just call the FileMap function again. I don't need to code another call to CreateFile either and, my function doesn't leave unnecessary "baggage" laying around that I eventually have to deal with.Reusing code is part of, you just need one class that opens files and allows you basic functions related to the open file. You do not need to call CreateFile every time you need to deal with some file.I don't need to repeat that code,It's not about repetition. The call to CreateFile occurred in only one place in the non-OOP version. The same place where the file mapping and the view are created. All nice and neat in one function. No need to go looking for stuff someplace else and wonder why the File Mapping is closed/freed but the File Handle isn't. Unlike the OOP version, symmetrical and parallel as it should be.
If I had wanted to make the function portable I could have made it portable but, since it doesn't have to be, there is no need to write unnecessary code. Portability is nice to have when it is needed but, it's not always needed. The real point is, OOP has nothing to do with portability. Portability is usually obtained by implementing an additional interface layer which can be implemented using any programming paradigm.I used the RTL class, and I mentioned what I know about the RTL (which applies to the class), not trying to credit it to OOP, but mentioned it as a fact.and as an extra bonus, that part related to opening the file is going to work on any major platform FPC runs on: Windows, Linux, and Mac. If I were to port this class to, say, Linux, I just need to worry about MapFile function.That's not a result of OOP programming, it's a result of the RTL. You're giving OOP credit it did not earn.
The RTL doesn't bring anything to the table that the program needs. The Filemap function is very reusable as it is but, I will admit that I could have coded it in a way that would make it completely reusable instead of just "easily reusable". I prefer code that is easy to customize than code that is fully reusable. By fully re-usable I mean code that can simply be copied and pasted without any changes or placed in a unit and compiled for direct re-use.I used the RTL, your function did not use the RTL. I credit my class the benefit of using the RTL, see? Re-usability.Is your code multi-platform?No and, it doesn't have to be. The program (not just the function) only applies to Windows therefore, portability in this case is not applicable and, even if it were, it is no thanks to OOP but, to the RTL functions regardless of the paradigm used to implement them.
And what you've avoided is justifying leaving the file handle open. Your OOP version burdens the program with unfinished tasks.I just answered this one when I mentioned changing the function into class function, of course with whatever modification needed.The file doesn't get opened magically. Based on the classes and methods you've presented, it gives the impression that the inherited create method (from TFileStream) opens the file and, unnecessarily keeps the file handle opened.QuoteTFileMapper = class(TFileStream) // piece #2 - I suppose this is where the file is opened.No, this is to give our class all the abilities of TFileStream, and to build on top of it.
Are you saying that the function is better implemented without using OOP ?... I agree :) As far as the amount of memory is concerned, as I've stated previously, the amount of memory isn't my primary concern in this case, my concern is that there are memory blocks left laying around that have to be freed. The non-OOP version doesn't have to worry about that because no such memory blocks exist.Again, no need for OOP to run one function. The result of your function is going to be used somehow. Also, the amount of memory is not relevant, leaks are.It's a fact that, somewhere memory is being allocated to hold the object and, that memory block has to be eventually freed. That's another thing your program needs to keep track of that the non-OOP version doesn't have to do.Quoteconstructor Create(AFileName: String); // piece #3 - didn't need that before. Again, it probably allocates memoryThis part is not necessary in our example, and it does not allocate any memory that needs to be freed.
// which doesn't seem to be freed anywhere.
You can keep related functions together without using OOP. OOP doesn't bring that to the table.In this case of this example, this is true. But with objects you can keep related functions together.Nothing there that cannot be done just as simply without OOP.Quote// not comparing, just an observation. The routine I posted didn't give the option to map at an offset.Because I can focus on mapping itself and different possibilities. Like you can pass the offset if you want, and if you do not it takes 0 by default.
// your implementation is going beyond the call of duty... which is perfectly fine.
function MapFile(AOffset: QWord = 0): pointer; {AOffset from beginning of file} // piece #4
Maybe there is no reason to show the pointer returned by MapFile, and all the functionality could be contained in the class.Maybe so but, that would create a lot of unnecessary coupling among the functions that implement the functionality.
An object gives you more control over your variables. Some variables are local to one member function, while other variables are used by more than one member function and need to be valid along the life of the object. Some variables are common among all instances of this object.If an object gives more control over variables then why is it that a file handle which is no longer needed isn't closed ? I don't call that more control. I call that creating artificial dependencies in the implementation.
It also gives you control over visibility, you could choose to make some fields or member functions not visible outside the object, or expand their visibility to descendant classes, or totally visible.The only thing that needs to be visible is the pointer where the file is mapped. What is visible in the OOP version, among other things, is that there is a file handle somewhere that hasn't been closed and there are memory blocks (objects/classes/whatever) that need to be freed. More things to keep track of. More room for something that needs to be done to be forgotten or left out.
Then you have properties (and default property), inheritance, interfaces, RTTI, ... etc. And don't forget generics. Generics is where you can't compare it with functions anymore, not even with generic functions. You need a list that deals with, say, doubles? Use TFPGList from unit fgl: specialize TFPGList<double>. Maybe you need a list of strings as well, simple: specialize TFPGList<string>. These lists are objects.That sounds nice IF you need them and/or don't have better solutions. Of everything you've listed there, the only thing I have occasionally wished for are generics and, generics are not an OOP construct, it's essentially a compiler macro. They are a nice feature when needed but they have nothing to do with OOP.