Marat1961 17 Posted October 15, 2020 6 minutes ago, Kryvich said: Don't take it to heart. Sometimes it is difficult to find the right English words, which makes my sentences sound too categorical. And what is your native language, for me it is Russian, although I am a Bashkir by nationality. Share this post Link to post
Kryvich 165 Posted October 15, 2020 (edited) I have several open-source projects, old and new, but there is no active use of enumerations. But they are actively used in my amateur projects related to linguistics and others. To give a hint of how the enumerations can be used, take for example the syntactic analyzer messages, warnings and errors: TSyntaxMessage = (smNone, // Here are suggestions of syntax analyzer: smRareLinkForWord, // Rare syntactic link smFirstSuggestion = smRareLinkForWord, smAdjToVerb, //... smSuggestionN, smLastSuggestion = smSuggestionN, // Here are warnings smNotLinkedWord, smFirstWarning = smNotLinkedWord, // ... smWarningN, smLastWarning = smWarningN // Here are errors ); We have a list of messages logically divided into groups: 1) no message smNone, 2) suggestions smFirstSuggestion..smLastSuggestion, 3) warnings smFirstWarning..smLastWarning, 4)... I don't need to set numerical values of members and check if they fall into the required sub-ranges. This happens automatically. Now I can write: case SyntaxMessage of smNone: ; // All is OK smFirstSuggestion..smLastSuggestion: ProcessSuggestion; smFirstWarning..smLastWarning: ProcessWarning; else Assert(False, 'Ops...'); end; We can create a helper to work with messages: TSyntaxMessageHelper = record helper for TSyntaxMessage class function FromString(S: string): TSyntaxMessageHelper; static; // Returns TSyntaxMessage by the name function ToString: string; // Returns the name of message function Text: string; // Get a text of user's message end; And use this as follows: Mess1 := TSyntaxMessage.FromString('smBadUseOfVerb'); // when loading from XML etc. s := Mess1.ToString; // when saving to XML, JSON,... ShowMessage(Mess1.Text); Then we can declare a set of messages, add a helper for it, and work in a convenient manner. TSyntaxMessages = set of TSyntaxMessage; TSyntaxMessagesHelper = record helper for TSyntaxMessages class function FromString(S: string): TSyntaxMessages; static; function ToString: string; function Text: string; function First: TSyntaxMessage; function IsEmpty: Boolean; function Count: Integer; procedure ExcludeWarnings; procedure Include(const Messages: TSyntaxMessages); procedure Exclude(const Messages: TSyntaxMessages); procedure ForEach(Proc: TSomeProcedure); //... end; MyMessages := TSyntaxMessages.FromString('smRareLinkForWord smAdjToVerb smNotLinkedWord'); if not MyMessages.IsEmpty then Mess := MyMessages.First; MyMessages.Include(OtherMessages); MyMessages.ForEach(DoSomething); We can add inline to the method declarations for faster code. A variable of type TSyntaxMessages will have a minimum size sufficient to store the set. The compiler will automatically check for ranges. Edited October 15, 2020 by Kryvich 2 Share this post Link to post
Marat1961 17 Posted October 15, 2020 21 hours ago, Stefan Glienke said: If that would compile - and even if it would that would not prevent that set to be assigned [4] to. Validation works for the range type, but this check fails for the set. Enum set in Delphi is also not implemented perfect. I've seen better and more correct implementations of the Pascal language. procedure TestTsgList.Test1; type TRange = 0..15; TSet = set of TRange; TMyEnum = (Neg = 1, Template = 4, Static = 15, Header = 2, Custom = 4); TMyEnumSet = set of TMyEnum; var i, size: Integer; r: TRange; s: TSet; e: TMyEnum; es: TMyEnumSet; begin size := sizeof(es); size := sizeof(s); // set of enum es := [TMyEnum(3), TMyEnum(10)]; e := High(TMyEnum); e := Succ(e); Include(es, e); Include(es, TMyEnum(10)); // set of range r := 15; r := Succ(r); Include(s, r); end; Share this post Link to post
Fr0sT.Brutal 900 Posted October 16, 2020 19 hours ago, Lars Fosdal said: You store Enums as strings in the database? In one case in multi-project complex I use constants for datatype. I'm 100% sure they won't change except adding new values so I carve them in stone. Not an option I like currently, now I think I'd better use mnemonic chars for this. There are several cases where I use mnemonic chars (abstract sample - 'M' for male sex, 'F' for female, whatever else for any possible later addition). For (de)serialization when dealing with files I use enum names and sometimes array of values if I don't want prefixed names to appear. 19 hours ago, Rollo62 said: Yes, but also names might change over time, so thats no real benefit over numbers. All right, numbers have a higher risk of being re-ordered, but in principle: "enums might change over time". Everything might change but enum names usually change less frequently. Share this post Link to post
Lars Fosdal 1792 Posted October 16, 2020 We mostly store the ordinal value of the enumerated type variable to an int in the database. Yes, it is fragile with regards to people changing the enumeration - but we have guidelines and unit tests to avoid that. type TPSDExpeditionType = (etRetailer,etWholesaler,etRetailerDairy,etWholesalerDairy); // Do not reorder or remove - add at end TLocation = class ExpeditionType: TPSDExpeditionType; We use RTTI to export the enums to a view in the database so that we can do logic in the SQL code using the same identifiers as we use in Delphi. T-SQL doesn't do constants per se, but selecting from a flat static view is optimized by the query engine and is extremely low cost. CREATE TABLE [dbo].[t_locations]( [Id] [int] IDENTITY(1,1) NOT NULL, [ExpeditionType] [int] NOT NULL -- CREATE VIEW [dbo].[v_psd_constants] AS SELECT 0 AS etRetailer, 1 AS etWholesaler, 2 AS etRetailerDairy, 3 AS etWholesalerDairy -- SELECT * FROM v_Locations loc WHERE loc.ExpeditionType = (SELECT TOP(1) etRetailerDairy FROM v_psd_constants) This means we can handle renames and additions - but not removals or reordering. From an SQL performance perspective, this runs circles around using strings. 1 1 Share this post Link to post
Stefan Glienke 2002 Posted October 16, 2020 And if you would use tinyint you could even save 3 byte per record 😉 Share this post Link to post
Lars Fosdal 1792 Posted October 16, 2020 8 minutes ago, Stefan Glienke said: And if you would use tinyint you could even save 3 byte per record 😉 In most cases, yes 🙂 Share this post Link to post
Fr0sT.Brutal 900 Posted October 16, 2020 2 hours ago, Lars Fosdal said: T-SQL doesn't do constants per se, but selecting from a flat static view is optimized by the query engine and is extremely low cost. Pretty good option! Share this post Link to post
Marat1961 17 Posted October 16, 2020 On 10/15/2020 at 2:16 PM, Rollo62 said: I'm not 100% sure, since I never checked that, but I think class helpers won't work on consts. I hope we are not talking about writing a helper to any specific value of the enumerated type? Besides the declaration of constants, you can declare other related types for these constants, as well as methods to work with them. Helpers for record can also be written. hEntity = record v: Cardinal; end; hEntityHelper = record Helper for hEntity constructor From (v: Cardinal); function FromRequest: Boolean; inline; function Request: hRequest; inline; end; TsdEntity = record const NoEntity: hEntity = (v: 0); It would be possible to write methods directly in the type itself, but in our case there was a mutual dependence of handlers for several types. Helpers allowed to solve this problem in a graceful way. Unlike the classes, I cannot write a forward declaration for writing. But at least I can declare pointers to a record which will be declared later, either in the interface part or in the implementation section. Share this post Link to post
Marat1961 17 Posted October 16, 2020 (edited) I use enumerated types a lot, there is nothing wrong with them. If you have an enumerated type parameter in a procedure, the compiler will not give you another substitution, unlike integer constants that can be confused. However, no one bothers to use handlers instead of integer constants. This is usually an record with a number or a packed record with a set of Subrange Types fields that can take up a comparable amount of memory. By the way, I caught myself thinking, I have not experimented with packed records with enumerated fields. In the handler, some of the bits can be set aside to determine the type, subtype, number of the object within this subtype. Something like a decimal classifier, like the medical classifier of diseases. I was the architect of the openEHR-based medical information system for the Tomsk Scientific Research Institute of Cardiology. Such classifiers are ubiquitous in various databases and reference information systems. Edited October 16, 2020 by Marat1961 wrong translation Share this post Link to post