Skip to content

Implement conversion/type expressions #1050

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

Merged
merged 11 commits into from
Dec 13, 2022
Merged

Implement conversion/type expressions #1050

merged 11 commits into from
Dec 13, 2022

Conversation

katcharov
Copy link
Collaborator

Copy link
Collaborator Author

@katcharov katcharov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the TODOs cover the same topic; I've left a comment covering each topic covered by all TODOs. With the exception of the TODO, the code marked by it is not a draft, but is usually something I want to highlight or am not entirely satisfied with (but have considered good enough).

// the direct "isT" (comparable to instanceof) are exposed via switch
// here, we expose isTypeOr. These would be used on an Expression of
// an unknown type, or to provide default values in cases where a null
// has intruded into the alleged type.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments above. I have not exposed any of the isBoolean/isArray/etc (returns BoolEx) expressions, since it seems that the methods below cover what users need them for (isBooleanOr, which is an "opposite" to ifNull, which I think is preferable). Other possible usages would be covered by switch. But perhaps there is some reason to expose these that I am missing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always expose those methods in the future if we discover that we can't get by without doing so.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other possible usages would be covered by switch.

How so? It also surprised me not to see these methods.

Copy link
Collaborator Author

@katcharov katcharov Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be only the following cases where isBoolean and the rest are are needed:

  1. When doing pattern-matching (comparable to a group of if-instanceof blocks in Java). Switch is always a better way to model this, when there is more than one type being checked.
