• Discriminants or Constructor Function for Limited Types

    From R R@21:1/5 to All on Wed May 4 02:02:54 2022
    There are two ways (to my knowledge) how to initialize objects of limited types. Either the limited type has some discriminants

    type DLT (Width, Length : Positive) is tagged limited private;
    Obj : DLT (3, 5);

    or I can provide a constructor function that takes corresponding parameters

    type LT (<>) is tagged limited private;
    function Make (Width, Length : Positive) return LT;
    Obj : LT := Make (3, 5);

    Do you recommend one way over the other? Why? Is it possible to combine both methods (discriminants plus constructor)?

    RREE

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to R R on Wed May 4 11:47:54 2022
    On 2022-05-04 11:02, R R wrote:
    There are two ways (to my knowledge) how to initialize objects of limited types. Either the limited type has some discriminants

    type DLT (Width, Length : Positive) is tagged limited private;
    Obj : DLT (3, 5);

    or I can provide a constructor function that takes corresponding parameters

    type LT (<>) is tagged limited private;
    function Make (Width, Length : Positive) return LT;
    Obj : LT := Make (3, 5);

    Do you recommend one way over the other? Why? Is it possible to combine both methods (discriminants plus constructor)?

    Two more ways are allocators and limited aggregates.

    There is no good way to safely initialize a limited object because Ada
    lacks proper constructors and because the initialization model is
    inherently unsafe with regard or exceptions, task components,
    self-referential discriminants (AKA Rosen's trick).

    But there are numerous hacks and workarounds depending on the objective.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Jeffrey R.Carter@21:1/5 to R R on Wed May 4 11:40:10 2022
    On 2022-05-04 11:02, R R wrote:
    There are two ways (to my knowledge) how to initialize objects of limited types. Either the limited type has some discriminants

    type DLT (Width, Length : Positive) is tagged limited private;
    Obj : DLT (3, 5);

    or I can provide a constructor function that takes corresponding parameters

    type LT (<>) is tagged limited private;
    function Make (Width, Length : Positive) return LT;
    Obj : LT := Make (3, 5);

    Do you recommend one way over the other? Why? Is it possible to combine both methods (discriminants plus constructor)?

    Just as you can do

    S : String := "Hello";

    you can do

    Obj : DLT := New_DLT (3, 5);

    --
    Jeff Carter
    "It has been my great privilege, many years ago,
    whilst traveling through the mountains of Paraguay,
    to find the Yack'Wee Indians drinking the juice of
    the cacti."
    The Old Fashioned Way
    152

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From AdaMagica@21:1/5 to All on Wed May 4 08:05:41 2022
    There are further ways (from Ada 83 on):

    Like any record type, you can initialize a limited record by giving default values for all components:
    type LRT is limited record
    Component: Some_Type := Default;
    end record;

    You can define an Initialize procedure:

    type LT is limited private;
    procedure Initialize (Obj: out LT);

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Wed May 4 18:49:51 2022
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t4ti4a$s1u$1@gioia.aioe.org...
    On 2022-05-04 11:02, R R wrote:
    There are two ways (to my knowledge) how to initialize objects of limited
    types. Either the limited type has some discriminants

    type DLT (Width, Length : Positive) is tagged limited private;
    Obj : DLT (3, 5);

    or I can provide a constructor function that takes corresponding
    parameters

    type LT (<>) is tagged limited private;
    function Make (Width, Length : Positive) return LT;
    Obj : LT := Make (3, 5);

    Do you recommend one way over the other? Why? Is it possible to combine
    both methods (discriminants plus constructor)?

    Two more ways are allocators and limited aggregates.

    There is no good way to safely initialize a limited object because Ada
    lacks proper constructors and because the initialization model is
    inherently unsafe with regard or exceptions, task components, self-referential discriminants (AKA Rosen's trick).

    But there are numerous hacks and workarounds depending on the objective.

    Those cases that you worry are "unsafe" seem to me to only occur because of "hacks and workarounds". There's no good reason to do any of those things intentionally unless you are using a "hack" to do something dubious in the first place.

    To answer the OPs question, the reason to prefer one initialization scheme
    over another mainly comes down to how you are going to use the type. If you don't expect it to be composed with other types, use whatever is comfortable
    (I ususally use a procedute in such cases, and have the type self-initialize
    to "invalid" so it can't be used until a call is made).

    If you need to compose the type with other similar types, it helps to use
    the same initialization mechanism for all (that is, it's easiest to
    initialize discriminants with other enclosing discriminants, and initialize
    via a function from another function).

    Also note that you can combine discriminants with the (automatically called) controlled type procedure Initialize to do more complex initialization based
    on the discriminants. My preference is to make almost all new types
    controlled, not everyone agrees with that.

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Thu May 5 08:56:50 2022
    On 2022-05-05 01:49, Randy Brukardt wrote:

    Those cases that you worry are "unsafe" seem to me to only occur because of "hacks and workarounds". There's no good reason to do any of those things intentionally unless you are using a "hack" to do something dubious in the first place.

    Is a task component a hack? Well so long there is no tagged task types aggregation is the only way.

    Controlled types are hacks, yes, but there is no alternative. You admit
    that all tagged types (and I would say all types) should be controlled = support user-defined initialization hooks.

    Dispatching from Initialize is a double hack, but again, if you need it,
    the alternative, client-side manual initialization is way worse.

    Exception propagation upon initialization? There is no enforceable
    exception contracts to fight it.

    So I say, if hacks occur, then because of the language problems, not
    only because some lazy programmer does something stupid.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From R R@21:1/5 to R R on Thu May 5 02:59:13 2022
    R R schrieb am Mittwoch, 4. Mai 2022 um 11:02:56 UTC+2:
    There are two ways (to my knowledge) how to initialize objects of limited types. Either the limited type has some discriminants

    type DLT (Width, Length : Positive) is tagged limited private;
    Obj : DLT (3, 5);

    or I can provide a constructor function that takes corresponding parameters

    type LT (<>) is tagged limited private;
    function Make (Width, Length : Positive) return LT;
    Obj : LT := Make (3, 5);

    Do you recommend one way over the other? Why? Is it possible to combine both methods (discriminants plus constructor)?

    RREE

    I finally went for the constructor function. A discriminant cannot be an array, at least that's what I understand from GNAT's error message. And I want to initialize the limited object with an array of config parameters.

    Thanks for all the answers.

    RREE

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Thu May 5 20:53:27 2022
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t4vsfi$jtb$1@gioia.aioe.org...
    On 2022-05-05 01:49, Randy Brukardt wrote:

    Those cases that you worry are "unsafe" seem to me to only occur because
    of
    "hacks and workarounds". There's no good reason to do any of those things
    intentionally unless you are using a "hack" to do something dubious in
    the
    first place.

    Is a task component a hack? Well so long there is no tagged task types aggregation is the only way.

    Task objects look like a cool language feature, but use beyond the simplest patterns will get one into a load of trouble (deadlocks, erroneous use of objects, etc.) Almost all of the correct uses of seen are implementations of the "pool of workers" scheme. But you can do that without any explicit tasks
    by using the Ada 2022 parallelism features, and that's a lot safer because
    they include checks against most of the problems. (At least, you can once they're implemented.) Or one could use a library like Brad Moore's to do
    that (letting it handle the tasks).

    So, very much like writing your own basic data structures from scratch, this
    is a feature which is mostly OBE. (Embedded uses are mostly Ravenscar, and
    that doesn't allow dynamically started tasks in the first place.)

    Controlled types are hacks, yes, but there is no alternative. You admit
    that all tagged types (and I would say all types) should be controlled = support user-defined initialization hooks.

    I don't think I said anything about controlled types being hacks.

    Dispatching from Initialize is a double hack, but again, if you need it,
    the alternative, client-side manual initialization is way worse.

    Agree here.

    Exception propagation upon initialization? There is no enforceable
    exception contracts to fight it.

    But you don't (or shouldn't) care what happens to an object when an
    exception is raised during initialization. The program is wrong, it needs to
    be fixed, end of story. On top of that, the language guarentees that all (controlled) objects that are initialized will get finalized (even when exceptions happen), so there is no possible leakage from a well-designed controlled type. So even in the case of a program that must keep running, resources will not be leaked or corrupted. You can handle the exception and retry. What you can't do is use the object that failed for anything, so the fact that you don't know if it was fully initialized is irrelevant.

    So I say, if hacks occur, then because of the language problems, not only because some lazy programmer does something stupid.

    You have a strange idea of language problems. (But of course I've known that for a long while; I've written these responses for the benefit of lurkers
    and the OP, not so much to convince you of anything. :-)

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Fri May 6 10:48:37 2022
    On 2022-05-06 03:53, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t4vsfi$jtb$1@gioia.aioe.org...
    On 2022-05-05 01:49, Randy Brukardt wrote:

    Those cases that you worry are "unsafe" seem to me to only occur because >>> of
    "hacks and workarounds". There's no good reason to do any of those things >>> intentionally unless you are using a "hack" to do something dubious in
    the
    first place.

    Is a task component a hack? Well so long there is no tagged task types
    aggregation is the only way.

    Task objects look like a cool language feature, but use beyond the simplest patterns will get one into a load of trouble (deadlocks, erroneous use of objects, etc.)

    Because the task type is not composable.

    Almost all of the correct uses of seen are implementations of
    the "pool of workers" scheme.

    That is a pretty much trivial case. I am talking about things like implementation of extensible types of active objects.

    So, very much like writing your own basic data structures from scratch, this is a feature which is mostly OBE. (Embedded uses are mostly Ravenscar, and that doesn't allow dynamically started tasks in the first place.)

    Which in many practical cases renders it unusable. Modern embedded
    systems need tasks started upon activation of the system configuration,
    which is no more static. You may not need new tasks during normal
    operation, but you need start and stop tasks in between.

    In general, it is a serious distortion of design considering networking protocols. Though it is possible to implement a protocol session without reserving a task for it, but it would be an extremely unnatural way to
    do. In fact the programmer is forced to re-implement tasking and write
    complex unreadable and unmaintainable state machine code. Lack of
    co-routines for light-weight non-preemptive tasking is yet another
    language problem.

    Controlled types are hacks, yes, but there is no alternative. You admit
    that all tagged types (and I would say all types) should be controlled =
    support user-defined initialization hooks.

    I don't think I said anything about controlled types being hacks.

    Of course they are, because Initialize is not a proper way to do safe
    object construction.

    Exception propagation upon initialization? There is no enforceable
    exception contracts to fight it.

    But you don't (or shouldn't) care what happens to an object when an
    exception is raised during initialization. The program is wrong, it needs to be fixed, end of story.

    That is an awful and unsustainable design, because you consider
    initialization magically separated from construction. It is simply
    impossible to have beyond very simple examples. In a normal program an
    object must be constructed using a set of parameters and this frequently
    a quite complex process should be designed in a structured way. Which in particular means that parts of construction are re-used and may fail.
    You simply cannot have an atomic construction.

    On top of that, the language guarentees that all
    (controlled) objects that are initialized will get finalized (even when exceptions happen),

    Here you contradict yourself.

    You can handle the exception and
    retry.

    This is not the intended use of exceptions. Exception per definition
    indicate a program state that cannot be handled locally.

    What you can't do is use the object that failed for anything, so the
    fact that you don't know if it was fully initialized is irrelevant.

    Nobody ever argued for using half-constructed objects. BTW, one of the
    reasons what Initialize is a hack is because it allows and encourages
    such things.

    The importance of failed construction can be easily seen of the design
    of Ada 83 File_Type. Since Ada 83 library uses initialization never
    fails approach, File_Type must have unusable states, like when the file
    is not open and dozens of operations to deal with these states in the
    client code as well as all sorts of additional errors coming with it as
    a "bonus."

    An alternative design would exclude unopened files but require object construction faults, e.g. when the file does not exist. Having such file
    a component will require construction faults of the container type,
    parameter passing etc.

    Merits of each design is always up to the programmer. The language must
    support him whatever design he pursues, which is unfortunately not the case.

    You have a strange idea of language problems. (But of course I've known that for a long while; I've written these responses for the benefit of lurkers
    and the OP, not so much to convince you of anything. :-)

    Thanks for introducing me! Bowing to the public... (:-))

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Fri May 6 22:26:13 2022
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t52nd4$vj2$1@gioia.aioe.org...
    On 2022-05-06 03:53, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
    news:t4vsfi$jtb$1@gioia.aioe.org...
    On 2022-05-05 01:49, Randy Brukardt wrote:

    Those cases that you worry are "unsafe" seem to me to only occur
    because
    of
    "hacks and workarounds". There's no good reason to do any of those
    things
    intentionally unless you are using a "hack" to do something dubious in >>>> the
    first place.

    Is a task component a hack? Well so long there is no tagged task types
    aggregation is the only way.

    Task objects look like a cool language feature, but use beyond the
    simplest
    patterns will get one into a load of trouble (deadlocks, erroneous use of
    objects, etc.)

    Because the task type is not composable.

    Irrelevant. A task can only be safe if it never interacts with no other
    objects outside of itself. But such a task is close to useless - even if extensible, since no access to globals is possible (such as networking, file systems, etc.). And if there is a use for it, it's easily modeled with
    parallel operations.

    You get deadlocks/races/etc from interacting with global objects of any
    kind. It's not possible to make it safe (some mitigation is of course possible).

    Almost all of the correct uses of seen are implementations of
    the "pool of workers" scheme.

    That is a pretty much trivial case. I am talking about things like implementation of extensible types of active objects.

    Exactly. The idea that everyone wants but simply will not work. At least in
    any sort of imperative language (it might work in a functional language, but it's unclear how one gets to globals like networking in such languages - if
    you can't do that safely, you're going nowhere anyway.).

    So, very much like writing your own basic data structures from scratch,
    this
    is a feature which is mostly OBE. (Embedded uses are mostly Ravenscar,
    and
    that doesn't allow dynamically started tasks in the first place.)

    Which in many practical cases renders it unusable.

    I'm not going to try to defend Ravenscar, I'm just stating a fact here.

    ...
    That is an awful and unsustainable design, because you consider initialization magically separated from construction.

    "Construction" is something that the compiler does (allocating the needed memory for the object). It doesn't have anything to do with the (logical) contents.

    ...
    On top of that, the language guarentees that all
    (controlled) objects that are initialized will get finalized (even when
    exceptions happen),

    Here you contradict yourself.

    No, I'm just pointing out that retrying is safe by the language. You seem to think it is not. Servers need such guarentees to avoid denial-of-service issues.

    You can handle the exception and
    retry.

    This is not the intended use of exceptions. Exception per definition
    indicate a program state that cannot be handled locally.

    "Retry" at a high level. For instance, in the web server, it means trying to process another connection. One doesn't necessary retry the same operation (that doesn't make a ton of sense anyway, since it is likely to fail the
    same way). An exception doesn't necessarily mean the program is failed, only the one operation.

    ...
    The importance of failed construction can be easily seen of the design of
    Ada 83 File_Type. Since Ada 83 library uses initialization never fails approach, File_Type must have unusable states, like when the file is not
    open and dozens of operations to deal with these states in the client code
    as well as all sorts of additional errors coming with it as a "bonus."

    Yup. That's pretty much the only way to design such things.

    An alternative design would exclude unopened files but require object construction faults, e.g. when the file does not exist. Having such file a component will require construction faults of the container type,
    parameter passing etc.

    That makes an interesting thought experiment, but it doesn't work in
    practice. Besides forcing virtually all objects to be allocated (since most objects have to live longer than a single subprogram), it doesn't handle
    cases where outside forces close the object (common in file systems, GUIs, etc.). You still end up with those error states, you've just complicated creating an object for no benefit at all.

    We initially trying designing Claw Window objects that way, but one had to handle the case where the user clicks the 'X' (close button) while a routine
    is working on the window. Windows closes the window almost immediately, and
    the GUI library has to deal with the consequences. (Moreover, if you didn't like Windows do that, you would have a very unresponsive GUI, which wouldn't meet user expectations.)

    Merits of each design is always up to the programmer. The language must support him whatever design he pursues, which is unfortunately not the
    case.

    There's little point in obsessing about designs that only work in academic exercises. Ada tries too hard already to accomadate designs like yours.

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Sat May 7 16:55:56 2022
    On 2022-05-07 05:26, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t52nd4$vj2$1@gioia.aioe.org...

    Because the task type is not composable.

    Irrelevant. A task can only be safe if it never interacts with no other objects outside of itself.

    Not when other objects are tasks, protected objects, active objects,
    atomic objects.

    Exactly. The idea that everyone wants but simply will not work. At least in any sort of imperative language (it might work in a functional language, but it's unclear how one gets to globals like networking in such languages - if you can't do that safely, you're going nowhere anyway.).

    Surely operations can be implemented by entry calls or protected calls.
    The language lacks rather obvious means of delegation to ease that for
    the programmer as well as a sane initialization protocol to be able to
    start and stop a task component or [impossible now] task parent.

    ...
    That is an awful and unsustainable design, because you consider
    initialization magically separated from construction.

    "Construction" is something that the compiler does (allocating the needed memory for the object). It doesn't have anything to do with the (logical) contents.

    Which is simply unacceptable for a language promoting abstract data types.

    ...
    On top of that, the language guarentees that all
    (controlled) objects that are initialized will get finalized (even when
    exceptions happen),

    Here you contradict yourself.

    No, I'm just pointing out that retrying is safe by the language. You seem to think it is not. Servers need such guarentees to avoid denial-of-service issues.

    No, the contradiction is that you claim that safe construction is
    fundamentally impossible and yet concede that it is safe nonetheless.

    You can handle the exception and
    retry.

    This is not the intended use of exceptions. Exception per definition
    indicate a program state that cannot be handled locally.

    "Retry" at a high level. For instance, in the web server, it means trying to process another connection.

    At a very low level, because the language require an object declaration
    to succeed only in the case of a controlled type.

    It does not require that for other types. E.g.:

    type Funny (X, Y : Integer) is record
    Z : Integer := X / Y;
    end record;

    begin
    declare
    F : Funny (A, B);
    begin
    null;
    end;
    exception
    when Constraint_Error =>
    Put_Line ("Sorry");
    end;

    This is just fine, initialization of F may fail with Constraint_Error,
    no need to invoke Program_Error.

    One doesn't necessary retry the same operation
    (that doesn't make a ton of sense anyway, since it is likely to fail the
    same way). An exception doesn't necessarily mean the program is failed, only the one operation.

    One does in the case of controlled types. The retry shall happen in
    Initialize for other types it can happen outside.

    ...
    The importance of failed construction can be easily seen of the design of
    Ada 83 File_Type. Since Ada 83 library uses initialization never fails
    approach, File_Type must have unusable states, like when the file is not
    open and dozens of operations to deal with these states in the client code >> as well as all sorts of additional errors coming with it as a "bonus."

    Yup. That's pretty much the only way to design such things.

    An alternative design would exclude unopened files but require object
    construction faults, e.g. when the file does not exist. Having such file a >> component will require construction faults of the container type,
    parameter passing etc.

    That makes an interesting thought experiment, but it doesn't work in practice.

    Because the language lacks obvious abstractions like user-defined discriminants. Otherwise I see no logical reason why:

    F : File_Type ("My_Ada_file.adb");

    should not work.

    Besides forcing virtually all objects to be allocated (since most
    objects have to live longer than a single subprogram), it doesn't handle cases where outside forces close the object (common in file systems, GUIs, etc.). You still end up with those error states, you've just complicated creating an object for no benefit at all.

    I simplified creating the object by removing states when the object is
    unusable for no other reason than language design. Cases when the file
    can be unreadable because of I/O errors have nothing to do with the case
    when the programmer did not open it.

    We initially trying designing Claw Window objects that way, but one had to handle the case where the user clicks the 'X' (close button) while a routine is working on the window. Windows closes the window almost immediately, and the GUI library has to deal with the consequences.

    Windows sends WM_CLOSE first.
    There's little point in obsessing about designs that only work in academic exercises. Ada tries too hard already to accomadate designs like yours.

    On the contrary, it is a very practical software design problem to
    reduce error sources as much as possible.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Sat May 7 21:32:22 2022
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t5619r$10bc$1@gioia.aioe.org...
    On 2022-05-07 05:26, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
    news:t52nd4$vj2$1@gioia.aioe.org...

    Because the task type is not composable.

    Irrelevant. A task can only be safe if it never interacts with no other
    objects outside of itself.

    Not when other objects are tasks, protected objects, active objects,
    atomic objects.

    That's a common fallacy. Such objects are safe only if there is exactly one
    in your system. If there is more than one, various forms of failure are possible even if everything is supposely safe by itself. To make them fully safe, you have to have strong access ordering (for instance, the onion skin model), which no programming language and probably no static tool can
    enforce. (Proof of safety requires verification that no possible program
    flow can cause a race condition.)

    ...
    No, I'm just pointing out that retrying is safe by the language. You seem
    to
    think it is not. Servers need such guarentees to avoid denial-of-service
    issues.

    No, the contradiction is that you claim that safe construction is fundamentally impossible and yet concede that it is safe nonetheless.

    "Construction" is safe, always has been (it's not a user level thing
    anyway). Initialization is safe unless you use hacks to make it unsafe (but that's sadly common). But the only initialization one should be depending
    upon is quite simple.

    ...
    Because the language lacks obvious abstractions like user-defined discriminants. Otherwise I see no logical reason why:

    F : File_Type ("My_Ada_file.adb");

    should not work.

    It does "work", but such designs put major restrictions on your clients.

    A common way my programs are structured is something like:

    declare
    Output_File : Some_File_Type;
    begin
    Create_or_Open_Output (Output_File);
    Write_Output (Output_File, Data);
    ...
    Close_Output (Output_File);
    end;

    You can't use such a structure with your design, because you can't pass in
    the unopened file object to open it appropriately (which can take multiple attempts depending upon options, file existence, and the like). Another
    design would hide the file object in the package body rather than passing it
    as a parameter. But you can't do that either, since you can't declare the object at library-level in this design (you haven't yet processed the
    program's command line, so you don't know what file to open).

    You have to resort to using access types and allocators to get the right effect. But any ADT design that requires that forces memory management on
    the user (it is necessarily incompatible with letting the language or
    libraries like the containers doing the memory management). Such designs are purely evil. :-)

    Discriminants are only useful for memory management (to size arrays, to make components conditional). Other uses are purely mistakes.

    Besides forcing virtually all objects to be allocated (since most
    objects have to live longer than a single subprogram), it doesn't handle
    cases where outside forces close the object (common in file systems,
    GUIs,
    etc.). You still end up with those error states, you've just complicated
    creating an object for no benefit at all.

    I simplified creating the object by removing states when the object is unusable for no other reason than language design. Cases when the file can
    be unreadable because of I/O errors have nothing to do with the case when
    the programmer did not open it.

    There is no difference between the state of an object before it is opened
    and the one it is in after it is closed. (And these aren't just error cases,
    as noted by the Windows example.)

    We initially trying designing Claw Window objects that way, but one had
    to
    handle the case where the user clicks the 'X' (close button) while a
    routine
    is working on the window. Windows closes the window almost immediately,
    and
    the GUI library has to deal with the consequences.

    Windows sends WM_CLOSE first.

    Sure, but the only thing you can do at that point is make the object
    invalid. You certainly can't make objects that exist and possibly are
    actively being used disappear! And you can't just raise an exception, you
    may not even be operating on that window object at the time.

    There's little point in obsessing about designs that only work in
    academic
    exercises. Ada tries too hard already to accomadate designs like yours.

    On the contrary, it is a very practical software design problem to reduce error sources as much as possible.

    An imaginary reduction of errors, since you have the same states that occur
    in other usage scenarios. And for that, you are preventing the object from being managed by the compiler, or even usefully composed. Stupid.

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Sun May 8 10:37:48 2022
    On 2022-05-08 04:32, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t5619r$10bc$1@gioia.aioe.org...
    On 2022-05-07 05:26, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
    news:t52nd4$vj2$1@gioia.aioe.org...

    Because the task type is not composable.

    Irrelevant. A task can only be safe if it never interacts with no other
    objects outside of itself.

    Not when other objects are tasks, protected objects, active objects,
    atomic objects.

    That's a common fallacy. Such objects are safe only if there is exactly one in your system. If there is more than one, various forms of failure are possible even if everything is supposely safe by itself. To make them fully safe, you have to have strong access ordering (for instance, the onion skin model), which no programming language and probably no static tool can enforce. (Proof of safety requires verification that no possible program
    flow can cause a race condition.)

    You are talking about a very low level and tightly coupled design.
    Higher level object is supposed to prevent that so that there would be
    no need in taking several mutexes or in chains of external entry calls,
    at least not explicitly. So an active object is entirely safe.

    Anyway, we do not discuss safety of using objects, we discuss safety of composing new objects out of existing ones. It would be silly to argue
    that since Positive is unsafe due to existence of the unary minus
    operation, it shall not be used a component of a record type.

    Because the language lacks obvious abstractions like user-defined
    discriminants. Otherwise I see no logical reason why:

    F : File_Type ("My_Ada_file.adb");

    should not work.

    It does "work", but such designs put major restrictions on your clients.

    No, you cannot have this syntax. At best you must use Pickwickian pseudo-functions:

    F : File_Type := Open ("My_Ada_file.adb");

    A common way my programs are structured is something like:

    declare
    Output_File : Some_File_Type;
    begin
    Create_or_Open_Output (Output_File);
    Write_Output (Output_File, Data);
    ...
    Close_Output (Output_File);
    end;

    You can't use such a structure with your design, because you can't pass in the unopened file object to open it appropriately (which can take multiple attempts depending upon options,

    It is surprisingly easy when the type system is used as it should be.
    Just derive a new type from Some_File_Type and provide a new constructor
    for it.

    Note, that your code is already unsafe because nobody knows if Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

    declare
    Output_File : Some_File_Type;
    begin
    Write_Output (Output_File, Data);
    end;

    is 100% safe.

    Discriminants are only useful for memory management (to size arrays, to make components conditional). Other uses are purely mistakes.

    Nope. Discriminant is a parameter, the semantic of must be up to the programmer. The problem with Ada is that it enforces a certain extremely limited implementation of discriminants, so that even that limited use
    you claimed is actually incorrect:

    type X (Size : Natural) is record
    S : String (1..Size + 1); -- Tell me about memory management!
    end record;

    I simplified creating the object by removing states when the object is
    unusable for no other reason than language design. Cases when the file can >> be unreadable because of I/O errors have nothing to do with the case when
    the programmer did not open it.

    There is no difference between the state of an object before it is opened
    and the one it is in after it is closed. (And these aren't just error cases, as noted by the Windows example.)

    Surely, in the design where you deal with open files only, you could not explicitly close one. The object does not have these states. Compare it
    with Ada 83's Standard_Input. You are not supposed to open or close it.

    We initially trying designing Claw Window objects that way, but one had
    to
    handle the case where the user clicks the 'X' (close button) while a
    routine
    is working on the window. Windows closes the window almost immediately,
    and
    the GUI library has to deal with the consequences.

    Windows sends WM_CLOSE first.

    Sure, but the only thing you can do at that point is make the object
    invalid.

    That is crude.

    It depends on the design but normally Windowed GUI objects are allocated
    on the stack and are blocking. So WM_CLOSE should simply make an exit
    from some hidden loop in something like:

    declare
    Dialog : Dialog_Box;
    begin
    Dialog.Run; -- Note, ugliness of lacking constructors again!

    Non-modal stuff is a part of some container and thus WM_CLOSE must go to
    the parent which then explicitly kills the child object. No problem.

    You do not need half-backed objects even in GUI. Such states can be
    hidden in most cases.

    There's little point in obsessing about designs that only work in
    academic
    exercises. Ada tries too hard already to accomadate designs like yours.

    On the contrary, it is a very practical software design problem to reduce
    error sources as much as possible.

    An imaginary reduction of errors, since you have the same states that occur in other usage scenarios.

    No they do not. The goal is to eliminate non-functional states. Things
    like closed file, uninitialized variable, null pointer etc are artifacts
    of the design. There is nothing in the physical world that requires them.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Doctor Who@21:1/5 to mailbox@dmitry-kazakov.de on Sun May 8 19:19:18 2022
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote:

    On 2022-05-08 04:32, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
    news:t5619r$10bc$1@gioia.aioe.org...
    On 2022-05-07 05:26, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message
    news:t52nd4$vj2$1@gioia.aioe.org...

    Because the task type is not composable.

    Irrelevant. A task can only be safe if it never interacts with no other >>>> objects outside of itself.

    Not when other objects are tasks, protected objects, active objects,
    atomic objects.

    That's a common fallacy. Such objects are safe only if there is exactly one >> in your system. If there is more than one, various forms of failure are
    possible even if everything is supposely safe by itself. To make them fully >> safe, you have to have strong access ordering (for instance, the onion skin >> model), which no programming language and probably no static tool can
    enforce. (Proof of safety requires verification that no possible program
    flow can cause a race condition.)

    You are talking about a very low level and tightly coupled design.
    Higher level object is supposed to prevent that so that there would be
    no need in taking several mutexes or in chains of external entry calls,
    at least not explicitly. So an active object is entirely safe.

    Anyway, we do not discuss safety of using objects, we discuss safety of >composing new objects out of existing ones. It would be silly to argue
    that since Positive is unsafe due to existence of the unary minus
    operation, it shall not be used a component of a record type.

    Because the language lacks obvious abstractions like user-defined
    discriminants. Otherwise I see no logical reason why:

    F : File_Type ("My_Ada_file.adb");

    should not work.

    It does "work", but such designs put major restrictions on your clients.

    No, you cannot have this syntax. At best you must use Pickwickian >pseudo-functions:

    F : File_Type := Open ("My_Ada_file.adb");

    A common way my programs are structured is something like:

    declare
    Output_File : Some_File_Type;
    begin
    Create_or_Open_Output (Output_File);
    Write_Output (Output_File, Data);
    ...
    Close_Output (Output_File);
    end;

    You can't use such a structure with your design, because you can't pass in >> the unopened file object to open it appropriately (which can take multiple >> attempts depending upon options,

    It is surprisingly easy when the type system is used as it should be.
    Just derive a new type from Some_File_Type and provide a new constructor
    for it.

    Note, that your code is already unsafe because nobody knows if >Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

    declare
    Output_File : Some_File_Type;
    begin
    Write_Output (Output_File, Data);
    end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.


    Discriminants are only useful for memory management (to size arrays, to make >> components conditional). Other uses are purely mistakes.

    Nope. Discriminant is a parameter, the semantic of must be up to the >programmer. The problem with Ada is that it enforces a certain extremely >limited implementation of discriminants, so that even that limited use
    you claimed is actually incorrect:

    type X (Size : Natural) is record
    S : String (1..Size + 1); -- Tell me about memory management!
    end record;

    I simplified creating the object by removing states when the object is
    unusable for no other reason than language design. Cases when the file can >>> be unreadable because of I/O errors have nothing to do with the case when >>> the programmer did not open it.

    There is no difference between the state of an object before it is opened
    and the one it is in after it is closed. (And these aren't just error cases, >> as noted by the Windows example.)

    Surely, in the design where you deal with open files only, you could not >explicitly close one. The object does not have these states. Compare it
    with Ada 83's Standard_Input. You are not supposed to open or close it.

    We initially trying designing Claw Window objects that way, but one had >>>> to
    handle the case where the user clicks the 'X' (close button) while a
    routine
    is working on the window. Windows closes the window almost immediately, >>>> and
    the GUI library has to deal with the consequences.

    Windows sends WM_CLOSE first.

    Sure, but the only thing you can do at that point is make the object
    invalid.

    That is crude.

    It depends on the design but normally Windowed GUI objects are allocated
    on the stack and are blocking. So WM_CLOSE should simply make an exit
    from some hidden loop in something like:

    declare
    Dialog : Dialog_Box;
    begin
    Dialog.Run; -- Note, ugliness of lacking constructors again!

    Non-modal stuff is a part of some container and thus WM_CLOSE must go to
    the parent which then explicitly kills the child object. No problem.

    You do not need half-backed objects even in GUI. Such states can be
    hidden in most cases.

    There's little point in obsessing about designs that only work in
    academic
    exercises. Ada tries too hard already to accomadate designs like yours. >>>
    On the contrary, it is a very practical software design problem to reduce >>> error sources as much as possible.

    An imaginary reduction of errors, since you have the same states that occur >> in other usage scenarios.

    No they do not. The goal is to eliminate non-functional states. Things
    like closed file, uninitialized variable, null pointer etc are artifacts
    of the design. There is nothing in the physical world that requires them.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Doctor Who on Sun May 8 20:00:15 2022
    On 2022-05-08 19:19, Doctor Who wrote:
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov"

    Note, that your code is already unsafe because nobody knows if
    Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

    declare
    Output_File : Some_File_Type;
    begin
    Write_Output (Output_File, Data);
    end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.

    Upon a write error an exception will propagate and Output_File will go
    out of the scope. The file will be closed and the exception will
    continue its way. This is a safe behavior. Randy's code is unsafe and
    requires catching and re-raising I/O exceptions in the client code.

    This is an illustration why exceptions should be allowed to propagate
    out of Finalize. When Finalize is used to close a handle this may fail
    and there is no way to retry inside Finalize. The choice in Ada is
    between aborting the program or sweeping the problem under the rug
    ignoring the problem. A more sane approach would be to propagate
    exception out of Finalize indicating the problem.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Doctor Who@21:1/5 to mailbox@dmitry-kazakov.de on Sun May 8 20:07:06 2022
    On Sun, 8 May 2022 20:00:15 +0200, "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote:

    On 2022-05-08 19:19, Doctor Who wrote:
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov"

    Note, that your code is already unsafe because nobody knows if
    Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

    declare
    Output_File : Some_File_Type;
    begin
    Write_Output (Output_File, Data);
    end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.

    Upon a write error an exception will propagate and Output_File will go
    out of the scope. The file will be closed and the exception will
    continue its way. This is a safe behavior. Randy's code is unsafe and >requires catching and re-raising I/O exceptions in the client code.

    This is an illustration why exceptions should be allowed to propagate
    out of Finalize. When Finalize is used to close a handle this may fail
    and there is no way to retry inside Finalize. The choice in Ada is
    between aborting the program or sweeping the problem under the rug
    ignoring the problem. A more sane approach would be to propagate
    exception out of Finalize indicating the problem.

    ok

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Niklas Holsti@21:1/5 to Dmitry A. Kazakov on Mon May 9 11:52:37 2022
    On 2022-05-08 21:00, Dmitry A. Kazakov wrote:
    On 2022-05-08 19:19, Doctor Who wrote:
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov"

    Note, that your code is already unsafe because nobody knows if
    Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

        declare
           Output_File : Some_File_Type;
        begin
           Write_Output (Output_File, Data);
        end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.

    Upon a write error an exception will propagate and Output_File will go
    out of the scope. The file will be closed and the exception will
    continue its way. This is a safe behavior. Randy's code is unsafe and requires catching and re-raising I/O exceptions in the client code.

    This is an illustration why exceptions should be allowed to propagate
    out of Finalize. When Finalize is used to close a handle this may fail
    and there is no way to retry inside Finalize. The choice in Ada is
    between aborting the program or sweeping the problem under the rug
    ignoring the problem. A more sane approach would be to propagate
    exception out of Finalize indicating the problem.


    That might work if the scope that is being left has only one local
    object that needs finalization. But what if there are several, and the
    first Finalize propagates an expection? Should the remaining
    finalizations be skipped? That would not be right. But if the remaining finalizations are also attempted, their execution may run into problems
    because the first Finalize was not completed. Moreover, the remaining finalizations might also propagate one or more exceptions, so now there
    could be a whole set of different exception instances being propagated
    from all these finalizations. Which exception(s) should be retained and handled?

    The term "can of worms" comes to mind...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Niklas Holsti on Mon May 9 11:45:33 2022
    On 2022-05-09 10:52, Niklas Holsti wrote:
    On 2022-05-08 21:00, Dmitry A. Kazakov wrote:
    On 2022-05-08 19:19, Doctor Who wrote:
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov"

    Note, that your code is already unsafe because nobody knows if
    Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

        declare
           Output_File : Some_File_Type;
        begin
           Write_Output (Output_File, Data);
        end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.

    Upon a write error an exception will propagate and Output_File will go
    out of the scope. The file will be closed and the exception will
    continue its way. This is a safe behavior. Randy's code is unsafe and
    requires catching and re-raising I/O exceptions in the client code.

    This is an illustration why exceptions should be allowed to propagate
    out of Finalize. When Finalize is used to close a handle this may fail
    and there is no way to retry inside Finalize. The choice in Ada is
    between aborting the program or sweeping the problem under the rug
    ignoring the problem. A more sane approach would be to propagate
    exception out of Finalize indicating the problem.


    That might work if the scope that is being left has only one local
    object that needs finalization. But what if there are several, and the
    first Finalize propagates an expection? Should the remaining
    finalizations be skipped?

    That is another design flaw that Finalize overrides rather than extends.

    That would not be right. But if the remaining
    finalizations are also attempted, their execution may run into problems because the first Finalize was not completed.

    Sure, in a typical program you always have a snowball effect caused by
    an abnormal condition. It is the programmer's duty to sort the things
    out and the language must help.

    What is the alternative, anyway?

    Moreover, the remaining
    finalizations might also propagate one or more exceptions, so now there
    could be a whole set of different exception instances being propagated
    from all these finalizations. Which exception(s) should be retained and handled?

    In Ada the latest exception overrides the previous one. If you are
    asking whether a task should be able to determine if an exception is
    active and have access to its occurrence, my answer would be
    affirmative. One can also consider propagating exception inside the
    parent's finalization hooks, the same way we do with exceptions raised
    within a task rendezvous. One can also consider separate class-wide and type-specific finalization hooks etc.

    The term "can of worms" comes to mind...

    Except that all worms are wiggling outside the can...

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Doctor Who@21:1/5 to mailbox@dmitry-kazakov.de on Mon May 9 12:19:19 2022
    On Sun, 8 May 2022 20:00:15 +0200, "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote:

    On 2022-05-08 19:19, Doctor Who wrote:
    On Sun, 8 May 2022 10:37:48 +0200, "Dmitry A. Kazakov"

    Note, that your code is already unsafe because nobody knows if
    Create_or_Open_Output always opens the file and because there is no
    guarantee that the file is closed, while the design

    declare
    Output_File : Some_File_Type;
    begin
    Write_Output (Output_File, Data);
    end;

    is 100% safe.

    Not in the case that your data space is exhausted, in that case
    Write_Output will fail, because you have no checks of free space
    before writing.

    Upon a write error an exception will propagate and Output_File will go
    out of the scope. The file will be closed and the exception will
    continue its way. This is a safe behavior. Randy's code is unsafe and >requires catching and re-raising I/O exceptions in the client code.

    This is an illustration why exceptions should be allowed to propagate
    out of Finalize. When Finalize is used to close a handle this may fail
    and there is no way to retry inside Finalize. The choice in Ada is
    between aborting the program or sweeping the problem under the rug
    ignoring the problem. A more sane approach would be to propagate
    exception out of Finalize indicating the problem.

    if you do a check before write there is no need to rise and propagate
    an exception.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Doctor Who on Mon May 9 13:15:54 2022
    On 2022-05-09 12:19, Doctor Who wrote:

    if you do a check before write there is no need to rise and propagate
    an exception.

    A bad idea.

    First it is an unnecessary overhead, because ultimately the check will
    be repeated.

    Secondly it is technically impossible to do for a huge number of reasons:

    1. Too complex to do. The modern hardware and software does a lot of bookkeeping, indexing, replicating, compression, encryption, the stuff
    almost impossible to estimate or predict in advance.

    2. Unreliable due to racing conditions, volatile network states etc.

    3. Very expensive, e.g. in the case of a networking file system or a
    database and the asynchronous nature of modern I/O subsystems. When you
    write a file you normally do not wait for the operation completion.
    Actual writing continues asynchronously to your application going
    through dozens of stacks, caches, buffers, buses. If that fails you will
    learn that later in a consequent operation unless you explicitly call
    Flush or equivalent. Checking the state is a synchronous operation that
    would have hundred- to thousandfold performance penalty.

    4. Just impossible like when dealing with a stream, a pipe line etc.

    As general design rules regarding exceptions consider:

    1. Never use return code.

    2. Never check anything non-trivial in advance.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Doctor Who@21:1/5 to mailbox@dmitry-kazakov.de on Mon May 9 14:05:09 2022
    On Mon, 9 May 2022 13:15:54 +0200, "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote:

    On 2022-05-09 12:19, Doctor Who wrote:

    if you do a check before write there is no need to rise and propagate
    an exception.

    A bad idea.

    First it is an unnecessary overhead, because ultimately the check will
    be repeated.

    Secondly it is technically impossible to do for a huge number of reasons:

    1. Too complex to do. The modern hardware and software does a lot of >bookkeeping, indexing, replicating, compression, encryption, the stuff
    almost impossible to estimate or predict in advance.

    2. Unreliable due to racing conditions, volatile network states etc.

    3. Very expensive, e.g. in the case of a networking file system or a
    database and the asynchronous nature of modern I/O subsystems. When you
    write a file you normally do not wait for the operation completion.
    Actual writing continues asynchronously to your application going
    through dozens of stacks, caches, buffers, buses. If that fails you will >learn that later in a consequent operation unless you explicitly call
    Flush or equivalent. Checking the state is a synchronous operation that
    would have hundred- to thousandfold performance penalty.

    4. Just impossible like when dealing with a stream, a pipe line etc.

    As general design rules regarding exceptions consider:

    1. Never use return code.

    2. Never check anything non-trivial in advance.


    we are talking about files, in a local disk right?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Doctor Who on Mon May 9 14:31:33 2022
    On 2022-05-09 14:05, Doctor Who wrote:
    On Mon, 9 May 2022 13:15:54 +0200, "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote:

    On 2022-05-09 12:19, Doctor Who wrote:

    if you do a check before write there is no need to rise and propagate
    an exception.

    A bad idea.

    First it is an unnecessary overhead, because ultimately the check will
    be repeated.

    Secondly it is technically impossible to do for a huge number of reasons:

    1. Too complex to do. The modern hardware and software does a lot of
    bookkeeping, indexing, replicating, compression, encryption, the stuff
    almost impossible to estimate or predict in advance.

    2. Unreliable due to racing conditions, volatile network states etc.

    3. Very expensive, e.g. in the case of a networking file system or a
    database and the asynchronous nature of modern I/O subsystems. When you
    write a file you normally do not wait for the operation completion.
    Actual writing continues asynchronously to your application going
    through dozens of stacks, caches, buffers, buses. If that fails you will
    learn that later in a consequent operation unless you explicitly call
    Flush or equivalent. Checking the state is a synchronous operation that
    would have hundred- to thousandfold performance penalty.

    4. Just impossible like when dealing with a stream, a pipe line etc.

    As general design rules regarding exceptions consider:

    1. Never use return code.

    2. Never check anything non-trivial in advance.

    we are talking about files, in a local disk right?

    Yes, all this fully applies to local disks and filesystems.

    Just look how writing an SSD works. I think you could get better chances
    with magnetic drums from 60's. (:-))

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to Dmitry A. Kazakov on Mon May 9 23:48:35 2022
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t57vgs$4su$1@gioia.aioe.org...
    ...
    No they do not. The goal is to eliminate non-functional states. Things
    like closed file, uninitialized variable, null pointer etc are artifacts
    of the design. There is nothing in the physical world that requires them.

    Most such things are artifacts of the target system. Closed files and closed windows happen because of events that occur outside of your program. You
    can't design them away.

    Randy.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Randy Brukardt on Tue May 10 08:18:21 2022
    On 2022-05-10 06:48, Randy Brukardt wrote:
    "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message news:t57vgs$4su$1@gioia.aioe.org...
    ...
    No they do not. The goal is to eliminate non-functional states. Things
    like closed file, uninitialized variable, null pointer etc are artifacts
    of the design. There is nothing in the physical world that requires them.

    Most such things are artifacts of the target system. Closed files and closed windows happen because of events that occur outside of your program. You can't design them away.

    It is a totally unrealistic scenario. If there exist a way to close a
    handle from outside then only in the system kernel.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)