Menu ▾ ▴

#880 Add a .LogicalWrapper class

5.3.0
pending
None
doc
1
2026-10-20
2026-10-17
No

Target: ooRexx 5.3 trunk (patches made against r13265; the files involved
are identical in r13271).
Component: Interpreter, interpreter/RexxClasses/CoreClasses.orx; the
json and yaml extensions; the test suite.
Name: .Boolean in the patches; .Logical would do as well, and fits
Rexx's own vocabulary ("logical value"). The choice is left open.

The problem

In Rexx, .true and .false are the strings "1" and "0". That is right
for Rexx, and nothing here proposes to change it. But it means that no code can
tell .false from 0, or .true from 1 by its value.

Some code must, though: anything that writes a format or talks to a language
with a boolean type. Each such package has invented the same answer, an object
that behaves as "1" or "0" everywhere and can still be recognized:

  • json.cls has JsonBoolean (.json~true, .json~false), so that
    .json~toJSON writes true/false and not 1/0.
  • yaml.cls has YamlBoolean. Its documentation says it follows "the same
    pattern as JsonBoolean in json.cls". The code is a copy: about 80 lines
    of logic, the same in both but for the serializer method (makeJSON,
    makeYAML) and small differences that have already crept in (the 88.900
    message, how = is computed).
  • Bridges to other languages need the same thing to pass a real boolean to the
    host. Today each has its own, with its own names.

So there are several copies of one idea, and one already has a bug in common:
\.json~true and \.YamlBoolean~true both give 1, because their UNKNOWN
forwards the message "\" to .true, and that hits the bug reported
separately (RexxInteger::notOp).

The proposal