something.switchMap(on -> on
                .isBoolean(v -> v.asString().concat(of(" - bool")))
                .isNumber(v -> v.asString().concat(of(" - number")))
                .isString(v -> v.asString().concat(of(" - string")))
                .defaults(v -> of("something else"))
...
  1. When the value is being checked against only one type, we use something like isBooleanOr(<defaultBolean>), which is conceptually shorthand for the following (though the generated MQL is different).
something.switchMap(on -> on
                .isBoolean(v -> v)
                .defaults(v -> <defaultBoolean>)
...

This case arises in the getField(f, default) methods, to allow users to get fields that they cannot assert contain only one type of value (this also covers null and missing, since those are not, for example, booleans).

  1. As @stIncMale brought up elsewhere, we might need array.filter(v -> v.isBoolean()).map(v -> v.isBooleanOr(true)), but it is better to provide something like our switch's pattern-matching for this, so array.filterToIntegers or something more suitably named. This reduces the boilerplate and gets rid of the unused default value true.

Apart from the above (which are all variations of the same theme of mapping to an alternative based on type), there seems to be no common case where these isType checks would be used (and the rarer cases can all be modelled using switch(on->isType(...).defaults(...)) if it comes to that).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be only the following cases

I don't buy this argument. How is this any different than instanceof in Java? Often it's the only condition in a swittch/if, but sometimes it's combined with other conditions, e.g. if (foo instanceof Bar || foo instance of Baz). It would kind of stink if the language syntax prevented that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The language does not prevent the unusual case you describe:

something.switchMap(on -> on
                .isBoolean(v -> of(true))
                .isNumber(v -> of(true))
                .defaults(v -> of(false)).not()

I say "unusual" because I spot checked our codebase for composite instanceof, and saw only ~5/100, and even those all seemed improbable in our API. In any case, the alternative, using isBoolean, seems less elegant even for the code fragment you provided:

var isBooleanOrNumber = v -> v.isBoolean().or(v.isNumber())
something.apply(isBooleanOrNumber).not()

The rationale for excluding it is not just YAGNI (which is itself a fair rationale). If we exclude it, users attempting to check types are forced into a "pit of success" for the most common cases, such as :

something.switchMap(on -> on
                .isDate(v -> v)
                .isString(v -> v.parseDate()))

If we expose the "isType" methods, users would gravitate towards the following:

something
        .isDate().cond(v, something
                .isStringOr(of("not used")).parseDate())

This is not as readable, requires an unused construct, and it is less maintainable (imagine that you must now check something for integer milliseconds - consider how immediately obvious the code change is to the switchMap, versus the example using isDate immediately above.

  1. Still trivially possible to do everything, and in a syntax that is arguably superior
  2. Forces users into a substantially better syntax for the most common cases
  3. Maintains "a single way of doing things" within the API
  4. YAGNI
  5. We can always add these later but cannot remove them

Copy link
Collaborator Author

@katcharov katcharov Dec 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed, and agreed that these can be added later if needed (and we will have more info to decide this later).

@rozza rozza removed their request for review November 22, 2022 14:01
@katcharov katcharov requested a review from stIncMale November 28, 2022 21:12
Base automatically changed from expressions-date to expressions November 29, 2022 15:48
@stIncMale stIncMale mentioned this pull request Nov 29, 2022
Copy link
Member

@stIncMale stIncMale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

// the direct "isT" (comparable to instanceof) are exposed via switch
// here, we expose isTypeOr. These would be used on an Expression of
// an unknown type, or to provide default values in cases where a null
// has intruded into the alleged type.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other possible usages would be covered by switch.

How so? It also surprised me not to see these methods.

@katcharov katcharov requested a review from jyemin December 6, 2022 18:22
Copy link
Collaborator

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not satisfied with parseNumber yet.

@katcharov katcharov requested a review from jyemin December 12, 2022 18:34
@katcharov katcharov merged commit 5a74814 into expressions Dec 13, 2022
@katcharov katcharov deleted the expressions-conv branch December 13, 2022 21:42
katcharov added a commit that referenced this pull request Jan 31, 2023
* Implement boolean expressions (#1025)

JAVA-4779

* Implement filter, map, reduce (#1031)

JAVA-4781

* Implement eq, ne, gt, gte, lt, lte (#1033)

JAVA-4784

* Implement string expressions (#1036)

JAVA-4801

* Implement arithmetic expressions (#1037)

Implement arithmetic expressions (from top 50, and others)

JAVA-4803

* Implement array expressions (#1043)

JAVA-4805

* Implement date expressions (#1045)

JAVA-4804

* Implement conversion/type expressions (#1050)

JAVA-4802

* Implement document expressions (#1052)

JAVA-4782

* Replace reduce with individual reductions (#1053)

JAVA-4814

* Implement map expressions (#1054)

JAVA-4817

* Implement switch expression (#1055)

JAVA-4813

* Test expressions in context (#1057)

JAVA-4820

* Add javadoc for boolean, date, number, integer, and expression (#1059)

 JAVA-4799

* Update and add documentation (#1059)

* Fix, tests

 JAVA-4799

* Add `@MqlUnchecked` and a few usage examples (#1059)

 JAVA-4799

* Add has to document, add tests (#1070)

 JAVA-4799

* Add javadocs for remaining classes (#1070)

 JAVA-4799

* 5.2 annotations (#1070)

 JAVA-4799

* 5.0 annotations (#1070)

 JAVA-4799

* 4.4 annotations (#1070)

 JAVA-4799

* 4.2 annotations (#1070)

 JAVA-4799

* 4.0 annotations (#1070)

 JAVA-4799

* Update and add documentation, add tests, fix minor issues (#1070)

Rename extractBsonValue

Fix access modifiers

Remove excess comments

Update docs

Fix: behaviour of get

Add notNull to API, add notNullApi test

Fix docs/annotations, tests

Fix docs, annotations, since

Fix docs

Revert external

Add missing MqlUnchecked

Fix missing null checks

Checkstyle

JAVA-4799

* Rename to Mql (automated) (#1073)

JAVA-3879

* Rename methods (automated) (#1073)

JAVA-3879

* Update naming, terms, and missing checks and annotations (#1073)

JAVA-3879

---------

Co-authored-by: Valentin Kovalenko <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants