TStringHelper is just broken, even at compiler level.

TStringHelper is just broken, even at compiler level.
Its injected methods do NOT work for sub types.

The following would not compile:

type
  mystring = type string;
var s: string;
    s2: mystring;
begin
  s := '1234';
  writeln(s.IndexOf('3'),'=2');
  s2 := '1234';
  writeln(s2.IndexOf('3'),'=2');
It complains on the last line compilation (I tested with XE6), with a:
[dcc32 Error] stringhelper.dpr(20): E2018 Record, object or class type required

IMHO this is just a show stopper.
If you define your own sub-type, you would not be able to use the string helper syntax.
Then you would have to use the system.pas classic functions - which will work, of course.

I use a lot of those custom types, which are IMHO mandatory to write safe code.
Strong typing, even for simple types, is a very good practice.
If you define

type
  TTimeInSeconds = type double;
  TTimeInMilliseconds = type double;
Then the compiler will make a difference between a second and millisecond variable, your function definitions would be much more efficient.
This is one of the reasons why the NASA would never use JavaScript to write its software. It may lead into disasters...

So, we are told by Delphi documentation to use TStringHelper methods, and that the system.pas string functions are about to be deprecated...
And we can not use it on custom sub types?

Another half backed "feature"...
http://blog.synopse.info/post/2015/05/08/I-do-not-like-people-shoot-in-my-foot%2C-do-you

