Skip to content
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

Better Error Trapping in map #8307

Merged
merged 121 commits into from
Dec 13, 2023
Merged

Better Error Trapping in map #8307

merged 121 commits into from
Dec 13, 2023

Conversation

GregoryTravis
Copy link
Contributor

@GregoryTravis GregoryTravis commented Nov 15, 2023

Pull Request Description

Important Notes

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed, the GUI was tested when built using ./run ide build.

@GregoryTravis GregoryTravis changed the title Wip/gmt/8110 map error Better Error Trapping in map Nov 15, 2023
@GregoryTravis GregoryTravis linked an issue Dec 4, 2023 that may be closed by this pull request
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

I am missing high-level overview in this PR. All I see is a bunch of xyz_primitive methods being added and I don't see a reason for that. The PR touching the core classes of the Enso engine. I'd like to understand its motivation before it gets in. I'd like to have a deeper review of the goals and ways we use to achieve them.

## PRIVATE
Implementation of `map` that uses `vector_from_function_primitive`, which
does not do error wrapping, and does not have `on_problems`.
map_primitive : (Any -> Any) -> Vector Any
Copy link
Member

Choose a reason for hiding this comment

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

They are not 'private helpers' but just a 'developer oriented' variant of map that is more oriented towards internal implementations than the user-facing map whose error handling is more IDE-centric.

So I don't think they should necessarily be that hidden - they are not meant for the GUI users primarily, but library devs on outside libraries will definitely appreciate this function.

Is the above said by the same @radeusgd that usually takes rationalistic point of view? This is not a rationalistic approach at all. This stance is horribly pragmatic.

However we are talking about core API class of Enso. Where else shall we act rationalisticly than here? Moreover we are talking about builtin - e.g. part of the engine. Hence, I am willing to fight against debris like this to appear in these core classes.

Perhaps what we ultimately need is a user-facing Vector that mostly delegates to the internal Vector, but also implements things like Map_Error wrapping.

If you want internal array like object while implementing a library, use java.util.ArrayList or co. We haven't

to introduce two types of vectors again.

prevent undocumented classes from being exposed to the user

