red / REP

Red Enhancement Process
BSD 3-Clause "New" or "Revised" License
11 stars 4 forks source link

Function spec is insufficiently specified #24

Open meijeru opened 6 years ago

meijeru commented 6 years ago

The order of elements (argument specs, optional argument specs, and return spec(s)) in a function spec block is insufficiently determined -- there is no formal documentation, and the compiler and the intepreter treat this apparently differently, so that a formal spec cannot be derived from there. The most constraining formulation would be the following:

<function> ::=
   make function! [<function-spec> <function-body>]
 | func <function-spec> <function-body>
 | has [<argument>*] <function-body>
 | does <function-body>
 | function <function-spec> <function-body>
<function-spec> ::= [<docstring>° <argument-spec> <return-spec>°]
<docstring> ::= <string>
<argument-spec>  ::= <argument>* <optional-argument>*
<argument> ::=
   <argument-name> <argument-doc>°
 | <argument-name> [<typeset-element>*] <argument-doc>°
<argument-name> ::= <word-literal > | '<word-literal> | :<word-literal>
<argument-doc> ::= <string>
<optional-argument> ::= <refinement> <argument-doc>° <argument>*
<refinement> ::= /<word-literal>
<return-spec> ::= return: [<typeset-element>*]
<function-body> ::= <block>
<typeset-element> ::= <type-name> | <typeset-name>

Here ° means optional and * means zero or more, as usual.

meijeru commented 6 years ago

I offer the following questions for discussion: . should there be a prescribed order as suggested above, and if not, should the order be completely free? . should there be at most a single return spec? . can the type information, if present, be empty, and if so, does it mean any-type! ?

hiiamboris commented 6 years ago

can the type information, if present, be empty, and if so, does it mean any-type! ?

currently it is default! not any-type!, I suggest it's left like this (default! doesn't contain unset! and unset! can be used to make vararg funcs, which is a bad hack ☺)

meijeru commented 6 years ago

This is noted. Another question for discussion is whether the /local refinement must be last or not.

9214 commented 6 years ago

@meijeru I believe it should be:

<return-spec> ::= return: [<typeset-element>*] <argument-doc>°

@greggirwin, worth to extend your PR, because some functions in Red codebase now appear to have a malformed spec.

>> func [return: [] "malformed!"][]
*** Script Error: invalid function definition: return:
*** Where: func
*** Stack:  
greggirwin commented 6 years ago

@9214, yes, it makes sense to allow a doc-string for return:.

meijeru commented 5 years ago

As a contribution to the discussion: the parse rule for function spec in %help.red suggests that (1) refinements and return spec(s) may occur in any order, (2) there may be more than one return spec, and (3) the local refinement must be last. Is this a good interpretation?

hiiamboris commented 5 years ago

(2) More than one return: doesn't make any sense to me. In fact it's forbidden by both R/S compiler and Red compiler. Interpreter's logic is currently a mess that allows senseless things like this: f: func [return: [integer!] /x return: [string!]] [] to be considered valid yet as you know it does not accept docstrings for returns, but I don't believe multiple returns are intentional even there.

(1&3) Order of return and locals is only enforced by R/S right now, the other spec formats are orderless by their nature (using any [...] block) so these are likely bugs in help. I can see two lines of reasoning though:

Personally I'm more (60 to 40) inclined to support orderlessness.

greggirwin commented 5 years ago

Help doesn't currently support multiple return specs. The last one wins.

We should consider the func spec dialect as a work in progress. The basic rules are in place, but advanced and new capabilities need to be considered, as we are discussing here.

A reason to support more than one return is to specify what type may be returned if a given refinement is used. Is that useful enough to support it? If so, the compiler should be able to enforce it. It could also collect the various return types and group them for you into a single return typeset.

hiiamboris commented 5 years ago

A reason to support more than one return is to specify what type may be returned if a given refinement is used. Is that useful enough to support it? If so, the compiler should be able to enforce it

So imagine I declare a function like this:

f: func [
   x return: [integer!]
   /s return: [string!]
   /b return: [block!]
   /u return: [unset!]

What type will the result of f/s/b/u 100 be? Typeset from all of its returns? It's an interesting idea to consider. But no. I'm not buying it yet. So much complexity... for what? Besides, I have a feeling that enforcing this will only be possible by inserting a runtime check into the function code, resulting in extra overhead. Count me as an opponent ☺

greggirwin commented 5 years ago

Great example. For my part, I am good with keeping things simple here, because the user can always spec the type and put notes in the code. If we want to change that in the future, nothing breaks. And if we want a strict language, make it a dialect.

hiiamboris commented 4 years ago

I'm wondering if Red is going to support the attributes block, like REBOL did, e.g. func [[throw] x y] [...], or is there another design planned for this.

9214 commented 4 years ago

Worth addressing in function spec's (re)-design:

>> has ['x :y][]
== func [/local 'x :y][]
>> func [/local 'x :y][]
== func [/local 'x :y][]

This doesn't affect semantics in any way, and words with the same symbols are counted as locals.

dander commented 4 years ago

I've noticed that some function specs include the (incredibly useful) signature for function! args, but it's not currently possible to create a function! using the same spec. Maybe only red/system supports it currently?

>> function probe spec-of :parse []
["Process a series using dialected grammar rules" 
    input [binary! any-block! any-string!] 
    rules [block!] 
    /case "Uses case-sensitive comparison" 
    /part "Limit to a length or position" 
    length [number! series!] 
    /trace 
    callback [function! [
        event [word!] 
        match? [logic!] 
        rule [block!] 
        input [series!] 
        stack [block!] 
        return: [logic!]
    ]] 
    return: [logic! block!]
]
*** Script Error: invalid type specifier: event word! match? logic! rule block! input series! stack block! return logic!
*** Where: function
*** Stack:  
hiiamboris commented 4 years ago

https://github.com/red/red/pull/4347#issuecomment-697303029