Skip to content

MCP-0034 Ternary #2477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 23 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3696f8b
Initial commit of files on MCP branch
henrikt-ma Nov 22, 2019
b7e8ce4
Add pseudo code definition of Ternary
henrikt-ma Nov 22, 2019
38b433e
Define Ternary relations by means of total order, making Ternary usef…
henrikt-ma Nov 22, 2019
7933c90
Mention the Boolean option type alternative in the rationale
henrikt-ma Nov 24, 2019
4f05aa8
Elaborate on the interpretation of 'none' as either 'unknown' or 'und…
henrikt-ma Dec 5, 2019
3715b9b
Fix incorrect markdown for issue link
henrikt-ma Dec 17, 2019
d2b42b0
Tidy up issue link
henrikt-ma Dec 17, 2019
fa8fa14
Updates after gaining experience with the prototype
henrikt-ma Feb 9, 2020
a5164b5
Reuse 'none' instead of introducing 'neither' when giving example of …
henrikt-ma Feb 9, 2020
e99fa56
Fix typo
henrikt-ma Jun 12, 2020
f27fb06
Fix language
henrikt-ma Jun 12, 2020
5d62e4f
Fix typo
henrikt-ma Jun 10, 2021
50b985e
Fix error in example of Ternary ==
henrikt-ma Jun 10, 2021
379c0bc
Logical connectives are defined _in accordance with_ Kleene
henrikt-ma Jun 21, 2021
0aa42c1
Clarify that Ternary is not Kleene
henrikt-ma Jun 21, 2021
cf846b7
Clarify uniqueness in the external language mapping of values
henrikt-ma Oct 12, 2021
a59d27e
Merge remote-tracking branch 'central/master' into MCP/0034
henrikt-ma Oct 12, 2021
a44062a
Mention collection of examples provided in separate repository
henrikt-ma Oct 25, 2021
397ee9c
Merge remote-tracking branch 'central/MCP/0034' into MCP/0034+ternary…
henrikt-ma Oct 25, 2021
b62a65f
Merge pull request #2958 from modelica/MCP/0034+ternary-is-not-kleene
christoff-buerger Oct 29, 2021
dd4a298
Change external language mapping of Ternary to int
henrikt-ma Mar 14, 2023
1d896a3
Change 'unknown' from keyword to top-level constant
henrikt-ma Mar 24, 2023
647a2c8
Add another line to the revision history
henrikt-ma Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions RationaleMCP/0034/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Modelica Change Proposal MCP-0034<br/>Ternary
Henrik Tidefelt

**(In Development)**

