Page MenuHomePhabricator

RFC: Eolian support for standalone (non-class) functions
Open, WishlistPublic

Description

I have a proposal for something I think Eolian is missing: non-class functions.

What is it?

Functions that are available in Eolian (so bindings generator can use them) but are not tied to a specific class or object. It's most useful for utility functions that do operations of objects, but they can also be used on non-objects (examples will follow).

Example usecases

As mentioned above there are two main kinds of utility functions I have in mind. Ones that operate on objects, and ones that don't.

Operate on objects

Those are useful in cases where you have some functionality that is external to an object and could even be implemented elsewhere. Here's an example from the efl.text world: consider our textblock object, let's call it efl_text. You set the text with efl_text_set(obj, text) and the formats with efl_text_annotation_set(obj, range_start, range_end, format_str).
We want to support our existing markup format, and we can, so we implement it in the text object.

But now we want to add Markdown support, and the RestructuredText, and then whatever else it may be. We can't implement them all in this text object as they may have dependencies, but also they may just be things we don't want in core efl. Inheriting and extending it in an external library is also not an option (for many reasons, like: what happens when you want both on the same object? Or maybe add another set of unrelated extensions).

We therefore need a utility function that is able to apply e.g. Markdown on the text object. It would look something like this (psuedo code):

// This is NOT an eo function!
efl_text_markdown_text_set(obj, markdown) {
    efl_text_set(obj, _markdown_to_text(markdown);
    for (an in _markdown_to_annotations(markdown)) {
        efl_text_annotation_add(obj, an->start, an->end, an->format);
    }
}

And the same for each of those.

This is all very easy to do already in C, and I think we have some places where we do similar stuff, but these very useful utility functions are currently not available in bindings. The suggested change would cover this change.

Operate on non-objects

We could have other utility functions that are not tied to objects. For example, utility functions to convert from HSL to HTML notation (#RRGGBBAA) for colors or anything like that. This example is not too efl specific, but I can come up with a few places where they make things easy in EFL specific ways too. The syntax would be the same and with this change it would to make them available for bindings.

How should it look?

It should look exactly like in class methods just outside and prefixed with the keyword function.

// efl_text_markdown.eo

function Efl.Text.Markdown.text_set {
       [[Sets a markdown text on an Efl.Text object]]
       params {
          obj: Efl.Text; [[The object]]
          markdown: string; [[A Markdown string]]
       }
 }

// @q66: any comments?

interface Whatever {
    ...
}

How would it generate?

For languages that have normal functions they would just generate as normal functions. For functions that don't, like Java, they could be generate in a special class in the appropriate namespace.

tasn created this task.Aug 7 2019, 10:34 AM
tasn triaged this task as Wishlist priority.
cedric added a subscriber: cedric.Aug 8 2019, 9:41 AM

If it is a utility function related to a specific class/interface/whatever, why should it be declared outside of the other function of the class?

It seems just like a function that is not inside the vtable. It kind of feel in some way like the opposite of a @pure_virtual . Just you can't inherit from it. Still you would want it in the class namespace and you want to avoid any clash on its name anywhere. I would argue this should go with an @no_inherit tag or something like that instead of standing out of the class.

In the more general sense, is this feature useful or not. It is a very very tricky to use one as it means impossible to inherit that function at any point in the future. It might be ok for some color helper for example, but I would not allow a color from a string to be of that kind. Actually your example of using it for markdown, is maybe a good example I would disagree to use it for. There is tons of different flavor/extension for markdown, having no inheritance means that every widget that will want to implement a variation of it will require application to have to deal with their own API for markdown, which isn't going to be a great outcome in my opinion. So it should be very seldomly and carefully used.

So my question is: is it worth the risk? The benefit I see is a smaller vtable, how much does that help?

tasn updated the task description. (Show Details)Aug 8 2019, 2:50 PM

I updated the example to use an interface instead of a class to make it more clear.
I'll also elaborate on my markdown example so it's even more clear (though I thought I was).

The second case is the clearer one, it doesn't even work on objects and it's just generic utility functions. You could make them class functions but they don't really make sense. I hear from @felipealmeida that @woohyun also had a similar usecase in mind.

As for the first case: You have Efl.Text which is implemented in libefl.so. That's outside of your control. Now Jess, a 3rd party developer wants to build a library for Markdown support for the Efl. She wants to implement two functions markdown_set and markdown_get. She however doesn't want to inherit and extend efl.text, that's because what happens when someone wants to add a utility function to set Rst, or maybe she's not the one creating the object (think elm_entry), or there are other utility functions that others would create. Anyhow, she can't inherit and extend, as there's not necessarily a linear extension graph. This means she has to create a utility function.

At the moment such functions are not easily bindable in bindings. This proposal fixes that.

cedric added a comment.Aug 8 2019, 3:36 PM
In T8118#139821, @tasn wrote:

As for the first case: You have Efl.Text which is implemented in libefl.so. That's outside of your control. Now Jess, a 3rd party developer wants to build a library for Markdown support for the Efl. She wants to implement two functions markdown_set and markdown_get. She however doesn't want to inherit and extend efl.text, that's because what happens when someone wants to add a utility function to set Rst, or maybe she's not the one creating the object (think elm_entry), or there are other utility functions that others would create. Anyhow, she can't inherit and extend, as there's not necessarily a linear extension graph. This means she has to create a utility function.

I think you are confusing me more by insisting on markdown being an example for this :-) In the above case, we have been talking for some time about adding the ability to have system and application override for an object. The idea being that you can do exactly what you describe and affect the entire system using inheritance and replace all system/app instance of a class by another. The idea was to do so only on Elementary widget, but it could be in Eo.

So let's ignore markdown as I really think it is a bad example. I was going to pick efl_gfx_color_cmyk_{set,get} as a better example, but even so, there might be case where you might want to have the ability to get the color directly without the conversion to RGB. It is maybe another bad example. Hum, I would like to find a good example first to have an idea on how it should be done. Will try to think more about it.

The idea of replacing one by the other won't solve what I described because like all global functions of such sort, you can have only one. Again, what happens when you have libefl-markdown.so that wants to offer markdown and libefll-html.so that wants to offer html? They can't both replace the textblock object globally.

Additionally, since T7675 broke class functions in Eo/EFL, you need to look no further than the codebase itself. Every @class function we currently have will end up as a floating function as suggested here (or the @static synatx as suggested by @lauromoura).

tasn added a comment.Aug 16 2019, 7:28 AM

It looks like it was agreed in T7675 to call this @static. @q66 is doing the rename, but just a few things that are missing and should be implemented (copied from the other ticket):

It's not just that, you also need to make sure that static is only allowed on classes and not interfaces, and you can only implement where it was defined, not elsewhere (so no overrides, and have to implement immediately).

tasn added a comment.Aug 16 2019, 9:39 AM

Mostly done in rEFL8a8a833837f7217aea0a33f4f7afbb6edfb103c4 (thanks @q66!), still missing the stuff from the previous comment though.

tasn added a comment.Aug 28 2019, 8:59 AM

@const should also not be allowed on @static, because it doesn't mean anything.