Yes, please use private modules (e.g. real private, not just a comment). Should any problem (like #8469) appear, speak up.

@@ -848,6 +849,7 @@
[8105]: https://github.com/enso-org/enso/pull/8105
[8150]: https://github.com/enso-org/enso/pull/8150
[8235]: https://github.com/enso-org/enso/pull/8235
[8307]: https://github.com/enso-org/enso/pull/8307
Copy link
Member

Choose a reason for hiding this comment

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

How comes that a PR like this doesn't have any overview at all?

obrazek

No reference to any issue!? No summary? No explanation why chosen path is the right one?

Implementation of `map` that does not do error wrapping, and does not
take an `on_problems` parameter.
map_primitive : (Any -> Any) -> Vector Any
map_primitive self function = Vector.map_primitive self function
Copy link
Member

Choose a reason for hiding this comment

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

What is a difference between map_primitive and map on_problems=Report_Error? I believe:

map_primitive self function = map self function Problem_Behavior.Report_Error

and I really don't understand why there should be two different methods to begin with!?

Copy link
Member

Choose a reason for hiding this comment

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

No, there is a difference.

Let me show by example:

f = if x == 3 then Error.throw (Illegal_Argument.Error "foo") else 100+x

v = [1, 2, 3, 4, 5]
r1 = v.map_primitive f
r2 = v.map f Problem_Behavior.Report_Error

Now the results are following:

r1 = Error: Illegal_Argument.Error "foo"
r2 = Error: Map_Error.Error 2 (Illegal_Argument.Error "foo")

The difference is that the user-facing map is wrapping the original error in a wrapper that allows our users to tell at which position in the collection the error appeared. This was a common issue that when the user was e.g. downloading 100 CSVs and the 87th download has failed - all they'd get is the failure but they would not know where it happened and the result of all other downloads was discarded.

Now with Report_Warning - all succeeding downloads are still kept, since the error value is replaced with Nothing so e.g. the user can decide to skip the problematic file.

And with Report_Error the first error is thrown, but the user has additional information on where it has happened, hopefully making it easier to analyze what went wrong.

The separate map_primitive variant is needed and cannot be private, because for 'lower-level' developers (e.g. not GUI end users but library devs), it often does not make sense to wrap the error in Map_Error - if the mapping operation is an internal implementation detail, e.g. checking a list of preconditions, I want the raw error to be thrown to the user, not a wrapped one - because I do not want to 'leak' the fact that I have used the map internally - it is not important to the user and in that case the Map_Error wrapping would only add noise.

And so this map_primitive functionality cannot be private, because we may very well want to use it in our other libraries; as well as even our more advanced users may want to use it in their internal code.


That is why we need both a Report_Error/Report_Warning modes for UX of our GUI users and a map_primitive mode which avoids these decorations and can retain the original APIs for lower level usage.

However, I don't think a special map_primitive function is the only solution to get this.

I think a viable alternative could be to instead allow a 4th mode on regular map - i.e. expose the map_primitive mode as vec.map f on_problems=Nothing or some other way (e.g. vec.map f on_problems=Report_Raw_Error) or whatever.

I think that approach is slightly cleaner as we still keep one map function.

OTOH the downside of this approach is slightly clashing requirements:

  • we want the on_problems to have a default value, and the default should be oriented towards our GUI users as they are our main focus - so it'd have to be Report_Error/Report_Warning.
  • that means that all usages in lib code would need to add on_problems=Nothing (or whatever syntax we choose) to the map. It seems slightly annoying and easy to forget to do so - so I imagine if we choose this, we will be forgetting from time to time and getting unnecessarily wrapped errors.

The advantage of map_primitive (I don't like the name) over the above solution is that it is e.g. much easier to find in our code all usages of it (search for map_primitive), versus the on_problems argument that is used on all kinds of functions so it may be harder to find.

Copy link
Member

Choose a reason for hiding this comment

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

r1 = Error: Illegal_Argument.Error "foo"
r2 = Error: Map_Error.Error 2 (Illegal_Argument.Error "foo")

Thank you for your explanation.

allow a 4th mode on regular map

Yes, that's a way better alternative!

advantage of map_primitive ... easy to forget to ... add on_problems=Nothing

Define an internal map_primitive extension method in each library and use it instead of map on_problems=Nothing where needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After discussion with @JaroslavTulach, I've moved the _primitive methods into extensions, and introduced a No_Wrap atom to implement them. Now all regular and primitive calls go through Array_Like_Helpers.vector_from_function.

Would we prefer perhaps map_no_wrap to map_primitive?

Copy link
Member

Choose a reason for hiding this comment

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

Personally, I like the map_no_wrap name :) well maybe map_without_wrapping? Idk which is better, but both seem better than map_primitive whose name is carrying some additional meaning that does not really fit the use-case.

@@ -1,7 +1,18 @@
import project.Any.Any
Copy link
Member

Choose a reason for hiding this comment

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

This class shall be private as demonstrated by diff in #8469

@GregoryTravis GregoryTravis removed the CI: Ready to merge This PR is eligible for automatic merge label Dec 7, 2023
Errors and Warnings that arise when executing the function are wrapped in
`Map_Error`.

Only `MAX_MAP_WARNINGS` warnings are attached to result values. After that,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Only `MAX_MAP_WARNINGS` warnings are attached to result values. After that,
Only `MAX_MAP_WARNINGS` number of warnings are attached to result values. After that,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@hubertp hubertp left a comment

Choose a reason for hiding this comment

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

I thought that if we define the methods as extensions then no
from project.Data.Vector.Extensions import all is needed. Yet this PR seems to add it in numerous places. Is that still necessary?

I mixed extensions with conversions. Ignore, as per conversation.

Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

Please hide Data/Vector/Extensions.enso from Standard.Base APIs.

Implementation of `map` that does not do error wrapping, and does not
take an `on_problems` parameter.
map_primitive : (Any -> Any) -> Vector Any
map_primitive self function = Vector.map_primitive self function
Copy link
Member

Choose a reason for hiding this comment

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

r1 = Error: Illegal_Argument.Error "foo"
r2 = Error: Map_Error.Error 2 (Illegal_Argument.Error "foo")

Thank you for your explanation.

allow a 4th mode on regular map

Yes, that's a way better alternative!

advantage of map_primitive ... easy to forget to ... add on_problems=Nothing

Define an internal map_primitive extension method in each library and use it instead of map on_problems=Nothing where needed.

@@ -1,6 +1,7 @@
from Standard.Base import all
import Standard.Base.Errors.Common.Index_Out_Of_Bounds
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
from Standard.Base.Data.Vector.Extensions import all
Copy link
Member

Choose a reason for hiding this comment

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

These extensions shouldn't be exported from Standard.Base they should be private for each project. Otherwise they become part of API (which is what I'd like to avoid). map_no_wrap shalln't be visible to Enso users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They've been removed entirely.

@GregoryTravis
Copy link
Contributor Author

GregoryTravis commented Dec 8, 2023

After discussion with @JaroslavTulach and @hubertp, we agreed it would be best that the_primitive methods should not be in Standard.Base. While moving them and their tests to the Table and Database projects, it became clear that this is all overengineering, and it's better to get rid of the _primitive methods just pass the No_Wrap flag where necessary.

@GregoryTravis GregoryTravis added the CI: Clean build required CI runners will be cleaned before and after this PR is built. label Dec 12, 2023
@GregoryTravis GregoryTravis merged commit 1c815a3 into develop Dec 13, 2023
34 checks passed
@GregoryTravis GregoryTravis deleted the wip/gmt/8110-map-error branch December 13, 2023 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Clean build required CI runners will be cleaned before and after this PR is built.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better Error Trapping in map
5 participants