One class in the core, with what JsonBoolean and YamlBoolean share:

  • Two instances per class, created at activation: .Boolean~true and
    .Boolean~false. NEW is private.
  • They behave as the Rexx logical values "1" and "0" everywhere: IF,
    WHEN, SELECT, the logical operators, DATATYPE(x, "O"), string methods,
    SAY, concatenation (makeString, string, and UNKNOWN forwarding to the
    value).
  • isA(.Boolean) tells them apart from plain strings.
  • Comparisons: =, \=, <>, >< compare logical values (Comparable,
    compareTo), across classes (.json~true = .Boolean~true is true).
  • value returns "1" or "0".
  • New: .Boolean~of(logical) returns the instance for a logical value
    (.Boolean~of(a > b)), and raises 34.900 for anything that is not 0 or 1.
  • Logical NOT is defined explicitly ("\"), so that the class does not depend
    on the forwarding path.
  • hashCode follows the value, so an instance works as a Table index.
  • A subclass gets its own two instances (activate runs for it). So
    JsonBoolean becomes:
::class "JSONBoolean" public subclass Boolean
::method makeJSON unguarded
  if self~value then return "true"
  return "false"

and YamlBoolean the same with makeYAML. Their public behaviour does not
change: .json~true, .JsonBoolean~true, isA(.JsonBoolean), makeJSON and
the YAML equivalents all stay. What changes is that both are now also
isA(.Boolean), and that \.json~true gives 0.

What it makes possible

A package that needs a boolean (a writer, a database binding, a bridge) checks
isA(.Boolean), and works with JSON's and YAML's values, and any future ones,
without knowing about JSON or YAML. A true read by json~fromJSON can go
straight to Python, .NET, Lua or JavaScript as that language's true.

Compatibility

  • .true and .false do not change.
  • JsonBoolean and YamlBoolean keep their names, class attributes and
    serializer methods. Two behaviours change: \ (it was wrong), and ==
    (.json~true == "1" gives 0 today, identity; 1 with the patch). See
    open question 2.
  • A program that defines its own class named Boolean (or Logical) is
    unaffected: its own class takes precedence over the core one, as with any
    core class (checked: such a program still gets its own .Boolean, while
    .json~true is an instance of the core one).
  • The test suite needs one change: MethodArgs.testGroup creates an instance
    of every public core class with NEW, so .Boolean goes into its list of
    special cases (as .Singleton and .StackFrame already are), with its two
    instances added explicitly.

Open questions

  1. The name: .Boolean or .Logical.
  2. == and \==: the patch makes == a strict comparison of the values as
    strings (.Boolean~true == "1" is true), as for a string. JsonBoolean
    and YamlBoolean do not define == today, so for them it is identity.
    String-like seems more consistent with "behaves as "1"", but it can be
    argued either way. (Identity is always available through identityHash.)
  3. Whether ~of is the right name for the factory method.

Patches and tests

  • boolean-interpreter.patch: the class in CoreClasses.orx; json.cls and
    yaml.cls reduced to subclasses.
  • boolean-tests.patch: ooRexx/base/class/Boolean.testGroup (new, 11 tests)
    and the MethodArgs.testGroup change.
  • boolean.cls: the same class as a standalone package, for use until (or
    instead of) the core.

Results, built from the r13265 source tarball on Linux x86_64 (gcc 13), with
this patch and the one for RexxInteger::notOp:

Tests Failures Errors
ooRexx/base, r13271 unpatched 14369 5 0
ooRexx/base, patched 14380 (the 11 new ones included) the same 5 0
json.testGroup, json_02.testGroup, yaml.testGroup, patched 314 0 0

The 5 failures are the same tests in both runs (File.testGroup and
Stream.testGroup, file permissions: the suite ran as root in a container).
Without the MethodArgs.testGroup change, that group fails to load
("Object "The Boolean class" cannot accept private message "NEW"").

3 Attachments

Discussion

1 2 > >> (Page 1 of 2)
  • Josep Maria Blasco

    One more consequence, which I think strengthens the proposal.

    json.cls and yaml.cls recognize booleans by their own classes:
    json.cls through the makeJSON method (emitValue), and through
    isA(.JsonBoolean) in its XML output; yaml.cls through
    isA(.YamlBoolean) (in the scalar emitter and in scalarType). So with the
    patch as it is, a plain .Boolean~true is written as 1 by both: it is a
    logical value, but not their logical value.

    If the writers check isA(.Boolean) instead, any logical value that knows it
    is one is written as true/false, whoever created it: JSON's, YAML's, or a
    bridge's. For example, a Python True that arrives in Rexx as
    .Boolean~true would go out to JSON as true with no extra code. That is the
    point of having one base class instead of one class per package.

    The change is four lines, attached as boolean-writers.patch, on top of
    boolean-interpreter.patch:

     json.cls, emitValue:
    
    +        when rexxObject~isA(.Boolean) then do
    +            -- any logical value that knows it is one (.JsonBoolean, a bridge's, ...)
    +            if rexxObject~value then buffer~append('true')
    +            else buffer~append('false')
    +        end
             when rexxObject~hasMethod('makejson') then do
    
     json.cls, XML output:
    
    -  if obj~isA(.JsonBoolean) then do
    +  if obj~isA(.Boolean) then do
    
     yaml.cls:
    
    -  if value~isA(.YamlBoolean) then return value~makeYAML
    +  if value~isA(.Boolean) then return .YamlBoolean~of(value)~makeYAML
    -  if value~isA(.YamlBoolean) then return "bool"
    +  if value~isA(.Boolean) then return "bool"
    

    Result, on the patched build:

    a = .array~of(.Boolean~true, .Boolean~false, .json~true, 1)
    say .json~toJSON(a)     -- [true,false,true,1]   (without this change: [1,0,true,1])
    say .yaml~toYAML(a)     -- - true / - false / - true / - 1
    

    json.testGroup, json_02.testGroup and yaml.testGroup: 314 tests, 0
    failures, 0 errors.

     
  • Rony G. Flatscher

    On 17.10.2026 09:41, Josep Maria Blasco wrote:

    I've just uploaded RFE #880 (https://sourceforge.net/p/oorexx/feature-requests/880/) to SourceForge.
    Yaml.cls defines a YamlBoolean class, and json.cls defines an almost identical JsonBoolean class.
    The new bridges that are being written will also need wrapper classes -- indeed, any language
    that defines a boolean type will need a Rexx wrapper if Rexx has to interface with it.

    Indeed. However, these are problems of the other language which define explicit/strict boolean types! Therefore an ooRexx bindings all of a sudden must distinguish between a JSON/YAML string with the value "0" string from a JSON/YAML false and a JSON/YAML string with the value "1" from a JSON/YAML true. This becomes significant when creating string renderings that get stored e.g. in a file and get read from a file. So, if ooRexx reads a JSON/YAML file and wants to reproduce it, it must remember, wether 0 was a JSON/YAML string or a JSON/YAML boolean. That is the reason, why the bridge needs to define a JSONBoolean or a YAMLBoolean class! This is not because of ooRexx lacking a Boolean class! The JSON implementation for JSONBoolen (I made sure that the YAMLBoolean is based on that) that I did made sure that even if on the ooRexx side JSONBooleans (or YAMLBooleans) appear, that these values can be used as plain ooRexx strings "0" and "1", such that the context on the ooRexx side determines whether the value gets used as a Rexx string or interpreted as a Boolean by the oo/Rexx context. (Whether a string value is to be used as a Boolean depends on the context, on the use of these string values, e.g. in the context of an "IF booleanValue THEN ..." or "WHEN booleanValue THE..." keyword statement or in the context of a &, or && concatenation where its operands must be Boolean values, i.e. the strings "0" or "1" in oo/Rexx.)

    It is the same as with arithmetics: in Rexx/ooRexx you can do arithmetics if the Rexx strings contain numbers, otherwise you will get an error at runtime, if you used strings for arithmetics that are not numbers. Because of that oo/Rexx is very easy. Now, if interacting with other languages that are strictly typed, depending again on the context, you may have to translate strings containing numbers to the target strict type like Byte, Char, Integer, Long, Float, Double, maybe - as in the case of .NET, COM/OLE - even to unsigned integer values, etc.

    It may be tempting for strictly typed aficionados to therefore introduce these strict typings into the oo/Rexx language (unnecessarily) by defining all these strict types of other languages also for ooRexx. But then you lose the easiness and simplicity of oo/Rexx that makes it so easy to use and to understand. [E.g., I could not teach object-oriented programming in such a short time anymore, because then you need to first teach the typing system to beginners, who hardly can understand the need for it.]

    For bridges to other languages, numbers therefore must be identifiable as numbers such that the bridge becomes able to translate strings with numbers to the target's language matching strict type. Of course it would simplify the bridge implementation (which needs to be done just once, but imply the burden then to the manyfold usages, i.e., to the oo/Rexx programmers), if we started to impose on the ooRexx language the same strict type system as the target language, but for the price to all of a sudden change the soul (and incredible dynamics) of the language into a "dull" ;) strictly typed language.

    However, that is the gist of a bridge, and the challenge in the Rexx/ooRexx world: shelter the Rexx/ooRexx programmer from the complexities of the partner language as much as possible. Do not introduce unneeded concepts into the Rexx/ooRexx language that other languages (which copied the concepts from its common ancestors) impose on their users.

    The ooRexx-Java bridge was developed for more than 25 years with the goal, to not force any Java concepts onto the Rexx/ooRexx language, and still be able to exploit all of Java. I think I have achieved that, making the bridge easy to use for Rexx and ooRexx programmers.
    So, yes, for all the other bridges (.NET, Pythong, etc.), if those languages rely on strictly typed Booleans, the bridge's support needs to create a NetBoolean, a PythonBoolean, etc. (and they should carry the name of the language, such that one can intermix all these bridges with each other).

    The RFE proposes to define a .Boolean (or maybe .Logical) base class. As YamlBoolean
    and JsonBoolean do now, it would only have two instances, .Boolean~true and .Boolean~false.
    .Boolean~true would be undistinguishable from 1, except that it would return .True when
    asked ~isA(.Boolean), and likewise for .Boolean~false.

    Well, in oo/Rexx one does not need an explicit class, which always adds a little burden to a language, but one can use the DATATYPE()-BIF with the lOgical argument: it will return .true, if the value is a Boolean, and .false, if not.

    Opinions? In particular, what do you prefer as the class name, Boolean or Logical?

    Well the second question already implies that a Boolean class gets introduced into the ooRexx language. Again, I can understand how one arrives at this impression and hope to have added a few more views onto what a bridge should do (insulating oo/Rexx from, but making oo/rexx fully ineroperable with the language on the other side of the bridge).

    Following one of the Rexx design principles - "keep the language small" - I would not introduce a new class (or any new feature), unless it is really mandatory (a new technical concept must be supported, an important new feature needs to be supported as easy as possible for the oo/Rexx programmer) or helping simplifying a common problem for the ooRexx programmers.

    ---rony

     
  • Rony G. Flatscher

    Ad missing features/errors in the implemenation of JSONBoolean: this can be fixed in the JSONBoolean class, thereby leaving ooRexx unaffected.

    One way to still get your idea for bridges: how about defining a package for all bridges that abstracts the common problems with strictly typed languages and tackling it there. So, one could define a Boolean class there and use it for any bridge that has a need for it?

    If this was possible/conceivable, this would leave ooRexx unaffected.

     
    • Josep Maria Blasco

      There are no errors in JsonBoolean. Incorrect behaviour is a consequence of https://sourceforge.net/p/oorexx/bugs/2117/ -- it's an interprete bug, not a problem with json.cls.

      Your idea of abstracting is exactly what we are doing now: boolean.cls is common to all the bridges.

       
  • Rony G. Flatscher

    Hmm, the question is, does it any good to ooRexx programmers, if they get an .Boolean class in ooRexx? Or with other words, are there other use cases (besides the need for the bridges which is well understood) that benefit ooRexx programmers?

    The mindset about this stance is: "keep the language small".


    Hmm, I would see one benefit that you show: classes that get distributed with ooRexx could take advantage of a Boolean class, i.e. the JSON and YAML classes, independent of any bridges. So, if we introduce a Boolean class as you propose to make these two ooRexx classes more bullet proof in a simple manner, then any bridges would be able to take advantage of it. (And adding a class would not change the language per se, just its class library gets one class more.)

    Opinions?

    ---rony

     
  • Josep Maria Blasco

    Reworked as discussed on oorexx-devel (17/10/2026): Boolean is now an extension class distributed with ooRexx, next to json.cls, not a core class. These attachments replace the earlier ones (boolean-interpreter.patch, boolean-tests.patch, boolean-writers.patch).

    boolean-extension.patch (against main/trunk r13273):

    • extensions/boolean/boolean.cls (new): the class Boolean, with the two instances .Boolean~true and .Boolean~false, which behave as "1" and "0" in every Rexx context; .Boolean~of(logical); comparisons through Comparable; defaultName returns the value, so that an Array shows 1/0 in makeString and toString.
    • CMakeLists.txt: copies and installs boolean.cls like json.cls.
    • json.cls: ::requires "boolean.cls"; JSONBoolean becomes a subclass of Boolean that keeps its own makeJSON; the writer checks isA(.Boolean), so it writes any Boolean as true/false.
    • yaml.cls: the same, with makeYAML; the class name is now quoted, "YamlBoolean", to keep its mixed case (Rony's suggestion).

    Public behaviour of .json~true, .JsonBoolean~true, .YamlBoolean~true, makeJSON and makeYAML does not change.

    boolean-extension-tests.patch (against test/trunk): ooRexx/extensions/boolean/Boolean.testGroup, 16 tests (the class, plus JSON and YAML interoperating through it).

    Results, built from the r13265 source tarball plus r13272 (bug #2117) and this patch, Linux x86_64:

    Tests Failures Errors
    Boolean.testGroup, json.testGroup, json_02.testGroup, yaml.testGroup 330 0 0
    ooRexx/base 14371 5 (file permissions: the suite ran as root in a container; the same without the patch) 0

    In one full run, Message.testGroup's TEST_NOTIFY also failed once (expected 5, got 4); it passed three times out of three when rerun, with and without the patch, so it looks like a timing-dependent test.

    No change is needed in the core or in MethodArgs.testGroup.

     
  • Josep Maria Blasco

    • summary: Add a .Boolean (or .Logical) base class --> Add a .Boolean (or .Logical) class
    • assigned_to: Josep Maria Blasco
     
  • Gil Barmwater

    Gil Barmwater - 2026-10-17

    Question (maybe answered above but missed): .True and .False are "special" in that ooRexx initializes them in .Environment to 1 and 0 respectively. If I code a = .true; say a~datatype("O") I get 1. What doI get (assuming we name the class .Logical) if I code .true~isA(.Logical)? To be consistent I would expect to also get 1.

     
    • Josep Maria Blasco

      I'm not sure we want to do that, by the reasons outlined above by Rony. Rexx is an untyped language, and by proposing a new Logical class we are not trying to bring typing into Rexx. Logical is a utility class that allows us to flag 0 or 1 as logical values for foreign languages or environments where this is necessary. OTOH, .True is undistinguishable from 1. Incidentally, .True, .False and .Nil are no longer .environment entries; there are entries there, but they are fake. Try

      .environment~true = 0; say .true .environment~true
      

      and you will get 1 0. The situation you describe was true of Object Rexx, though (checked in ArcaOS).

       
      • Rony G. Flatscher

        .true, .false, and .nil have become "constant environment symbols", probably to speed up ooRexx as therefore these environment symbols do not have to be looked up anymore. This was done without communication, AFAIK, for 5.0.0. The leftovers are the entries in .local, which until then were looked up. See rexxref.pdf, 1.13.6. Environment Symbols.

         
      • Gil Barmwater

        Gil Barmwater - 2026-10-18

        Yes, I understand but by introducing the class we may also introduce confusion for the user. Is there a reason the new class has to be visible to the user? ooRexx has a "Numeric" class "under the covers" and not visible to the user. Could this new class be done the same way? (Just wanting to explore all alternatives :-) ).

         
        • Josep Maria Blasco

          My impression is that the class should be public, but maybe it should have a more technical name, like LogicalWrapper. Its description should say that this is a technical class, used to interface with other languages, frameworks, etc., that need to discriminate the logical 1 from the numeric 1. Its first clients will be json.cls and yaml.cls, but immediately the WASM port, and the bridges Rexx/Lua, Rexx/Python and Rexx/.Net.

           
        • Rony G. Flatscher

          Yes, under the covers for the interpreter itself, hence not publically available.
          This class needs to be public in order to be usable by other ooRexx programmers, whoever they are. It simplifies a tricky problem considerably, making sure thereby that the error-proneness (maybe overlooking the potential problems) gets reduced.

          Ad name: "Boolean" is what is common in the current main stream languages. "Logical" is very Rexx-ish and would not draw the attention of newcomers to that class.

          BTW, once that class exists it may also be an option to use it for ooRexx itself wherever logical values get created (e.g. result of comparisons).

           
          • Josep Maria Blasco

            Thank you, Rony. I agree that it has to be public, and I take note of your preference for Boolean; Gil, what do you think?

            About using it for the results of comparisons: I would rather not. Today a = b returns the same object as .true, a string, and existing code relies on that, sometimes without knowing it: (a = b)~isA(.String) is 1. If comparisons returned Booleans, those programs would change behaviour silently. It would also bring typing into the core of the language, which is what you warned against. The idea is the opposite: an ordinary logical value stays the string “1” or “0”, and a program uses the class only when it needs to say “this 1 is a logical value” to JSON, YAML or another language.

             
  • Gil Barmwater

    Gil Barmwater - 2026-10-18

    Yes, it appears that it needs to be a public class so we need to be sure that the documentation is very clear as to its pupose. I still believe that .Logical or .LogicalWrapper (or some other variation) is better than Boolean. Rexx simply does not use that term (it only appears twice in RexxRef, one of which is in the Index(!) and not that much in the PG). Logical, however, is very common in the Rexx documentation and is well understood by the community. I, also, would NOT support its use outside of its intended purpose as it would lead to a whole host of knotty issues (like why isn't .tue/.false an instance of .Logical, etc.).

     
    • Josep Maria Blasco

      That's why I proposed LogicalWrapper: it says exactly what it is. Regarding Boolean/Logical, I don't have a strong opinion (so BooleanWrapper would also be fine for me). But I'm slightly inclined to Logical -- because we are wrapping one of our values, which is a logical value. Then the subclasses prepare that for the foreign treatment, and they can be Boolean -- because the foreign environment speaks of Booleans.

      That way, the frontier is clearer.

       
      • Rony G. Flatscher

        Indeed LogicalWrapper carries both pieces of information, it is about a logical Rexx value and a wrapper for others.
        +1

         
        • Josep Maria Blasco

          Great. To summarize; LogicalWrapper, and a public class. I will leave the RFE open some few days more and, if there is no further feedback, I'll proceed to implement it.

           
  • Gil Barmwater

    Gil Barmwater - 2026-10-18

    Sounds good to me.
    +1

     
  • Josep Maria Blasco

    • status: unread --> open
     
  • Josep Maria Blasco

    • summary: Add a .Boolean (or .Logical) class --> Add a .LogicalWrapper class
     
  • Erich

    Erich - 2026-10-19

    | no code can tell .false from 0, or .true from 1.

    It is trivial to distinguish .true from '1' or similar forms:

    do b over .true, 1, 1.0, '1'
      say .true~identityHash == b~identityHash
    end
    
     
    • Josep Maria Blasco

      Thank you, Erich, you are right: the sentence is too strong. Identity does tell them apart. I’ll reword that sentence in the description: “no code can tell .false from 0 by its value”.

       
  • Josep Maria Blasco

    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -9,7 +9,7 @@
    
     In Rexx, `.true` and `.false` are the strings `&#34;1&#34;` and `&#34;0&#34;`. That is right
     for Rexx, and nothing here proposes to change it. But it means that no code can
    -tell `.false` from `0`, or `.true` from `1`.
    +tell `.false` from `0`, or `.true` from `1` by its value.
    
     Some code must, though: anything that writes a format or talks to a language
     with a boolean type. Each such package has invented the same answer, an object
    
     
  • Josep Maria Blasco

    Implemented as agreed, in main/trunk r13276-r13278 and test/trunk r13279.

    LogicalWrapper is an extension class, distributed with ooRexx next to
    json.cls: extensions/logicalwrapper/logicalwrapper.cls, available with
    ::requires "logicalwrapper.cls". Its two instances, .LogicalWrapper~true
    and .LogicalWrapper~false, behave as "1" and "0" in every Rexx context,
    and isA(.LogicalWrapper) tells them apart from plain strings.
    .LogicalWrapper~of(logical) returns the instance for a logical value.

    JSONBoolean (json.cls) and YamlBoolean (yaml.cls) are now its subclasses.
    They keep their names, their makeJSON / makeYAML, and their public
    behaviour; the JSON writer now writes any LogicalWrapper as true/false.

    r13276: the class, and its build and install lines
    r13277: json.cls
    r13278: yaml.cls
    r13279: ooRexx/extensions/logicalwrapper/LogicalWrapper.testGroup, 16 tests

    Thanks to Rony, Gil and Erich for the discussion and the name.

     
1 2 > >> (Page 1 of 2)

Anonymous
Anonymous

Add attachments
Cancel