Page 1 of 2 12 LastLast
Results 1 to 10 of 12

Thread: Idea about "pseudo-variable" target functions

  1. #1

    Idea about "pseudo-variable" target functions

    Anyone who has used PL/1 is familiar with what they call "pseudo-functions", which in modern terms are what we would now call objects with assignment-operator semantics. PL/1 calls them pseudo-variables because they can be assigned-to the same way as conventional variables can. For example, Basic has a MID$ function, which corresponds to a PL/1 SUBSTR. Examples:

    PL/1:
    
    DECLARE (ONE,TWO) CHAR(4);
    SUBSTR(ONE,1,2) = SUBSTR(TWO,3,2);
    
    Basic:
    
    DIM ONE, TWO AS STRING
    MID$ (ONE,1,2) = MID$(TWO,3,2)
    
    ThinBasic calls the MID$ on the left side a "MID$ Statement", since there is no other grammatical role that could be given to it. This is all well and good as far as MID$ is concerned, but in language terms, it is "cheating". That is, this technique works great for MID$, and for the few other functions that have an alternative "statement" form to them, but it doesn't help me if I want to make my OWN "statement-like" functions. I just can't do it. ThinBasic creates this capability, but reserves it only for itself, not allowing users to write their own such syntax.

    I believe it would be of benefit to allow this technique to be applied on a general basis.

    In order to do that, a new procedural construct would have to be introduced. Along with SUB procedures and FUNCTION procedures, there would be TARGET procedures.

    In some ways, a TARGET is like a "setter" function, a concept sometimes used in languages that support "accessors" or "attribute functions".

    One possible use of a FUNCTION/TARGET pair is to define access to an array with bounds that do not start with 1. Another use might be to define "mapping" or "associative storage" functions which store data based on keys. The Dictionary module implements such logic, but targets would allow the use of Dictionaries in a much more natural notation.

    A TARGET would have the following characteristics:

    • A target is a SUB-like block of code, begun with TARGET and ending with END TARGET
    • A target accepts a parameter list like a SUB procedure does
    • A target has an AS type-name clause, like a FUNCTION does. However, for a TARGET, the AS type-name specifies the expected type of value to be assigned to the target. For example, a SUBSTR-like target would have an AS STRING clause, since it would be expected that only strings would be assigned to the target.
    • A target may have the same name as a FUNCTION. Thus, the same program may have both a FUNCTION ABC and a TARGET ABC.
    • When both a FUNCTION and a TARGET exist with the same name, the implementation code for these two procedures are completely different and independent. It is up to the developer to decide how the two procedures relate to each other.
    • A target is only recognized as being referenced when it appears on the left side of an = sign
    • Inside the code of a target, a built-in function would be required to access the value on the right-hand side of the = sign. This built-in function could have any appropriate name, such as RHS, VALUE, SOURCE, etc. The exact name isn't as critical as the fact that this function is needed. For sake of discussion, let's say this function is called RHS.
    • In a TARGET, the RHS function is polymorphic, in the sense that its type is always the same as the AS type-name clause on the TARGET header statement. For a given TARGET procedure, RHS has only one type, and so is not the same thing as a VARIANT. There is no need to define multiple spellings, such as RHS, RHS$, etc. because the type of RHS is known by the AS type-name clause.
    • If a TARGET had knowledge of compound assignment operators, such as +=, it would open up a number of intriguing possibilities. However, it would also make the semantics more complex, perhaps prohibitively so. For that reason, this proposal does not define semantics for implementing compound assignment operators, except in the most simple way. That is, if ABC is both a function and a target taking one numeric parameter, then ABC(5) += 1 would be interpreted as ABC(5) = ABC(5) + 1, where the ABC on the left side of the = (and the left of the original += ) would be a reference to the TARGET, and the right-hand ABC is a reference to the FUNCTION of the same name.


    Simple (though not terribly useful) example: Define access to a zero-based array:

    DIM __MYHEX(16) AS STRING = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
    
    FUNCTION MYHEX (N AS LONG) AS STRING
        IF N >= 0 AND N <= 15 THEN RETURN __MYHEX(N+1)
        RETURN ""
    END FUNCTION
    
    TARGET MYHEX (N AS LONG) AS STRING
        IF N >= 0 AND N <= 15 THEN __MYHEX(N+1) = RHS
        ' error handler could be placed here for N out of range
    END TARGET 
    
    DIM I AS LONG
    FOR I = &H0A TO &H0F
        ' in next statement, left-hand MYHEX is a target, while right-hand MYHEX is a function:
        MYHEX(I) = LCASE$ (MYHEX (I))
    NEXT
    

  2. #2
    If function overloading overloading is supported then pseudo-variables can be expressed quite easily, by loosening the call syntax. Essentially, replacing a "," with ")="

    For instance these two expressions do the same thing:

    f(1,2*f(2))

    f(1)=2*f(2)

    code:

    dim as int aa[4]={10,20,30,40}


    function f(byval i as int , byval a as int)
    aa[i]=a
    end function


    function f(byval i as int ) as int
    return aa[i]
    end function



    f(1,2*f(2))


    print f(1) 'result: 40


    f(1)=2*f(2)


    print f(1) 'result 40


    To enforce pseudo-variable syntax, '=' could be placed in the prototype like this

    function f(byval i as int ,= byval a as int)
    ...
    Last edited by Charles Pegge; 13-07-2013 at 08:25.

  3. #3
    Your comment about putting an = sign in the function prototype gives me an idea.

    Right now, a function can set a return value by assigning it to the keyword FUNCTION. Let's rewrite your (conventional) function that way:

    DIM AA(4) AS INT = 10,20,30,40
    
    FUNCTION F (BYVAL I AS INT) AS INT
        FUNCTION = AA (I)
    END FUNCTION
    
    Note that in a (conventional) function, the AS clause on the FUNCTION level corresponds to a RETURNS keyword in languages like PL/1, where it describes the data type of the returned value.

    Now, the "target" function can be rewritten as the reverse of the (conventional) function, by using FUNCTION to represent the 'source' of the data that appears on the right hand side of the = sign. That way, it's not necessary to invent a new keyword like RHS, VALUE or SOURCE.

    The only thing left is to define some kind of syntax to show this is a 'target' function instead of a conventional one. We can do that also without a new keyword, by replacing the AS keyword on the FUNCTION level with an = sign, like this:

    FUNCTION F (BYVAL I AS INT) = INT    ' the = means this is a target function
        AA (I) = FUNCTION                ' FUNCTION means the R.H.S. of the = sign
    END FUNCTION
    
    
    ' use the target and conventional functions:
    
    F(1) = 2 * F(2)
    
    
    PRINT F(1)    'result: 40
    

    One final point: I originally wrote about using target functions in combination with compound operators like +=. Because a target function and a conventional one of the same name have different implementation code, there's nothing to prevent the two things from having different function signatures. However, if you used a target with a compound assignment, the two signatures would have to be compatible.

    So, you could have a function
    FUNCTION ABC (A AS INT) AS INT
    and a target
    FUNCTION ABC (A AS INT, B AS INT) = INT

    but if you tried to say ABC(1,2) += 1 it wouldn't work, because this would imply
    ABC(1,2) = ABC(1,2) + 1
    and while there a target compatible with ABC(1,2) there's no conventional function that could be called as ABC(1,2), so this would be a syntax error.

    That means, in most cases, when there is both a target and a conventional function defined, they would pretty much have to have compatibe signatures.
    Last edited by Robert Hodge; 13-07-2013 at 15:55.

  4. #4
    thinBasic MVPs
    Join Date
    Oct 2012
    Location
    Germany
    Age
    54
    Posts
    1,530
    Rep Power
    171
    Reading this brought me to another idea...

    It's very often the case that one calls a (user-defined) function and needs that same result a few times in a row. So not to call the same function again one had to store that result local somehow. Now I don't know how this works - if functions result is laying on a stack or cache until it gets passed back and if it gets destroyed then or if it stays static as some functions-variable unchanged until the function gets called again where it gets new initialized.

    It would be nice to "recall" the last functions result whithout running the function again

    small example:
    If myFunc(1,2,3) < 0 Then
    '...
    ElseIf Function_RecallResult("myFunc") > 0 Then
    '...
    Else
    '...
    Endif
    
    I think there are missing some Forum-sections as beta-testing and support

  5. #5
    The trick would be in deciding how to 'refer' to the prior function results.

    Let's take your example:

    ' example 1
    If myFunc(1,2,3) < 0 Then
    '... 
    ElseIf Function_RecallResult("myFunc") > 0 Then
    '... 
    Else
    '... 
    Endif
    
    This is essentially the same as:

    ' example 2
    DIM RESULT AS NUMBER =  myFunc(1,2,3)
    If RESULT < 0 Then
    '... 
    ElseIf RESULT > 0 Then
    '... 
    Else
    '... 
    Endif
    
    But, of course, the point is that you don't WANT to declare the result; you just want to be able to use it.

    There are a lot of ways you could go about doing this, but to get this done with the least impact to the syntax, I believe the easiest way to do it would be with an "in-line assignment declaration". The idea is that you 'name' the result in a very concise way, and then you can quickly reuse it. Suppose we use inline syntax of AS within an expression to mean (a) declare the variable on the left side of AS to be of the same type as the expression on the right side of the AS and then (b) assign the expression to the variable, and finally (c) use the value of the expression in the remainder of the statement.

    In C, you can do something *like* this:

    /* C example */
    int F;
    if ((F = myFunc(1,2,3) < 0) {
      /* ... */
    }
    else if (F > 0) {
      /* ... */
    }
    else {
      /* ... */
    }
    
    But, depending on the C/C++ version you have, you may or may not be able to *declare* F, and sometimes the 'scope' of F gets 'cut short' before you are done using it.

    Let's rewrite your example using this "in-line assignment declaration" idea:

    ' example 3
    If F AS myFunc(1,2,3) < 0 Then
    '... 
    ElseIf F > 0 Then
    '... 
    Else
    '... 
    Endif
    
    The main issue is deciding on a syntax. Using AS is just a suggestion.

    Other possible syntax could perhaps use :: or == for this:

    If F :: myFunc(1,2,3) < 0 Then
    If F == myFunc(1,2,3) < 0 Then
    
    The exact syntax isn't as critical as getting a correct design.
    Last edited by Robert Hodge; 18-07-2013 at 15:59.

  6. #6
    thinBasic MVPs
    Join Date
    Oct 2012
    Location
    Germany
    Age
    54
    Posts
    1,530
    Rep Power
    171
    Maybe user-defined functions (probably not RGB() or other already built-in stuff) could work alike this:

    PrintL f   ' prints 123
    PrintL Function_RecallResult("f") ' prints 123
    PrintL f  ' prints 246...
    
    
    Function Static f() as Long
    
      Function += 123
    
    End Function
    
    ... could offer a way to carry some value from one local scope to another also...
    Last edited by ReneMiner; 18-07-2013 at 21:42.
    I think there are missing some Forum-sections as beta-testing and support

  7. #7
    I think it's important not to get too carried away with the syntax. After all, the main idea is that you are trying to accomplish two things here. First, you are trying to save some typing, and you hope to avoid calling the same function twice.

    Let's go back to the example. Instead of naming the function, let's just say that if a function call was prefixed with @, it means you want to use it again in some forward reference by just the @ sign alone. That would make the function look like this:

    If @myFunc(1,2,3) < 0 Then
    '...  
    ElseIf @ > 0 Then
    '...  
    Else
    '...  
    Endif
    
    If you had more than one function you wanted to reference as a 'shorthand' you might need more syntax than this - maybe a named reference like F@myFunc:

    If F@myFunc(1,2,3) < 0 Then
    '...  
    ElseIf F@ > 0 Then
    '...  
    Else
    '...  
    Endif
    
    As for the basic question of saving execution time, you are talking about an optimization effort. For an interpreter, it's not likely that optimizing it would be worth the effort, when you could simply assign the function result to a local variable and then reuse it.

    In PL/1, they have a concept called REDUCIBLE and IRREDUCIBLE. If you have a function that is REDUCIBLE, and it's using with the same parameters, the results of the prior call are saved and then reused. In your example, the call to myFunc(1,2,3) is 'remembered' and then if the function is called with 1,2,3 again, the prior returned value is used again. When a function modifies BYREF parameters, or uses STATIC data, or perhaps works with transient data like timer values, or deals with values created in other threads, then the same passed parameters won't guarantee the same results, which means the function is IRREDUCIBLE.

    In the case of PL/1, it took IBM a long time to implement the IRREDUCIBLE feature. It is probably too much to ask for thinBasic to do something like that, when its primary function to be an interpreter. Trying to detect and optimize 'reducible' functions would probably take more time than it would take to just run them. And, any function that was so complex that making it 'reducible' might help some is a situation that the developer would realize too, and they would just save off the value if it were needed to be reused.

    But, suppose you really needed to optimize the function, and there wasn't anything in thinBasic to help you? You could do it yourself, by writing a "front-end" function that cached prior operands and results. If any parameters were repeated, you could pass back the cached results.

    Now, let's say you had some complex function you wanted cached this way, and you wanted thinBasic to help. That would be a relatively straight-forward change to the syntax.

    Example: Suppose you wanted the last 9 passed parameters and the result saved internally, so that if they were repeated you'd get the same results. You could do this by putting a CACHE option on the FUNCTION header:

    FUNCTION myFunc (A AS LONG, B AS LONG, C AS LONG) WITH CACHE(9) AS LONG
    ' ...
    END FUNCTION
    
    Then, each time that myFunc(1,2,3) is called, for instance, code in the beginning of the function would do a table lookup of the parameters, and if a match was found, the same result calculated before would be returned again.

    In this case, the contents of the 'CACHE' would essentially look like this internally:

    TYPE CACHE_TYPE
        RESULT AS LONG
        A AS LONG
        B AS LONG
        C AS LONG
    END TYPE
    
    DIM CACHE(9) AS CACHE_TYPE
    STATIC LAST AS LONG = 0   ' keeps number of saved entries in cache
    STATIC CURR AS LONG = 0    ' next slot to be used   
    
    ' on entry to function, determine if there is a cache hit:
    
    DIM I AS LONG
    
    FOR I = 1 TO LAST
        IF A = CACHE.A(I) AND A = CACHE.A(I) AND A = CACHE.A(I) THEN
            RETURN CACHE.RESULT(I)
        END IF
    NEXT
    
    CURR += 1
    IF CURR > 9 THEN CURR = 1
    IF CURR > LAST THEN LAST = CURR
    
    CACHE.A(CURR) = A
    CACHE.B(CURR) = B
    CACHE.C(CURR) = C
    
    ' ... remainder of function
    
    Last edited by Robert Hodge; 18-07-2013 at 23:06.

  8. #8
    thinBasic MVPs
    Join Date
    Oct 2012
    Location
    Germany
    Age
    54
    Posts
    1,530
    Rep Power
    171
    In most cases such function runs a few hundred or thousand times so in the end it would be using up more time to check if those parameters were passed already and also the function could calculate with different other globals then. Such cache is not secure i think
    Last edited by ReneMiner; 18-07-2013 at 23:07.
    I think there are missing some Forum-sections as beta-testing and support

  9. #9
    Quote Originally Posted by ReneMiner View Post
    In most cases such function runs a few hundred or thousand times so in the end it would be using up more time to check if those parameters were passed already and also the function could calculate with different other globals then. Such cache is not secure i think
    Yes, and that would be the deciding factor. Only the developer would really know if the same arguments would ever produce different results. Suppose the function were to fetch data from a read-only file, but locating it was time-consuming. A cache might make sense then.

    The issue is no different than trying to save the results locally from a function and reusing them.

    Example:

    DIM RESULT = MYFUNCTION(1,2,3)
    
    IF RESULT > 0 THEN PERFORM_SOME_ACTION ()
    
    ' ... several statements later ...
    
    IF RESULT > 0 THEN PERFORM_SOME_ACTION ()    ' ... is this still valid ?
    
    So, even without any change to thinBasic, you can cache results yourself, and you still have the same question to answer: Are the cached results still valid?

    This is where it goes beyond grammar and language and syntax; it's a matter of proper system design.

  10. #10
    Quote Originally Posted by John Spikowski View Post
    OT

    @Rene & Robert - Have you guys thought about collaborating and write a BASIC of your own? You could call it LikeBasic and roll all those cool ideas into a BASIC (like) language without any restrictions. (or direction) I would suggest writing it in OxygenBasic for the most flexibility and least risk. (open source with a bright author)
    Some of the ideas I have kicked around are from another language I have been trying to design, off and on, for a number of years.

    As for the Basic-specific ideas, I am actually hopeful that Eros will, some day, take a look at trying to incorporate some of them into thinBasic. My main interest here is because we use TB as a script language for our editor SPFLite (see www.spflite.com for info). The ideas, if implemented, would be helpful in oiur use of TB for editor macros.

Page 1 of 2 12 LastLast

Similar Threads

  1. Idea about "VAR ARG" parameters
    By Robert Hodge in forum Suggestions/Ideas discussions
    Replies: 8
    Last Post: 05-07-2013, 17:55
  2. Is there a need for a new thinBasic-specific "idea" forum?
    By Robert Hodge in forum QOD: Question Of the Day
    Replies: 4
    Last Post: 10-06-2013, 22:53
  3. Replies: 17
    Last Post: 21-02-2010, 07:45
  4. Uses "File", "Crypto" ... ???
    By marcuslee in forum thinBasic General
    Replies: 3
    Last Post: 01-12-2009, 19:38

Members who have read this thread: 0

There are no members to list at the moment.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •