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

Automatically apply from conversion when runtime argument check fails #7009

Merged
merged 12 commits into from
Jul 11, 2023

Conversation

JaroslavTulach
Copy link
Member

@JaroslavTulach JaroslavTulach commented Jun 10, 2023

Pull Request Description

Enhancing ReadArgumentNode to apply from conversions when the argument type check fails. For example there is no need to type 42.to_text when requiring a Text argument. The conversion will happen automatically.

Details

#6790 has introduced ability to opt-in and check type of arguments where needed (from a standard library perspective). These checks are slowly appearing in the Enso code base. They help us catch typing errors as soon as possible and report errors to users in a meaningful way. User can then change their code and apply necessary conversions themselves. However there is no need for such manual conversions!

We can automatically apply from conversion when the argument type doesn't fit the ascribed type and such a conversion is available in the module scope. We can do such conversions effectively - almost at no performance cost as benchmarks added by this PR demonstrate.

Non-goal

Changing the way conversions are imported into scope of a module isn't a goal for this PR. The way conversions are found remains unchanged by this PR. If a from conversion was found "manually" before, it is going to be found "automatically" now. Designing new ways for conversions to be located is out of scope of this PR. However as #7271 demonstrates, the current behavior may actually be good enough.

Future Work

These automatic conversions work great with runtime type classes. Implementing such runtime type classes with automatic conversions from any value is going to be possible when this PR is integrated.

It is (almost certainly) possible to add support for & operator without any performance penalty - e.g. one could write (a: Ord & Eq) and the type system would verify that the argument a supports all the required conversions - to Ord as well as to Eq. Implementation of this is out of scope of this PR.

Checklist

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

  • The documentation has been updated, if necessary.
  • All code follows the
    Scala,
    Java,
  • All code has been tested:
    • Unit tests have been written where possible.
    • Ensure no regression in ListBenchmarks.mapOverList benchmark

@GregoryTravis
Copy link
Contributor

I am concerned that this might result in hard-to-find bugs. If you write a bad method call that should fail, and it succeeds because of a conversion you weren't aware about, you'll get unexpected behavior that will be tricky to find, because it is made possible by a call to from that you did not explicitly make.

On the other hand, I am not familiar with the from conversions that exist in the code, so I don't know how much of a problem this will actually be in practice.

@xvcgreg
Copy link

xvcgreg commented Jun 26, 2023

@JaroslavTulach what is the status of this issue?

@JaroslavTulach
Copy link
Member Author

@JaroslavTulach what is the status of this issue?

It is a draft.

@JaroslavTulach
Copy link
Member Author

JaroslavTulach commented Jul 5, 2023

Running benchmarks to get official results for the new ListBenchmarks. The results are as expected:

        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapAnyOverList</label>
            <scores>
                <score>147.31108088695652</score>
            </scores>
        </case>
        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapConvOverList</label>
            <scores>
                <score>84287.3115472</score>
            </scores>
        </case>
        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapIntegerOverList</label>
            <scores>
                <score>145.74264644395203</score>
            </scores>
        </case>
        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapMultiOverList</label>
            <scores>
                <score>144.37380159563025</score>
            </scores>
        </case>
        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapOverList</label>
            <scores>
                <score>141.27027240281691</score>
            </scores>
        </case>
        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapVOverList</label>
            <scores>
                <score>141.1073415324603</score>
            </scores>
        </case>

all relevant benchmarks need 140-150ms per operation, but mapConvOverList which needs more than a minute (84287ms).

@JaroslavTulach
Copy link
Member Author

JaroslavTulach commented Jul 7, 2023

The case with conversion shall now be faster since ec5a3b3 - running benchmarks to verify. mapConvOverList is now as fast as regular runs without any conversions:

        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapConvOverList</label>
            <scores>
                <score>144.07712597041146</score>
            </scores>
        </case>

there seems to be a slowdown in plain

        <case>
            <label>org.enso.interpreter.bench.benchmarks.semantic.ListBenchmarks.mapOverList</label>
            <scores>
                <score>150.43897446282224</score>
            </scores>
        </case>

and it needs to be investigated.

@JaroslavTulach
Copy link
Member Author

JaroslavTulach commented Jul 9, 2023

Benchmarking most recent changes at benchmark run.... existing benchmarks seem to be fine...

mapOverList

@JaroslavTulach JaroslavTulach marked this pull request as ready for review July 9, 2023 09:46
Copy link
Member

@wdanilo wdanilo left a comment

Choose a reason for hiding this comment

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

This is absolutely AMAZING! It also means that we are closer and closer to the .to method, right? Also, would it be possible to notify GUI where such conversion was applied? We would then display that using a special connection style (e.g. dashed one).

@GregoryTravis your concerns are very valid. Every implicit thing has the downside of code being more complex to trace. That's why it's important to visually indicate all such places - both on the graph and in the text editor. Anyway, such auto-conversions will help make the graph clean and fast to work with and I believe they can become one of the most powerful features of Enso.

@JaroslavTulach
Copy link
Member Author

