-
Notifications
You must be signed in to change notification settings - Fork 326
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
Conversation
This reverts commit c30d171.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
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!?
There was a problem hiding this comment.
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 map
ping 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 beReport_Error/Report_Warning
. - that means that all usages in lib code would need to add
on_problems=Nothing
(or whatever syntax we choose) to themap
. 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.
There was a problem hiding this comment.
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 ... addon_problems=Nothing
Define an internal map_primitive
extension method in each library and use it instead of map on_problems=Nothing
where needed.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this 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.
There was a problem hiding this 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.
distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Array_Like_Helpers.enso
Show resolved
Hide resolved
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 |
There was a problem hiding this comment.
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 ... addon_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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector/Extensions.enso
Outdated
Show resolved
Hide resolved
After discussion with @JaroslavTulach and @hubertp, we agreed it would be best that the |
The methods have been removed.
Pull Request Description
Important Notes
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.
./run ide build
.