Jump to content

Leaderboard


Popular Content

Showing content with the highest reputation on 11/09/18 in all areas

  1. When writing libraries you sometimes want to provide users (that is, programmers) with a flexible API. If a specific part of your library can be used in different ways, you may want to provide multiple overloaded methods accepting different combinations of parameters. For example, IOmniPipeline interface from OmniThreadLibrary implements three overloaded Stage functions. function Stage(pipelineStage: TPipelineSimpleStageDelegate; taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload; function Stage(pipelineStage: TPipelineStageDelegate; taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload; function Stage(pipelineStage: TPipelineStageDelegateEx; taskConfig: IOmniTaskConfig = nil): IOmniPipeline; overload; Delphi’s own System.Threading is even worse. In class TParallel, for example, there are 32 overloads of the &Forclass function. Thirty two! Not only it is hard to select appropriate function; it is also hard to decode something useful from the code completion tip. Check the image below – can you tell which overloaded version I’m trying to call? Me neither! Because of all that, it is usually good to minimize number of overloaded methods. We can do some work by adding default parameters, but sometimes this doesn’t help. Today I’d like to present an alternative solution – configuration records and operator overloading. To simplify things, I’ll present a mostly made-up problem. You can download it from github. An example type TConnector = class public procedure SetupBridge(const url1, url2: string); overload; procedure SetupBridge(const url1, proto2, host2, path2: string); overload; procedure SetupBridge(const proto1, host1, path1, proto2, host2, path2: string); overload; // procedure SetupBridge(const proto1, host1, path1, url2: string); overload; end; This class expects two URL parameters but allows the user to provide them in different forms – either as a full URL (for example, ‘http://www.thedelphigeek.com/index.html’) or as (protocol, host, path) triplets (for example, ‘http’, ‘www.thedelphigeek.com’, ‘index.html’). Besides the obvious problem of writing – and maintaining – four overloads this code exhibits another problem. We simply cannot provide all four alternatives to the user! The problem lies in the fact that the second and fourth (commented out) overload both contain four string parameters. Delphi doesn’t allow that – and for a good reason! If we could define both at the same time, the compiler would have absolutely no idea which method to call if we write SetupBridge(‘1’, ‘2’, ‘3’, ‘4’). Both versions would be equally valid candidates! So – strike one. We cannot even write the API that we would like to provide. Even worse – the user may get confused and may expect that we did provide the fourth version and they try to use it. Like this: conn := TConnector.Create; try conn.SetupBridge('http://www.thedelphigeek.com/index.html', 'http://bad.horse/'); conn.SetupBridge('http://www.thedelphigeek.com/index.html', 'http', 'bad.horse', ''); conn.SetupBridge('http', 'www.thedelphigeek.com', 'index.html', 'http', 'bad.horse', ''); // this compiles, ouch: conn.SetupBridge('http', 'www.thedelphigeek.com', 'index.html', 'http://bad.horse/'); finally FreeAndNil(conn); end; Although the last call to SetupBridge compiles, it does something that user doesn’t expect. The code calls the second SetupBridge overload and sets url 1 to ‘http’ and url 2 to (‘www.thedelphigeek.com’, ‘index.html’, ‘http://bad.horse/’). Strike two. The output of the program proves that (all ‘1:’ lines should be equal, as should be all ‘2:’ lines): Last but not least – the API is not very good. When we need to pass lots of configuration to a method, it is better to pack the configuration into meaningful units. So – strike three and out. Let’s rewrite the code! A solution Records are good solution for packing configuration into meaningful units. Let’s try and rewrite the API to use record-based configuration. TURL = record end; TConnector2 = class public procedure SetupBridge(const url1, url2: TURL); end; Much better. Just one overload! Still, there’s a problem of putting information inside the TURL record. I could add a bunch of properties and write: url1.Proto := 'http'; url1.Host := 'www.thedelphigeek.com'; url1.Path := 'index.html'; url2.URL := 'http://bad.horse/'; conn2.SetupBridge(url1, url2); Clumsy. I have to declare two variables and type lots of code. No. I could also create two constructors and write: conn2.SetupBridge(TURL.Create('http', 'www.thedelphigeek.com', 'index.html'), TURL.Create('http://bad.horse/')); conn2.SetupBridge(TURL.Create('http://www.thedelphigeek.com/index.html'), TURL.Create('http://bad.horse/')); That looks better, but still – in the second SetupBridge call both TURL.Create calls look completely out of place. Do I have to pull back and rewrite my API like this? TConnector = class public procedure SetupBridge(const url1, url2: string); overload; procedure SetupBridge(const url1: string; const url2: TURL); overload; procedure SetupBridge(const url1, url2: TURL); overload; procedure SetupBridge(const url1: TURL; const url2: string); overload; end; Well, yes, this is a possibility. It solves the problem of supporting all four combinations and it nicely puts related information into one unit. Still, we can do better. Operators to the rescue! I’m quite happy with the Create approach for providing an information triplet. it is the other variant – the one with just a single URL parameter – that I would like to simplify. I would just like to provide a simple string when the URL is in one piece. To support that, we only have to add an Implicit operator which converts a string into a TURL record. (Another one converting TURL into a string is also helpful as it simplifies the use of TURL inside the TConnector class.) Here is full implementation for TURL: TURL = record strict private FUrl: string; public constructor Create(const proto, host, path: string); class operator Implicit(const url: string): TURL; class operator Implicit(const url: TURL): string; end; constructor TURL.Create(const proto, host, path: string); begin FURL := proto + '://' + host + '/' + path; end; class operator TURL.Implicit(const url: string): TURL; begin Result.FURL := url; end; class operator TURL.Implicit(const url: TURL): string; begin Result := url.FURL; end; Simple, isn’t it? The implementation uses the fact that TConnector has no need to access separate URL components. It is quite happy with the concatenated version, created in the TURL.Create. This allows us to provide parameters in a way that is – at least for me – a good compromise. It allows for a (relatively) simple use and the implementation is also (relatively) simple: conn2 := TConnector2.Create; try conn2.SetupBridge('http://www.thedelphigeek.com/index.html', 'http://bad.horse/'); conn2.SetupBridge('http://www.thedelphigeek.com/index.html', TURL.Create('http', 'bad.horse', '')); conn2.SetupBridge(TURL.Create('http', 'www.thedelphigeek.com', 'index.html'), TURL.Create('http', 'bad.horse', '')); // this works as expected: conn2.SetupBridge(TURL.Create('http', 'www.thedelphigeek.com', 'index.html'), 'http://bad.horse/'); finally FreeAndNil(conn2); end; The output from the program shows that everything is OK now:
  2. We collected most of popular controls, which has a support of VCL Styles. All products was tested with our styles and default styles. We hope that this information will be very helpful in your choise... https://www.delphistyles.com/vcl/tlist.html This page shows that many components from different vendors can be in the same custom style in your application! Regards, DelphiStyles
  3. Stefan Glienke

    New in 10.3: IDE UI Improvements in the Main Window

    It will not get them more customers just because it's 64bit and runs out of memory later because all the instabilities and the compiler getting into a bad state because of compile errors or what not crashing the IDE will still be there as will the poor code tooling. Going the easy/cheap way every time is what puts Delphi and the IDE into the current situation of constantly having to react to external factors and tinker around edges. Sometimes I really have a hard time believing that or you just avoid certain features or have subconsciously developed a certain habit to do things differently. Or you just use it to prepare demos.
  4. When you are viewing the contents of a form, Delphi substitutes Unicode characters with the corresponding codes in #nnnn format. (This is valid for Delphi 10.2.3 and earlier, I do not know how it will be in 10.3). Therefore, manual editing of text strings of form elements is very difficult. After installing the Kryvich's Forms Humanizer package in your IDE, you can use a simple keyboard shortcut Ctrl-Alt-H to "humanize" all the text strings in your form's editor. This means that codes like #nnnn will be replaced with the corresponding Unicode characters. You can download and test the plugin for free here: https://sites.google.com/site/kryvich/kryvichs-form-humanizer. Enjoy!
  5. Stefan Glienke

    New in 10.3: IDE UI Improvements in the Main Window

    Yeah, then the IDE can more excessively waste memory left and right until your RAM is used up instead of crash early, brilliant idea... Throwing more memory at an application that does not know how to properly manage memory is not the solution. Oh, please, no excuses about the compiler leaking memory because of LLVM, if you know that then don't load it into the IDE process. Ah, that has to be done to use it for IDE tooling, like code completion and all kinds of source related things. Now you see why other languages and IDEs go the way of separating this and using a language service to provide such information
  6. David Hoyle

    Reset the editor modified status in IDE

    In instance 1) you either need to Save the file or rollback all the changes. If you're trying to show an alternate view of a line of text in the editor then I think you getting into the territory of hacking the IDE and drawing something over the top of the information in the editor. I think David Millington wrote at least one article on this before he joined Embarcadero (https://parnassus.co/mysteries-ide-plugins-painting-code-editor-part-2/). On point 2, you might need to see if the editor is viewing a DFM file to understand whether the editor is viewing a .pas file of the .dfm file. You might need notifiers so have a look at the following: https://www.davidghoyle.co.uk/WordPress/?p=1272; https://www.davidghoyle.co.uk/WordPress/?p=1761; https://www.davidghoyle.co.uk/WordPress/?p=1810
  7. HolgerX

    ShlObj -> PickIconDlg() - strange behaviour

    Hmm.. the buffer for pszIconPath 'must' be have the size of MAX_PATH.. https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-pickicondlg function PickIconDlg(AHwnd : HWND; pszIconPath : PWideString; cchIconPath : DWORD; var piIconIndex : integer):integer; stdcall; external 'Shell32.dll' name 'PickIconDlg'; function PickIconDialog(var Filename: WideString; var IconIndex: Integer ): Boolean; var tmp : Array[0..MAX_PATH-1] of WideChar; // Min Size of pszIconPath must be MAX_PATH begin Result := False; FillChar(tmp[0], MAX_PATH * SizeOf(WideChar),0); Move(FileName[1],tmp[0],Length(FileName)* SizeOf(WideChar)); if ( PickIconDlg( 0, @tmp[0], MAX_PATH, IconIndex ) <> 0 ) then begin Filename := Widestring(tmp); Result := True; end; end; procedure TForm1.Button1Click(Sender: TObject); var tmpIdx : integer; tmpFile : WideString; begin tmpFile := ParamStr(0); PickIconDialog(tmpFile, tmpIdx); ShowMessage(tmpFile + ' - ' + IntToStr(tmpIdx)); end;
  8. Agreed, type redeclaration is really not very helpful in many situations. - you still have implicit type compatibility no matter what - but helpers stop working
  9. And after that you show why the "right" way is bad... An URL consists of different parts which combined give a string, yes but should be represented as the different parts, hence the record. Especially since there are cases where such a type cannot just be represented as string or number but consists of different data of different types. Yes there might be a string representation but it should not be passed as that through your API.
  10. The benefit of the TUrl type opposed to differently named overloads is also that you declare a domain type and make the API cleaner than just (string, string, string) regardless the naming of the parameters
  11. You can even shorten the TURL.Create by introducing a function URL wrapping around that. This is similar to TRect.Create and Rect. As a drawback it somewhat litters your scope.
  12. Dear Primoz, thanks for the nice article. For me personally (this is not that I want to convince everybody), I like and use another pattern quite often in such cases: conn.SetupBridge_FromRelative(url : String); conn.SetupBridge_FromAbsolute(url : String); conn.SetupBridge_FromXxxx( ....); This makes it a lot easier for me to choose the right version, and I got clear parameter lists, on the cost of longer function names, of coarse. Since I'm a friend of long and "speaking" names, this is not a big problem to me, but I can confess this can get nasty in some situations too. Rollo
  13. Andrea Magni

    How to make a component available to all platforms

    A nice hack from @Dalija Prasnikar here: https://stackoverflow.com/questions/27486939/is-there-simpler-way-of-saying-that-delphi-component-control-is-supported-on-all HTH
  14. Kryvich

    Delphi SQL Formatter

    I think 5 function calls or 5 assigments is not the best code style. Consider these variants: SQL.Add('SELECT EntCity, EntStageName' + sLineBreak + 'FROM Entertainers' + sLineBreak + 'WHERE 1=1' + sLineBreak + 'AND TEST=''TEST''' + sLineBreak + 'ORDER BY EntCity ASC, EntStageName ASC'); CommandText := 'SELECT EntCity, EntStageName' + sLineBreak + 'FROM Entertainers' + sLineBreak + 'WHERE 1=1' + sLineBreak + 'AND TEST=''TEST''' + sLineBreak + 'ORDER BY EntCity ASC, EntStageName ASC'; Optionally replace line breaks with spaces: SQL.Add('SELECT EntCity, EntStageName ' + 'FROM Entertainers ' + 'WHERE 1=1 ' + 'AND TEST=''TEST'' ' + 'ORDER BY EntCity ASC, EntStageName ASC');
  15. In the right pascal way it would be like this: type TUrl = type string; TProto = type string; THost = type string; TPath = type string; TConnector = class public procedure SetupBridge(const url1, url2: TUrl); overload; procedure SetupBridge(const url1: TUrl; const proto2: TProto; const host2: THost; path2: TPath); overload; procedure SetupBridge(const proto1: TProto; const host1: THost; path1: TPath; proto2: TProto; const host2: THost; path2: TPath); overload; procedure SetupBridge(const proto1: TProto; const host1: THost; path1: TPath; url2: TUrl); overload; end; The type in "TUrl = type string" means that TUrl is a new different string type. Then you would need to specify the type of the parameter when calling: Connector.SetupBridge(TUrl('http://www.thedelphigeek.com/index.html'), TUrl('http://bad.horse/')); Connector.SetupBridge(TUrl('http://www.thedelphigeek.com/index.html'), TProto('http'), THost('bad.horse'), TPath('')); So are we safe? No! Unfortunately, Delphi allows assignments between custom string types. Connector.SetupBridge('http://www.thedelphigeek.com/index.html', 'http://bad.horse/'); Connector.SetupBridge(TProto('http://www.thedelphigeek.com/index.html'), TProto('http://bad.horse/')); There is no overloaded methods like "procedure SetupBridge(const url1, url2: TProto);". But the compiler will not give any warning or hint. I would like the compiler developers to add a hint in such situations, not only for the types generated from string, but also for any other types (Integer, Byte etc.)
  16. Lars Fosdal

    New in 10.3: IDE UI Improvements in the Main Window

    I got 32Gb RAM, and I rarely go above 18-20Gb - so I got a lot of RAM to spare for IDEs excessively wasting memory.
×