@GregoryTravis your concerns are very valid.
Every implicit thing has the downside of code being more complex to trace.
That's why it's important to visually indicate all such places ... on the graph

+1

Also, would it be possible to notify GUI where such conversion was applied?

Yes, we are able to trace when a conversion happens using GraalVM Insight (a toy I love). Imagine following convert.enso program:

from Standard.Base import all

meaning (v : Text) = "Meaning of world " + v

main = meaning 42

Text.from (that : Number) = that.to_text

which will do the automatic conversion when passing number 42 to the meaning function as it requires Text. You can run it with this PR as:

enso$ ./built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/bin/enso --run convert.enso 
'Meaning of world 42'

Let's now use the GraalVM's Insight capabilities by creating a tracing script to print out a message whenever a from function is invoked. Create trace.js:

insight.on('return', (ctx, frame) => {
    let that = frame.that;
    let to = ctx.returnValue(frame);
    print(`Converting via ${ctx.name} at ${ctx.source.name}:${ctx.line}`
       + ` from ${that}:${typeof that} to ${to}:${typeof to}`);
}, {
    rootNameFilter : '.*from',
    roots : true
});

and then execute with additional JAVA_OPTS parameters:

enso$ JAVA_OPTS=-Dpolyglot.insight=trace.js ./built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/bin/enso --run convert.enso 
Converting via convert::Standard.Base.Data.Text.Text::from at convert.enso:7 from 42:number to 42:string
'Meaning of world 42'

and we can see that the from conversion has been observed. As GraalVM Insight offers similar capabilities as Enso instrumentation I am pretty sure we can trace automatic conversions at runtime...

We would then display that using a special connection style (e.g. dashed one).

...we just need to design a way to provide such information to the IDE and then display it. That will need some future work, CCing @4e6.

That's why it's important to visually indicate all such places - both on the graph and in the text editor.

I am sure we can do it in the graph - e.g. when the program is running - easily. Alas, I don't know how to do the same in the text editor - e.g. just by static analysis.

@JaroslavTulach
Copy link
Member Author

It also means that we are closer and closer to the .to method, right?

This PR proves there is no overhead when applying the conversions - e.g. that the .to method can be implemented effectively. On the other hand, this PR also removes the need to use the .to conversion in certain situations. Rather than writing in the previous example:

meaning (42.to Text)

one can simply call meaning 42 and benefit from the automatic conversion of ascribed runtime arguments.

@JaroslavTulach JaroslavTulach merged commit 2aeef4d into develop Jul 11, 2023
@JaroslavTulach JaroslavTulach deleted the wip/jtulach/ArgumentConversion branch July 11, 2023 06:23
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.

nits

}

static ReadArgumentCheckNode build(String argumentName, Type[] expectedTypes) {
if (expectedTypes == null || expectedTypes.length == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

That's a bit unusual. IIRC for all nodes we assume that build will return the node, no exceptions?

}

private static boolean isAllFitValue(Object v) {
return v instanceof DataflowError
Copy link
Contributor

Choose a reason for hiding this comment

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

speaking of DataflowError logic being distributed ;)

}

ApplicationNode findConversionNode(Type from) {
var convAndType = findConversion(from);
Copy link
Contributor

Choose a reason for hiding this comment

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

from can be null I think, but I don't think we handle that nicely in findConversion, do we?

@radeusgd
Copy link
Member

We would then display that using a special connection style (e.g. dashed one).

...we just need to design a way to provide such information to the IDE and then display it. That will need some future work

Just please note that in many cases we put parameters directly on a node, and this conversion may also happen directly there and there will be no connection to stylize.

i.e. if we have

y = my_operation 42

instead of

x = 42
y = my_operation x

there is no connection between 42 and my_operation to display in the first case as the parameter 42 is directly in the node.

Maybe we should consider to then add some indication for these cases as well? Possibly some dashed frame over the parameter, which when hovered says which conversion has been applied?

mergify bot pushed a commit that referenced this pull request Jul 13, 2023
)

Engine benchmark downloader tool [bench_download.py](https://github.com/enso-org/enso/blob/develop/tools/performance/engine-benchmarks/bench_download.py) can now plot multiple branches in the same charts. The tooltips on the charts, displayed when you hover over some data point, are now broken for some branches. I have no idea why, it might be a technical limitation of the Google charts library. Nevertheless, I have also extended the *selection info* section, that is displayed under every chart, where one can see all the important information, once you click on some data point.

Options added:
- `--branches` specifies list of branches for which all the benchmark data points will be in the plots. The default is `develop` only.
- `--labels` that can limit the number of generated charts.

This PR also **deprecates** the `--compare` option. There is no reason to keep that option around since we can now plot all the branches in the same charts.

An example for plotting benchmarks for PR #7009 with
```
python bench_download.py -v --since 2023-07-01 --until 2023-07-11 --branches develop wip/jtulach/ArgumentConversion
```
is:
![image](https://github.com/enso-org/enso/assets/14013887/62010850-79d2-4c6c-92bc-9627bb4c6a0b)

# Important Notes
- Deprecate `--compare` option
- Add `--labels` option
- Add `--branches` option
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.

9 participants