Comments

  1. while I agree with all your points, I can see why TStringHelper doesn't apply to "mystring", even if it would make sense; I think it sits somewhere in the gray area...

    ReplyDelete
  2. It doesn't apply to "mystring" because the type helper is "attached" to a given type. So it is not propagated to sub types. BTW FPC does the same.
    Since string functions are told to be deprecated, I would not define it as a "gray area".... but as a "black hole"..
    IMHO type helpers would not be the way to introduce OOP to simple types: it should be done at compiler level, in a clean way (as it is in Oxygene or DWS AFAIR).
    Then, type helpers may be used at a given type level, e.g. introducing conversion between seconds and milliseconds. 
    But for common methods (like IndexOf/Remove...), it should be at compiler level, or with some kind of "inheritance".
    This TStringHelper looks to me like a lazy implementation.

    ReplyDelete
  3. You can circumvent the issue by writing:
        writeln(string(s2).IndexOf('3'),'=2');
    Not so clean, right? And it will copy the whole string content (via _UStrFromPWCharLen) from s2 to an UnicodeString, just to run the IndexOf() method...
    Performance nightmare...

    ReplyDelete
  4. Personally I find the 'type' syntax itself a 'half-baked "feature"' given it doesn't affect assignment compatibility...

    ReplyDelete
  5. Chris Rolliston You are right, under Delphi.
    You have {$T+} to enable type safety at pointer (@) level.
    It could be handy to enable type safety at assignement level also.
    Isn't FPC able to do this, BTW?

    ReplyDelete
  6. A. Bouchez It's not as easy as just allowing a helper for any derived type - according to that logic a TDoubleHelper would work for TDateTime. In the case of TCaption or TFileName using the TStringHelper might be useful - however on TFileName it already might be arguable. Maybe "attaching" an existing helper to a derived type of the same type should be possible. So you could make the TStringHelper available for TCaption as well. At the same time it would be nice if such helpers would support inheritance (not so hard I guess since I have been told this can be enabled by some compiler hacking ^^).

    ReplyDelete
  7. Isn't the whole point of "type x = type y" syntax that X is different from Y and not just an alias? Or is my braincells failing me (wouldn't be a huge surprise). If they're not, then I think I quite agree with the compiler.

    ReplyDelete
  8. Asbjørn Heid Yes, this is how it works.
    But the problem is that string functions are documented as deprecated, and that the TStringBuilder won't work with string sub-types: in this case, you can not use IndexOf() or any other pseudo-method on your sub-type..

    ReplyDelete
  9. A. Bouchez Yeah but if it's a "type X = type string" then the compiler can't know if makes sense to call IndexOf on an X instance.

    ReplyDelete
  10. A string is a string, right? IndexOf() does make sense whenever it is a type or a sub-type...

    ReplyDelete
  11. A. Bouchez If a string is a string, why use a subtype then?

    ReplyDelete
  12. Ah ok, editors. Forgot about that part.

    ReplyDelete
  13. "point of "type x = type y" syntax that X is different from Y and not just an alias?"

    Only in a strongly typed language, in Delphi it's kinda half that way and kinda half not. There are rules, but they change from time to time. And often you need to know the implementation in order to reason about it: inc(TDateTime) adds a day, for example. And int i:=3; TDateTime today=i; shouldn't compile but IIRC does in every Delphi version. OTOH, double foo=i; should...

    ReplyDelete
  14. Moz Le I do not understand.
    AFAIK inc(aDateTime) does not compile. You have to write aDateTime := aDateTime+1, just like with plain double.
    aDateTime := aInteger compiles, just like aDouble := aInteger - since there is no precision loss. Conversion from the other side (i.e. aInteger := aDateTime) does not compile, since it would need information about rounding/truncation: the decimals are lost.
    All this sounds pretty consistent.
    What are you talking about?
    What is not consistent is that function parameters handle strong typing whereas assignment doesn't, as pointed by Chris.

    ReplyDelete
  15. Yeah I was wrong, I distinctly remembered "type X = type Y" made X and Y instances assignment incompatible. Not sure where I picked up on that idea.

    Given that they're not, I agree it's sensible to "forward" the helpers as well.

    ReplyDelete
  16. A. Bouchez what I mean in that specific case is that numbers don't have units but dates do. Take SQL, for example, you can't just say "DATETIME =DATETIME + 3", you have to say DATEADD(DATETIME, INTERVAL 3 DAYS)" or whatever units you actually mean. The bug that you can't actually use inc() on numbers-that-are-TDateTime's is one that's existed for a very long time.

    In a strongly typed language you couldn't do any of those tricks, subtypes are incompatible and there is often a lot of casting (safecasting as a rule), which is one reason strongly typed languages are unpopular.

    Delphi has a weird set of rules about which numeric types and functions work with each other and which don't. Those are just two examples. Other languages have parallel but differnt rules - I swap between C, Pascal and PHP/Python regularly and this stuff takes way too much of my brainpower. "can I pass the value across this boundary" questions, specifically, but also "it works in language X, why not in language B".

    One example: incrementing a time in C adds one second, not one day.

    ReplyDelete
  17. Moz Le IMHO those are not bugs, and the set of rules about types is pretty clear.
    There is a distinction between "ordinal" types (like integer or enumerations) and "floating point" types (like double or currency).
    About the time encoding, this is because the storage convention is not the same. Delphi TDateTime follows the COM/OLE date time encoding as a float, where C defined time_t as an integer.
    All those are conventions. Both have advantages and disavantages. I do not see the problem.

    ReplyDelete
  18. A. Bouchez It's only a problem if you expect Delphi to be strongly typed, or try to reason about its behaviour rather than experimenting.

    ReplyDelete
  19. Moz Le I do not think that you need "experimenting". Just try to get the fundamentals of pascal in this case.
    Since I read the Turbo Pascal language manual almost 30 years ago, the concept of "ordinal" and "decimal" values did not change.
    And I suspect the "type" definition did exist back to the original Wirth grammar, in 1971. https://www.cs.utexas.edu/users/novak/grammar.html

    ReplyDelete
  20. I'm not disagreeing with you on the specific case of TDateTime being floating point, but with the general problem that reasoning about types in any language requires either exhaustive knowledge of all the specifications, and bugs, or experiment. Especially in the case of Delphi, where there is no specification that I'm aware of, just documents in various states of construction and repair.

    The C++ spec is not a good example of the benefits of a specification, it's more like a counter-example.

    ReplyDelete

Post a Comment