## Summary
It was concluded in [#2211](https://github.com/modelica/ModelicaSpecification/issues/2211) that an MCP for _Ternary_ (3-valued logic) in Modelica should be created, and here it is.

The proposal is based on the following principles:
- A new built-in type, `Ternary`.
- A new literal constant `unknown` for the third truth value.
- Explicit as well as implicit conversion from `Boolean`.
- No implicit conversion to `Boolean`.
- No new built-in functions.

See [design](ternary.md) for details.

## Revisions
| Date | Description |
| --- | --- |
| 2019-11-22 | Henrik Tidefelt. Filling this document with initial content. |

## Contributor License Agreement
All authors of this MCP or their organizations have signed the "Modelica Contributor License Agreement".

## Rationale
See [separate document](rationale.md).

## Backwards Compatibility
The introduction of the keyword `unknown` introduces a backwards incompatibility with code making use of that name for identifiers.

## Tool Implementation
A prototype has been implemented in a development version of Wolfram SystemModeler. In the prototype, the type is named `__Wolfram_Ternary`, and the third truth value is named `__Wolfram_unknown`.

### Experience with Prototype
Although introducing a new built-in type is a change that ammounts to a large number of smaller changes, finding the places in a code base that need attention is easy due to the similarity between `Ternary` and `Boolean`. In a similar way, the implicit conversion from `Boolean` to `Ternary` is a feature that can be implemented by glancing at the handling of implicit conversion from `Integer` to `Real`.

Regarding the application of this MCP to a new attribute called `visible` in the `Dialog` annotation, the proposed choice of Kleene logic certainly gets the job done. The default value of `unknown` which means _apply tool-specific rules for visibility_, a user can easily write a ternary logical expression to override the default in either way, and the expression may also evaluate to `unknown` in cases where the user doesn't want to fall back on the tool logic. Thanks to the implicit cast from `Boolean`, most uses of `visible` would probably be in the simple forms `visible = true` or `visible = false`. However, upon closer acquaintance with the Kleene logic, which still appears as the most natural choice for `Ternary`, it has been questioned whether this is the best way to model the absence of information, see [rationale](rationale.md#The-option-type-alternative).

## Required Patents
To the best of our knowledge, there are no patents that would conflict with the incorporation of this MCP.

## References
- Wikipedia, _Three-valued logic_, https://en.wikipedia.org/wiki/Three-valued_logic
- Wikipedia, _Four-valued logic_, https://en.wikipedia.org/wiki/Four-valued_logic
107 changes: 107 additions & 0 deletions RationaleMCP/0034/rationale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Background
For those who don't want to read through #2211 to get the background, the application of ternary logic there is to have a `Dialog` annotation member called `visible`. The problem with using `Boolean` is that as soon as an expression is given, it is no longer possible to say _default behavior_ (which is neither `true` nor `false`, but depends on tool-specific logic). With a third truth value, _unknown_, it becomes possible to give an expression for selecting between:
- `false` — force hiding
- `unknown` — follow tool-specific logic (same as not specifying `visible` at all)
- `true` — force showing

# Rationale
The principles on which this MCP is base were listed on the [entry page](ReadMe.md), and are repeated here for convenience:
- A new built-in type, `Ternary`.
- A new literal constant `unknown` for the third truth value.
- Explicit as well as implicit conversion from `Boolean`.
- No implicit conversion to `Boolean`.
- No new built-in functions.

The rationale for each of these is given below.

## New built-in type
Although one could imagine ternary logic only being used in annoations to just have a pseudo code definition, and not be present in the language, it would seem like a half-baked solution. Besides, the use of `Ternary` for expressing explicit conversion from `Boolean` solves the problem of constructing the two known truth values nicely without introducing two more literal constants.

One could also have imagined just introducing `Ternary` as a new built-in enumeration, but expressing ternary logic in analogy with Boolean logic would then require defining the meaning of Boolean operators such as `and` to have meaning for this particular enumeration. That is probably not how we want enumerations to be used.

## New literal constant
The introduction of the keyword `unknown` to represent the third third truth value makes it very convenient to construct. Alternatives that were considered less attractive include:
- Defining `Ternary()` to construct `unknown`.
- Defining a `Ternary`-valued operation that can produce `unknown` as a function of only known values, for example `consensus(true, false)`.

The alternative approaches both suffer from the problem of not having a symmetric way of refering to _unknown_ in the same way as we refer to `false` and `true`. This lack of symmetry is both a problem for source code as well as in specification text and other places where writing about Modelica.

## Conversion from Boolean

### Explicit conversion
With the explicit conversion from `Boolean`, the other two `Ternary` values besides `unknown` can be expressed as `Ternary(false)` and `Ternary(true)`. This is preferred over a solution with three new literal constants (such as {`False`, `Unknown`, `True`}), where it would be a constant source of confusion that there would be two different literals for _false_ and two different literals for _true_.

### Implicit conversion
The implicit conversion from `Boolean` to `Ternary` would be very similar to the implicit conversion from `Integer` to `Real` — a well established and everywhere used feature of Modelica. To start with, it would mean that one would never need to write `Ternary(false)` or `Ternary(true)`; just writing `false` in a context where a `Ternary` is expected would be equivalent to writing out `Ternary(false)`, just like writing `1` is equivalent to writing `1.0` in a context where a `Real` is expected. Then, it would also make mixing `Ternary` and `Boolean` logic seamless, so the user doesn't have to explicitly choose between either `Boolean` or `Ternary` logic for all the subexpressions that don't make use of the third truth value. For example:
```
parameter Ternary t;
Real x;
Ternary y = t or (time > 1 and x < 5); /* No need to explicitly convert conjunction to Ternary. */
```

## Conversion to Boolean

### Ternary operations resulting in Boolean
Instead of introducing conversion constructs, there are some operations on `Ternary` that can be used to express exactly what should map to `false` and what should map to `true`. For example, `unknown == true` is defined as `false`, not `unknown`.

### No implicit conversion
To not have implicit conversion from `Ternary` to `Boolean` (such as defining the meaning of `Boolean(t)` where `t` is a `Ternary`) means that the user will always have to make an explicit choice of how to interpret a `Ternary` where a `Boolean` is required, for example in `if` conditions. There will be no surprises of _unknown_ being treated as either `true` or `false` instead of being reported as an error.

## No built-in functions
This MCP does not include any new built-in functions, although there are several that would be natural to have. For an example of a useful function the `consensus(t1, t2, …, tn)` would mean the common ternary value if all arguments are equal, otherwise `unknown`.

The reason for not introducing such functions is to minimize backwards incompatibility. If the lack of such functions turns out to be too much of a limitation, they can be introduced with a future MCP.

# The option type alternative
Before deciding to go with the design where `Ternary` is introduced, we must also mention the following important alternative approach. Reasons will be given why this isn't the design proposed by this MCP.

## Using `none` to represent _unknown_
A completely different way of introducing ternary logic would be to introduce a `Boolean` _option_ type. Instead of writing
```
Ternary t = unknown;
```
one would then write something like one of the alternatives
```
Boolean? t = Boolean?(); /* Explicit construction of the 'none' of a particular option type. */
```
or
```
Boolean? t = none; /* Using type inference to infer the particular option type. */
```
where the `?` plays a similar role as an array dimension; it constructs a new type based on the type to the left. In this case, an option type, meaning that the value of `t` is either a `Boolean` value, or a value representing the absence of a `Boolean` value.

While the type inference alternative with `none` looks more elegant on first glance, it doesn't really fit well with current Modelica, and there is also a problem with type inference and implicit conversion that speaks to the advantage of explicitly giving the type of a _none_-value. For the rest of this section, only the `Boolean?()` alternative for constructing the _none_-value is considered. Explicit construction of a non-_none_ value of `Boolean?` would take the form `Boolean?(e)` where `e` is an expression of type `Boolean`. There is thus an obvious way to define implicit conversion from any type `T` to `T?` simply by the mapping `t` → `T?(t)`, thus making the use of `Boolean?` almost as convenient as the use of `Ternary` (the difference being the keyword `unknown` vs the more cumbersome `Boolean?()`). (To see why implicit conversion doesn't work nicely with type inference, note that the implicit conversion from `none` to `Boolean??` is ambiguous; should it be `Boolean??()` or `Boolean??(Boolean?())`.)

