herminiogg / ShExML

A heterogeneous data mapping language based on Shape Expressions
http://shexml.herminiogarcia.com
MIT License
15 stars 2 forks source link

Nested functions #129

Closed anaigmo closed 2 years ago

anaigmo commented 2 years ago

Hi! I was checking the new features of the language, and wanted to ask if ShExML supports functions inside functions. I tried the code from the spec in the example of the online editor (changed from films.name to helper.allCapitals(films.name) in the :Films shape), but it outputs an error key not found: Var(helper).

herminiogg commented 2 years ago

Hi Ana,

There are two things to have in mind when using functions in ShExML:

You can see a full example using different functions extracted from the ShExML tests below. As you can see it defines the helper variable pointing to a Scala class hosted on Github.

As for your question regarding nested functions, it depends what you mean by nested functions. If you want to define functions inside other functions that's supported by Scala so you can do that without further problems. However, if you want to nest function calls or even pass a function as an argument that's not supported for now in ShExML.

I hope this information can be helpful for you and do not hesitate to pose all the questions that you may still have.

Best wishes, Herminio García

PREFIX : <http://example.com/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX schema: <http://schema.org/>
SOURCE films_xml_file <http://shexml.herminiogarcia.com/files/films.xml>
SOURCE films_json_file <http://shexml.herminiogarcia.com/files/films.json>
FUNCTIONS helper <scala: https://raw.githubusercontent.com/herminiogg/ShExML/enhancement-%23121/src/test/resources/functions.scala>
ITERATOR film_xml <xpath: //film> {
    FIELD id <@id>
    FIELD name <name>
    FIELD year <year>
    FIELD country <country>
    FIELD directors <crew/directors/director>
    FIELD screenwritters <crew//screenwritter>
    FIELD music <crew/music>
    FIELD photography <crew/photography>
    ITERATOR actors <cast/actor> {
        FIELD name <name>
        FIELD role <role>
        FIELD film <../../@id>
    }
    ITERATOR actresses <cast/actress> {
        FIELD name <name>
        FIELD role <role>
        FIELD film <../../@id>
    }
}
ITERATOR film_json <jsonpath: $.films[*]> {
    PUSHED_FIELD id <id>
    FIELD name <name>
    FIELD year <year>
    FIELD country <country>
    FIELD directors <crew.director>
    FIELD screenwritters <crew.screenwritter>
    FIELD music <crew.music>
    FIELD photography <crew.cinematography>
    ITERATOR actors <cast[*]> {
        FIELD name <name>
        FIELD role <role>
        POPPED_FIELD film <id>
    }
}
EXPRESSION films <films_xml_file.film_xml UNION films_json_file.film_json>

:Films :[films.id] {
    :name [helper.allCapitals(films.name)] ;
    :year [helper.addOne(films.year)] ;
    :countryOfOrigin dbr:[films.country] ;
    :director dbr:[films.directors] ;
    :screenwritter dbr:[films.screenwritters] ;
    :screenwritterName [helper.getName(films.screenwritters)] ;
    :titleYear [helper.nameAndYear(films.name, films.year)] ;
    :musicBy dbr:[films.music] ;
    :cinematographer dbr:[films.photography] ;
    :actor @:Actor ;
    :actor @:Actress ;
}

:Actor :[films.actors.name] {
    :name [helper.getName(films.actors.name)] ;
    :surname [helper.getSurname(films.actors.name)] ;
    :nameParts [helper.getNameParts(films.actors.name)] ;
    :appear_on :[films.actors.film] ;
}

:Actress :[films.actresses.name] {
    :name [helper.getName(films.actresses.name)] ;
    :surname [helper.getSurname(films.actresses.name)] ;
    :nameParts [helper.getNameParts(films.actresses.name)] ;
    :appear_on :[films.actresses.film] ;
}
anaigmo commented 2 years ago

Hi Herminio,

Thanks for the answer, I'll make the next tests using the CLI then. Seeing the implementation of functions more in detail, seems like an expressive and concise way of specifying and using tailored functions :)

Regarding the issue with the nested functions, I'm not sure I follow your answer completely. Let's say, if I wanted to concatenate a string with the output of a lowercase function (concat("string1-", lowercase("STRING2)) to get string1-string2), that would be possible with the new functions feature, right? I think it's what you mean in the answer, but just to make sure.

Best, Ana

herminiogg commented 2 years ago

Hi Ana,

Yes, that was the idea and it seems that you can corroborate it :)

No, that's exactly what I was trying to explain. Currently this is not possible as supporting this behavior is a bit more complex (although I do not discard to include it in the future). So, neither a construction like function(argument1, functionB(argument2)) nor function(argument1, argument2, functionB) are supported right now.

However, it is possible to reach the same behavior with the following Scala code (using Scala nested functions):

class Helper {
    def concat(arg1: String, arg2: String): String = {
        def lowercase(arg: String): String = {
            arg.toLowerCase
        }

        arg1 + "-" + lowercase(arg2)
    }

or just using a more idiomatic (in my opinion) way of coding it:

class Helper {
    def concat(arg1: String, arg2: String): String = {
        arg1 + "-" + this.lowercase(arg2)
    }

    private def lowercase(arg: String): String = {
        arg.toLowerCase
    }
}

Let's say that I deliberately left the complexity to the Scala compiler in order not to complicate things too much on my end.

Best regards, Herminio García

anaigmo commented 2 years ago

Hi Herminio,

Ha, I got everything backwards. That solves my doubts around the issue, thanks a lot for the answer and the examples!

Best, Ana

herminiogg commented 2 years ago

No problem! Don't hesitate to open another issue if you have more questions or you want to discuss something else.

Best regards, Herminio García