Even though the use of option types could be restricted to `Boolean` to start with, it would open up for supporting more types in the future. Given the use case of allowing an expression of a built-in attribute to refer to the default value, it seems as if this could be useful for other types as well. For example, pretend that the `group` of `Dialog` didn't have `"Parameters"` as fixed start value, allowing tools to organize dialogs as they see fit. Then one could imagine giving an expression for `group` that only shall determine the group under certain circumstances, and otherwise leave it to the tool to decide. Then the use of a `String` option would be an elegant way of making this possible.

Another advantage of introducing option types instead of `Ternary` would be that it would probably be acceptable to say that no option type can be used for indexing into arrays. This would simplify parts of the implementation compared to `Ternary`.

However, these are some reasons for sticking with `Ternary` instead of `Boolean?`:
- If the usual ternary logic according to Kleene is what we want — which is the assumption of this MCP — the use of `Boolean?` would imply an unnatural interpretation of `none`. [This argument is elaborated below.](#Using-none-to-represent-undefined)
- Introducing the literal `none` to refer to the absence of a value for an option type leads to a significant change of the Modelica type system, as `none` can have any option type. Even if type inference would be implemented, there would be problems when it comes to implicit conversions (see above).
- Explicitly writting out the type of a _none_ as in `Boolean?()` would be inconvenient compared to just saying `unknown`. (However, it would then be natural to define implicit conversion to any option type.)
- Attributes of other type than `Boolean` typically have a default behavior that can be expressed with a default value (like the empty string in case of `String`), removing the need for an option type to express the absence of a value.
- The `Boolean?` type might end up being the only special case of an option type with meanings given to the built-in logical operators. (Possibilities to define meanings for other option types exist. For example, one could imagine the `Real?()` and `Integer?()` both being at the same time additive zeros and multiplicative ones. The question is whether there would ever be a need to introduce such definitions in the language.)
- Relational operators would need to be defined generically, most likely giving the order `Boolean()?` < `Boolean(false)?` < `Boolean(true)?`, which doesn't correspond to the understanding of `Boolean?()` as some kind of middle ground between false and true.
- Defining external language interface for option types is a pretty big change compared to just introducing one new scalar type (it would need to be defined generically, not just with `Boolean?` in mind).
- It is probably a bad idea to just introduce option types in Modelica without considering the more general concepts that would give option types as a special case. Such more general constructs inspired by MetaModelica have been discussed at many design meetings without getting much traction.

## Using `none` to represent _undefined_
While possible to interpret `Boolean?()` (hereafter referred to as `none` for brevity) as _unknown_ and define logical operation on `Boolean?` in the same way as for `Ternary` to get a Kleene logic, the generalization of this interpretation to other option types such as `Real?` or `String?` isn't as useful. The natural interpretation of `none` would rather be _undefined_, which leads to more useful generalizations to other option types.

For example, one could define that concatenation with an _undefined_ `String?`, would be a no-op, and the same for addition or multiplication with an _undefined_ `Real?`. For `Boolean?` it would then be natural to define both disjunction and conjunction with _undefined_ to be no-ops, leading to things such as `none and true = some(true)`.

Combined with `Ternary` to get `Ternary?`, one could define four-valued logic, having its own applications beyond both `Ternary` and `Boolean?`. In detail, by identifying `true` both with `Ternary(true)` and `Boolean?(true)`, and similarly for `false`, the truth tables for `Ternary` could be reordered and extended so that they define consistent tables for all of `Boolean`, `Ternary`, `Boolean?` and `Ternary?`. For instance, consider conjunction and let `none` refer to `Boolean?()` for brevity:

| `x and y` | `y = none` | `y = false` | `y = true` | `y = unknown` |
| ------------- | ------------- | ----------- | ------------- | ------------- |
| `x = none` | `none` | `false` | `true` | `unknown` |
| `x = false` | `false` | `false` | `false` | `false` |
| `x = true` | `true` | `false` | `true` | `unknown` |
| `x = unknown` | `unknown` | `false` | `unknown` | `unknown` |

Please note that this logical table does not correspond to the often cited four-valued logic by Belnap, hinting at the potential problems of reaching agreement on how to define logic on `Boolean?` and `Ternary?` in Modelica.

For the numeric types `Real` and `Integer`, a better analog to `unknown` would be `NaN` (not-a-number), having very different arithmetic behavior compared to the no-ops of _undefined_. For example, this gives the expected behavior of adding _unknown_ to any number resulting in _unknown_.

To summarize, while option types have many interesting applications — including modeling of built-in attributes with non-trivial defaults — it would be a mistake to use `Boolean?` for the usual ternary logic according to Kleene with _unknown_ as the third truth value. This also shows that `Ternary` is not going to become redundant if option types are added to Modelica in the future.
Loading