Yet Another Scripting Language - Routines 
Author Message
 Yet Another Scripting Language - Routines

This article is a continuation of the discussion of "yet another
scripting
language", a scripting language that I am proposing to create.  The
ongoing presentation is broken up into sections, each section
discussing
some aspect of the proposed language.  This article is (with the
exception of a few minor updates) about procedures, functions,
and other executable routines.  Future articles will discuss
scope of variables, program structuring devices, etc.  In the
meantime take it as given that there won't be globals.

The articles are not definitive specifications; rather, they are
working
documents exploring possibilities.  

There are three prior threads in comp.lang.misc that are devoted to
this
projected language.  The thread titles are:

    Yet Another Scripting Language - Syntax thoughts
    Yet another scripting language - arithmetic
    Yet another scripting language - formalizing flow control
    Yet another scripting language - Variables, function invocation,
and some syntax
    Yet Another Scripting Language - Reprise

There are three web pages that summarize my thoughts on said topics at
the
end of said threads.  These pages are:

    http://www.*-*-*.com/ ~cri/2001/newlang01.html
    http://www.*-*-*.com/ ~cri/2001/newlang02.html
    http://www.*-*-*.com/ ~cri/2003/newlang03.html

The current interim name for this language is YASL(Yet Another
Scripting
Language - pronounced yazzle).  Suggestions for a better name are
solicited.

5.0     Miscellaneous updates

5.0.1   Comments, comment function, and comment blocks

Putting the '#' character as the first non-white character in
a line makes the entire line a comment.  The '#' character is
also the name of the comment function, which may appear anywhere
in the program text.  A comment block starts with 'begin' with
either '#' or 'comment' as an argument, contains an indented
block of comments, and ends with the corresponding end statement.  
For example:

        begin #
                This is a comment.
                It is only a comment.
                If it were code it would
                Still be a comment.
        end #

5.0.2   Infix operators

Functions with two arguments can be converted into an infix
operation by appending a question mark to the function name.  
For example

        <lt x y>        <# Yields true if x is less than y>

is equivalent to

        (x lt? y)       <# Also yields true if x is less than y>

In general (i.e., I haven't spelled out rules yet) missing
arguments are filled out from the enclosing scope.

5.0.3   Change order of operation in sequents

Sequents now look like this:

        [source | op ...]

For example,

        [1...100 | step 2 | lt? 10]

produces the sequence [ 1, 3, 5, 7, 9]

The resemblence to unix pipes is not entirely accidental.

5.1     A bestiary of identifier bound executable code aggregates

The term, identifier bound executable code aggregate, is
certainly clumsy, but I don't know of a good alternative.  Terms
such as "procedure" have narrow connotations due to their use in
mainstream programming languages.  For the sake of convenience I
will call them routines.

What, then, is a routine?

A routine is a block of code with an associated identifier, code
that can accept control (can be invoked, activated, or
equivalent).  Routines include functions, procedures,
coroutines, tasks, event handlers, data flow components, and goto
acceptance blocks.  

Fibers can be distinguished between those that are activated with
the call/return scheme (traditional functions and procedures) and
those event activated (event handlers, exception handlers, data
flow elements).  Likewise they can be distinguished between those
that can be resumed and those that cannot.

5.1.1   Functions and procedures

Functions and procedures are the work horses of procedural
languages.  The two forms are closely related, and some
languages, e.g. C, fuzz the two together.  In practice, however,
they are distinct.  Functions have values that can appear in
expressions; procedures do not.  An example of a function
invocation is

        x := <cos theta>        <# yasl syntax>
        x  = cos(theta);        /* C syntax */

In yasl I take the view that functions cannot alter variables in
their calling environment, i.e., function invocations use call by
copy.

An example of a procedure invocation is

        call sort data          <# yasl syntax >
        sort(data);             /* C syntax */

With procedures I take the view that procedures can alter
variables in their calling environment, i.e., procedure
invocations use call by reference.  Having two different modes of
argument semantics is a possible source of confusion; however
call by reference (or call by address) is a practical necessity
for procedures.

I am not enthralled about using the "call" keyword and may omit
it; however I'm of the view that the syntax for function
invocation should be different from that of procedure invocation.

5.1.2   Coroutines and threads

For the most part coroutines are not supported in the popular
languages.  As an example of coroutines:

        coroutine produce
            loop thing in <getstuff>[]
                <edible? thing> => resume consume thing
                 end loop
            end produce

        coroutine consume goodie
            loop
                call eat goodie
                resume produce
                end loop
            end consume

If call/return semantics were used instead of suspend/resume
semantics (replace "resume" by "call" and "coroutine" by
"procedure") the activation records stack would grow indefinitely
with each cycle of produce/consume.  This outcome can be avoided
by either (a) making one of the {produce, consume} pair
subordinate to the other or (b) both subordinate to a superior
executive routine.  The "catch" in having coroutines is that the
execution cannot use a simple stack of activation records.  

A similar consideration holds with threads (tasks).  Since
terminology varies I will consider a thread to be the activation
of a base routine as the head of an activation records stack
(obscure wording alert).  

An issue with threads is flow control.  Coroutines are either
called or resumed.  They either suspend themselves or resume
another coroutine.  Threads presumably can be activated by event
handlers, exception handlers, direct activation/resumption from
another thread, and indirectly as the result of another thread
suspending itself.

Upon reflection my thought is that rather than having coroutines
and/or threads yasl will have a rather more elaborate routine that
I will call an agent.

5.1.3   Agents and Dispatchers

An agent (in yasl) is an active thread of control with a single
stack of activation records.  Associated with the agent is a task
description list describing the kinds of tasks that the agent can
perform.  For each kind of task there is a corresponding task
handler.  Also associated with the agent is a priority queue
(heap) of tasks to be performed.  Tasks to be performed can be
added and deleted either by the agent (including its task
handlers) or by an external dispatcher.  Agents can define
subagents; if they do they can also contain a dispatcher for the
subagents.

A dispatcher is a passive element that "supervises" a set of
agents.  Basically it is a dispatch table; for each kind of event
it specifies changes in task priority queues of the agents it is
responsible for.  

5.1.4   Data Flow Elements

A data flow element is a routine with 0 or more input ports
and 0 or more output ports.  Input ports accept data from
input streams; output ports emit data to outputs. Data
flow elements are activated whenever there is input on an input
port.  Inputs and outputs are streams; however block markers to
delimit blocks can be accepted and emitted.

In yasl input ports are labelled $in0, $in1, etc, and output
ports are labelled $out0, $out1, etc.

The current value in an input port is referenced by the port
name, e.g., $in0.  The content of remainder of a block is
referenced by appending brackets, e.g., $in0[].

Emitting the contents of an input port advances the input.  The
emit command sends data to $out0; emitn sends data to $outn.

Data flow elements are called operators.  An example of a data
flow element, one that copies its input into its output, is:

        operator cat
            emit $in0
        end cat

5.1.5   Sequent operators

Sequent operators are special cases of data flow elements that
satisfy these conditions:

There are two input ports, $in0 and $in1, and one output port
$out0.  Inputs are presumed to be blocked.  When invoked in a
sequent $in0 comes from the piped input and $in1 comes from the
argument list.  $out0 is sent to the output pipe.  

Here are examples of sequent operator programming.  The first
routine is an implementation of quick sort; the second routine is
an implementation of merge sort.

    operator qsort
        pivot   :=  $in0
        small[] := [$in0[ ] | lt? pivot | qsort]
        big[]   := [$in0[ ] | gt? pivot | qsort]
        same[]  := [$in0[ ] | eq? pivot        ]
        emit [small[], same[], big[]]
        end qsort

    operator msort
        operator merge
            caseof
                <empty? $in0> => emit $in1[]
                <empty? $in1> => emit $in0[]
                $in0 lt? $in1 => emit $in0
                else          => emit $in1
                end caseof
            end merge
        cut $in0[] $in0.$len/2
        emit [[cut.head[] | msort]| merge [cut.tail[] | msort]]
        end msort


http://www.*-*-*.com/ ~cri, http://www.*-*-*.com/
Optimists think the glass is half full, pessimists think it's half
empty, and engineers think it's twice as big as it ...

read more »



Thu, 05 Jan 2006 15:29:25 GMT  
 Yet Another Scripting Language - Routines

Quote:
> For the most part coroutines are not supported in the popular
> languages.  As an example of coroutines:

I'm happy to see more coroutine aware languages - I didn't really know about
them until I used Io (http://www.iolanguage.com).  They are much more
efficient than threads.  I think Java threads have made us realize the
dreads of threads.  I think Erlang has the most efficient user threads, but
the language is not well suited for visual or component design.

- Can you tell us why you chose lt?, gt?, etc. instead of <, > and <cos
theta> instead of cos(theta)?
- Can you explain 'morphs' in more detail?

Also, can you describe why you are writing your own language?  What do
others lack?  I've been working on mine here and there, a kind of mix
between Self, python and Ruby.  It's syntax is probably closest to Python
and Ruby, but with keyword arguments, prototypes, predicate dispatching and
bit of a functional programming feel.  The latter is not yet implemented.

1..10,1..10 doSequence: i, j
  puts 'i * j = ', i * j

would be:

for( int i = 1; i <= 10; i++ )
  for( int j = 1; j <= 10; j++ )
    printf( "i * j = %d\n", i * j );

method doit: <string> object [length > 100]
  puts 'a long string'

would be:

void doit( Object* object ) {
  if( object->type ) == STRING )
    if( object->length() > 100 )
      puts( "a long string" );

I like your quote:

Quote:
> Optimists think the glass is half full, pessimists think it's half
> empty, and engineers think it's twice as big as it needs to be.

It's true.

~ Mike ~


Quote:

> This article is a continuation of the discussion of "yet another
> scripting
> language", a scripting language that I am proposing to create.  The
> ongoing presentation is broken up into sections, each section
> discussing
> some aspect of the proposed language.  This article is (with the
> exception of a few minor updates) about procedures, functions,
> and other executable routines.  Future articles will discuss
> scope of variables, program structuring devices, etc.  In the
> meantime take it as given that there won't be globals.

> The articles are not definitive specifications; rather, they are
> working
> documents exploring possibilities.

> There are three prior threads in comp.lang.misc that are devoted to
> this
> projected language.  The thread titles are:

>     Yet Another Scripting Language - Syntax thoughts
>     Yet another scripting language - arithmetic
>     Yet another scripting language - formalizing flow control
>     Yet another scripting language - Variables, function invocation,
> and some syntax
>     Yet Another Scripting Language - Reprise

> There are three web pages that summarize my thoughts on said topics at
> the
> end of said threads.  These pages are:

>     http://home.tiac.net/~cri/2001/newlang01.html
>     http://home.tiac.net/~cri/2001/newlang02.html
>     http://home.tiac.net/~cri/2003/newlang03.html

> The current interim name for this language is YASL(Yet Another
> Scripting
> Language - pronounced yazzle).  Suggestions for a better name are
> solicited.

> 5.0     Miscellaneous updates

> 5.0.1   Comments, comment function, and comment blocks

> Putting the '#' character as the first non-white character in
> a line makes the entire line a comment.  The '#' character is
> also the name of the comment function, which may appear anywhere
> in the program text.  A comment block starts with 'begin' with
> either '#' or 'comment' as an argument, contains an indented
> block of comments, and ends with the corresponding end statement.
> For example:

>         begin #
>                 This is a comment.
>                 It is only a comment.
>                 If it were code it would
>                 Still be a comment.
>         end #

> 5.0.2   Infix operators

> Functions with two arguments can be converted into an infix
> operation by appending a question mark to the function name.
> For example

>         <lt x y>        <# Yields true if x is less than y>

> is equivalent to

>         (x lt? y)       <# Also yields true if x is less than y>

> In general (i.e., I haven't spelled out rules yet) missing
> arguments are filled out from the enclosing scope.

> 5.0.3   Change order of operation in sequents

> Sequents now look like this:

>         [source | op ...]

> For example,

>         [1...100 | step 2 | lt? 10]

> produces the sequence [ 1, 3, 5, 7, 9]

> The resemblence to unix pipes is not entirely accidental.

> 5.1     A bestiary of identifier bound executable code aggregates

> The term, identifier bound executable code aggregate, is
> certainly clumsy, but I don't know of a good alternative.  Terms
> such as "procedure" have narrow connotations due to their use in
> mainstream programming languages.  For the sake of convenience I
> will call them routines.

> What, then, is a routine?

> A routine is a block of code with an associated identifier, code
> that can accept control (can be invoked, activated, or
> equivalent).  Routines include functions, procedures,
> coroutines, tasks, event handlers, data flow components, and goto
> acceptance blocks.

> Fibers can be distinguished between those that are activated with
> the call/return scheme (traditional functions and procedures) and
> those event activated (event handlers, exception handlers, data
> flow elements).  Likewise they can be distinguished between those
> that can be resumed and those that cannot.

> 5.1.1   Functions and procedures

> Functions and procedures are the work horses of procedural
> languages.  The two forms are closely related, and some
> languages, e.g. C, fuzz the two together.  In practice, however,
> they are distinct.  Functions have values that can appear in
> expressions; procedures do not.  An example of a function
> invocation is

>         x := <cos theta>        <# yasl syntax>
>         x  = cos(theta);        /* C syntax */

> In yasl I take the view that functions cannot alter variables in
> their calling environment, i.e., function invocations use call by
> copy.

> An example of a procedure invocation is

>         call sort data          <# yasl syntax >
>         sort(data);             /* C syntax */

> With procedures I take the view that procedures can alter
> variables in their calling environment, i.e., procedure
> invocations use call by reference.  Having two different modes of
> argument semantics is a possible source of confusion; however
> call by reference (or call by address) is a practical necessity
> for procedures.

> I am not enthralled about using the "call" keyword and may omit
> it; however I'm of the view that the syntax for function
> invocation should be different from that of procedure invocation.

> 5.1.2   Coroutines and threads

> For the most part coroutines are not supported in the popular
> languages.  As an example of coroutines:

>         coroutine produce
>             loop thing in <getstuff>[]
>                 <edible? thing> => resume consume thing
>                  end loop
>             end produce

>         coroutine consume goodie
>             loop
>                 call eat goodie
>                 resume produce
>                 end loop
>             end consume

> If call/return semantics were used instead of suspend/resume
> semantics (replace "resume" by "call" and "coroutine" by
> "procedure") the activation records stack would grow indefinitely
> with each cycle of produce/consume.  This outcome can be avoided
> by either (a) making one of the {produce, consume} pair
> subordinate to the other or (b) both subordinate to a superior
> executive routine.  The "catch" in having coroutines is that the
> execution cannot use a simple stack of activation records.

> A similar consideration holds with threads (tasks).  Since
> terminology varies I will consider a thread to be the activation
> of a base routine as the head of an activation records stack
> (obscure wording alert).

> An issue with threads is flow control.  Coroutines are either
> called or resumed.  They either suspend themselves or resume
> another coroutine.  Threads presumably can be activated by event
> handlers, exception handlers, direct activation/resumption from
> another thread, and indirectly as the result of another thread
> suspending itself.

> Upon reflection my thought is that rather than having coroutines
> and/or threads yasl will have a rather more elaborate routine that
> I will call an agent.

> 5.1.3   Agents and Dispatchers

> An agent (in yasl) is an active thread of control with a single
> stack of activation records.  Associated with the agent is a task
> description list describing the kinds of tasks that the agent can
> perform.  For each kind of task there is a corresponding task
> handler.  Also associated with the agent is a priority queue
> (heap) of tasks to be performed.  Tasks to be performed can be
> added and deleted either by the agent (including its task
> handlers) or by an external dispatcher.  Agents can define
> subagents; if they do they can also contain a dispatcher for the
> subagents.

> A dispatcher is a passive element that "supervises" a set of
> agents.  Basically it is a dispatch table; for each kind of event
> it specifies changes in task priority queues of the agents it is
> responsible for.

> 5.1.4   Data Flow Elements

> A data flow element is a routine with 0 or more input ports
> and 0 or more output ports.  Input ports accept data from
> input streams; output ports emit data to outputs. Data
> flow elements are activated whenever there is input on an input
> port.  Inputs and outputs are streams; however block markers to
> delimit blocks can be accepted and emitted.

> In yasl input ports are labelled $in0, $in1, etc, and output
> ports are

...

read more »



Fri, 06 Jan 2006 13:08:27 GMT  
 Yet Another Scripting Language - Routines
On Sun, 20 Jul 2003 22:08:27 -0700, "Vis Mike"

Quote:



>> For the most part coroutines are not supported in the popular
>> languages.  As an example of coroutines:

>I'm happy to see more coroutine aware languages - I didn't really know about
>them until I used Io (http://www.iolanguage.com).  They are much more
>efficient than threads.  I think Java threads have made us realize the
>dreads of threads.  I think Erlang has the most efficient user threads, but
>the language is not well suited for visual or component design.

>- Can you tell us why you chose lt?, gt?, etc. instead of <, > and <cos
>theta> instead of cos(theta)?

I tend toward the lisp style where a function is the first thing in a
parenthesized list.  I also like a language character set to not be
overloaded.  My thought is to let each pair of delimiters provided by
the character set to have a different function.  Thus:

        () grouping
        <> function invocation
        [] iterators/arrays
        {} string substitution

Also lt,gt et al are all functions.  Adding a question mark at the end
is a convention for using them in infix form.

Quote:
>- Can you explain 'morphs' in more detail?

See the cut and pasted description at the end.

Quote:

>Also, can you describe why you are writing your own language?  

It's a vice I have - I've done it before.  In part it's because I have
a number of projects in mind where it would be convenient to have my
hands under the hood, so to speak.  In part because I want to do some
experimental programming.  In part I want the language I needed two
decades ago. Et cetera.

Quote:
>What do
>others lack?  

Depends on the language; name a feature and you can find a language
that has it.  Name a defect and you can find a language that has it.

Quote:
>I've been working on mine here and there, a kind of mix
>between Self, Python and Ruby.  It's syntax is probably closest to Python
>and Ruby, but with keyword arguments, prototypes, predicate dispatching and
>bit of a functional programming feel.  The latter is not yet implemented.

>1..10,1..10 doSequence: i, j
>  puts 'i * j = ', i * j

>would be:

>for( int i = 1; i <= 10; i++ )
>  for( int j = 1; j <= 10; j++ )
>    printf( "i * j = %d\n", i * j );

>method doit: <string> object [length > 100]
>  puts 'a long string'

>would be:

>void doit( Object* object ) {
>  if( object->type ) == STRING )
>    if( object->length() > 100 )
>      puts( "a long string" );

>I like your quote:
>> Optimists think the glass is half full, pessimists think it's half
>> empty, and engineers think it's twice as big as it needs to be.
>It's true.

>~ Mike ~

4.4     The morph concept

"Morph" is a neologism, a term I've invented to describe an
approach I haven't seen elsewhere.  A morph is an entity tied to
an identifier that can have any of several types (and values)
depending on the context.  These different context dependent
values are call aspects. As an illustration let foo be a morph.  
Then

  print foo             // Prints the string aspect of foo
  foo := x + y          // The number aspect of foo is set to x+y
  x   := <foo y>        // The function aspect of foo is invoked

All morphs are guaranteed to have a default value aspect and an
executable function aspect.  Aspects need not have any particular
relationship to each except for the following rule:

        When the function aspect is invoked the default
        value aspect is set to the return value of the
        function aspect.  The execution of the function aspect
        updates the entire state of the morph.

In addition to aspects a morph can also have attributes.  These
attributes are in turn morphs; they are designated by qualified
names separated by periods.  Since the attributes are morphs
they in turn can have attributes that are morphs.  Here is an
example:

  circle                // Identifier for a morph representing
                        // a circle.
  circle.radius         // Attribute of circle - its radius

  circle.center         // Attribute of circle - its center
  circle.center.x       // The center's x coordinate
  circle.center.y       // The center's y coordinate

Although morphs can be thought of as executable objects with
state they are not objects as the term is generally understood in
the object oriented programming paradigm.  In particular:

(a)  Morphs are necessarily bound to identifiers; each identifier
is tied to a unique morph and vice versa.  Objects, on the other
hand, may have more than one identifier pointing to it.

(b)  Objects themselves do not (usually) have values in their own
right nor are they functions in their own right.  Morphs always
do and always are.

(c)  Objects (in most paradigms) has a fixed structure, being
instances of defined classes (templates being a partial
exception.)  Morphs do not have a fixed structure; they are
protean; their structure can be modified at any time.

Special syntax is used to specify a morph aspect as such.  Thus
in YASL identifiers may not begin the $ character.  The fixed
aspects may be referenced with an "attribute" that does begin
with a $ character.  Thus if foo is a morph then

        foo.$s  is the current value of the string aspect
        foo.$r  is the current value of the number aspect
        foo.$b  is the current value of the boolean aspect
        foo.$f  is the current value of the function aspect

In YASL, although not necessarily in other languages using the
morph concept if there should ever be such, all values (except
functions) are strings.  That is, the boolean aspect is either
the string "true" or the string "false".  Likewise the numeric
aspect is a string that represents a valid number.  When either
the numeric aspect or the boolean aspect is set the string aspect
is set to the new assignment.

Note:  Naturally an implementation will only make these
conversions on an as needed basis.

AND

4.7 The structure of variables

In YASL an identifier always represents a table of variables (morphs).
The columns of this table are the sub-fields of the identifier
(variable). In addition to the named columns there is one anonymous
column; it holds the indexed principal morphs. The rows are indexed
numerically; row zero holds the names of the sub-fields
(conventionally the anonymous column has name $v); rows 1 to n contain
the n instances of the sub-fields. The table is guaranteed to have at
least one column (the $v column) and one row, 0. The special fields,
$nrow and $ncol, hold the number of rows (not counting row 0) and the
number of columns.

References into the table are specializations of the form
variable_name[index].[field_name]. Here are the possibilities for
table foobar:

foobar                  refers to foobar[1].$v
foobar.$v               refers to foobar[1].$v
foobar.a                refers to foobar[1].a
foobar[i]               refers to foobar[i].$v
foobar[i].a             refers to foobar[i].a
foobar[]                is an iterator representing col $v
foobar[].a              is an iterator representing col 'a'
foobar.[]               is an iterator rep. all columns except $v
foobar[i].[]            is an iterator holding values from row i

foobar[].[]             is syntactically illegal


http://home.tiac.net/~cri, http://www.varinoma.com
Optimists think the glass is half full, pessimists think it's half
empty, and engineers think it's twice as big as it needs to be.



Sat, 07 Jan 2006 05:51:24 GMT  
 Yet Another Scripting Language - Routines

Quote:

> I also like a language character set to not be overloaded. My thought is
> to let each pair of delimiters provided by the character set to have a
> different function.
[...]
> A morph is an entity tied to an identifier that can have any of several
> types (and values) depending on the context.

Funny that you don't like overloading on the lexical level, yet happily
overload the meaning of identifiers (where disambiguation is more subtle).

Quote:
>         When the function aspect is invoked the default
>         value aspect is set to the return value of the
>         function aspect.  The execution of the function aspect
>         updates the entire state of the morph.

I don't like it. Virtually every use of that feature is not thread-safe.

Quote:
>   circle.radius         // Attribute of circle - its radius

>   circle.center         // Attribute of circle - its center
>   circle.center.x       // The center's x coordinate
>   circle.center.y       // The center's y coordinate
[...]
> (a)  Morphs are necessarily bound to identifiers; each identifier
> is tied to a unique morph and vice versa.

I assume that by "identifier" here you mean "possibly qualified name".
Does it mean that two circles can't have the same center (i.e. when one
is modified, modification can be seen in the other)?

Since morphs must have global names, how do you represent dynamically
created objects with fields? I guess that it's either an entirely different
mechanism, or the programmer must invent unique names for all his objects.

In the first case the morph concept has a very limited use. Global variables
are a poor programming style, they usually put otherwise unnecessary
restrictions on code which uses them (it's not reentrant, not thread-safe,
and using it more than once requires to carefully restore initial values of
variables which is error-prone).

In the second case it's an unnecessary complication for the programmer, also
error-prone (how to ensure that names are really globally unique?), makes
garbage collection impossible (global variables are potentially accessed
from anywhere in the future so they must not be automatically freed), and
makes implementation details visible to anyone.

Doesn't look good.

You might look at Tcl. It has the same defects.

Do you have local variables? Are they morhps? Do they have unique global
names? I'm afraid I don't understand your model.

Quote:
> In YASL, although not necessarily in other languages using the
> morph concept if there should ever be such, all values (except
> functions) are strings.

How would you represent a tree of directories and files in a filesystem?
Imagine a virtual filesystem which lives only in the memory of the program,
with operations like on real filesystems (e.g. creating, moving, deleting
files and directories). In particular I want to be able to move a large
subtree efficiently.

--
   __("<         Marcin Kowalczyk

    ^^     http://qrnik.knm.org.pl/~qrczak/



Sat, 07 Jan 2006 06:18:38 GMT  
 Yet Another Scripting Language - Routines

[snip]

There was a formatting problem with the first three paragraphs.  They
are:

This article is a continuation of the discussion of "yet another
scripting language", a scripting language that I am proposing to
create.  The ongoing presentation is broken up into sections,
each section discussing some aspect of the proposed language.  
This article is (with the exception of a few minor updates) about
procedures, functions, and other executable routines.  Future
articles will discuss scope of variables, program structuring
devices, etc.  In the meantime take it as given that there won't
be globals.  

The articles are not definitive specifications; rather, they are
working documents exploring possibilities.  

There are three prior threads in comp.lang.misc that are devoted
to this projected language.  The thread titles are:

---

I want to call particular attention to the last line of the first
paragraph, which I repeat:

IN THE MEANTIME TAKE IT AS GIVEN THAT THERE WON'T
BE GLOBALS.  

I repeat: THERE WON'T BE GLOBALS.


http://home.tiac.net/~cri, http://www.varinoma.com
Optimists think the glass is half full, pessimists think it's half
empty, and engineers think it's twice as big as it needs to be.



Sat, 07 Jan 2006 12:15:24 GMT  
 Yet Another Scripting Language - Routines

Quote:

>>Funny that you don't like overloading on the lexical level, yet happily
>>overload the meaning of identifiers (where disambiguation is more subtle).

> I opine that the disambiguation would be clear cut since the aspect
> used depends on how it is used and that, at least as I am envisioning
> things, is unambiguous.

Ok, what are the rules governing which aspect of a morph is meant?

Quote:
>>>         When the function aspect is invoked the default
>>>         value aspect is set to the return value of the
>>>         function aspect.  The execution of the function aspect
>>>         updates the entire state of the morph.

>>I don't like it. Virtually every use of that feature is not thread-safe.

> I don't follow you.  What do you have in mind you say that it is not
> thread-safe?

If two threads use the pattern "call the function, do something, and then
examine the result stored in the default aspect of the function", then one
of them might unexpectedly look at the result stored by the other one.

Or even in a single thread when "do something" happens to call that
function, overwriting its stored result.

Quote:
> In each activation of foo there a morph bound to x; each activation
> has its own such morph.  The name, x, is local to foo.

If you pass it as an argument to a function, does the function refer to the
same morph as was passed or to its deep copy?

The first case doesn't agree with your statement that "each identifier is
tied to a unique morph and vice versa", because the same morph is bound to
different identifiers at the same time.

The second case doesn't look practical.

Quote:
> The routine to move a subtree (sans error checking and special cases)
> would look like this:

> procedure movedir src dest dir
> set dest.dir src.dir
> delete src.dir

Ok, so I don't understand how morphs "are not objects as the term is
generally understood in the object oriented programming paradigm."
You said:

"(a)  Morphs are necessarily bound to identifiers; each identifier
is tied to a unique morph and vice versa.  Objects, on the other
hand, may have more than one identifier pointing to it."

What happens if you skip "delete src.dir" in the above? src.dir and dest.dir
are two identifiers tied to the same morph, no? And if "set" makes a deep
copy of the subtree, I wanted to *efficiently* move a subtree, i.e. in time
indepent of the size of the subtree.

"(b)  Objects themselves do not (usually) have values in their own
right nor are they functions in their own right.  Morphs always
do and always are."

Objects *are* values. One might say that a value is a reference to an
object, which is roughty the same. Why would an object *have* a value
which is not the object itself? If it's indeed so, is this value an object
too, i.e. you can speak about the value of the value of the value of
something? If not, it's not quite obvious where you mean objects and where
values, e.g. what is passed to a function - object or value, what is stored
in data structures, and if it's value - how to refer to the whole object,
and if you can't refer to the whole object, only to its values - what does
it mean that the object exists at all.

In several languages, e.g. Lisp and Python, functions are objects which are
callable. I suppose that the difference between that and your language is
that the function associated with the object is separate from a value
associated with the object. But this just reminds me of Lisp's two
namespaces, separate for functions and other values (well, functions can be
stored in other objects but then they are not applied like functions bound
to names). Does it work like Lisp? So morphs are just symbols (names), and
your syntax
   <function arguments>
is equivalent to Lisp's
   (setq function (function arguments))

Quote:
> The real problem, after dispensing with bogus globals, is how to
> access functions and procedures.

I have an answer for my language, which is not what most dynamically typed
languages do - more what the ML family do. A group of consecutive function
declarations forms a recursive group, where all names introduced there are
visible in their bodies. Since creation of functions (as opposed to
applying them) doesn't need to run user code which might need them already
defined, the compiler can implement that by creating all functions all at
once, and (if they refer to free local variables) store pointers to them
inside them before the user code has a chance to use them.

It's more tricky to get recursion between other values than functions.
Sometimes you can have lazy bindings: the value of a variable is computed
the first time it is used. Such bindings can be mutually recursive,
together with functions. And you can also resort to mutable variables,
i.e. create a binding now and fill its value later.

--
   __("<         Marcin Kowalczyk

    ^^     http://qrnik.knm.org.pl/~qrczak/



Sat, 07 Jan 2006 18:08:42 GMT  
 Yet Another Scripting Language - Routines
On Tue, 22 Jul 2003 12:08:42 +0200, Marcin 'Qrczak' Kowalczyk

Quote:


>>>Funny that you don't like overloading on the lexical level, yet happily
>>>overload the meaning of identifiers (where disambiguation is more subtle).

>> I opine that the disambiguation would be clear cut since the aspect
>> used depends on how it is used and that, at least as I am envisioning
>> things, is unambiguous.

>Ok, what are the rules governing which aspect of a morph is meant?

Usage rules, i.e. the syntax determines what aspect is relevant.  Frex

        X => call flapdoodle <# X is a boolean>
                                <# flapdoodle is a procedure>

        X := <bageldorf>  <# X is a string>

        X := Y + Z              <# X, Y, and Z are numbers>

Quote:

>>>>         When the function aspect is invoked the default
>>>>         value aspect is set to the return value of the
>>>>         function aspect.  The execution of the function aspect
>>>>         updates the entire state of the morph.

>>>I don't like it. Virtually every use of that feature is not thread-safe.

>> I don't follow you.  What do you have in mind you say that it is not
>> thread-safe?

>If two threads use the pattern "call the function, do something, and then
>examine the result stored in the default aspect of the function", then one
>of them might unexpectedly look at the result stored by the other one.

Well yes, that would be the case if they were calling the same
function, but they can't call the same function.  Functions,
executable elements, aren't globals.  Each invocation is an invocation
of its own copy because the identifier for the morph exists in the
local context.  Thus

00      function foo x
01              x le? 0 => return foo
02              x := x - 1
03              <foo x>
04              end foo

The foo that appears in function foo is not the same as the foo
appearing in the environment that calls foo; instead it ia local
variable (morph).  Each invocation will reference a separate copy of
foo.

Quote:
>Or even in a single thread when "do something" happens to call that
>function, overwriting its stored result.

Won't (shouldn't) happen as detailed above.

Quote:

>> In each activation of foo there a morph bound to x; each activation
>> has its own such morph.  The name, x, is local to foo.

>If you pass it as an argument to a function, does the function refer to the
>same morph as was passed or to its deep copy?

If we're talking about function invocations the function refers to a
deep copy.  Please see section 5.1.1 of the original post; function
invocations are call by copy value.  Procedure invocations are call by
reference.  More precisely, procedures act on a copy of the argument
and alter it in the calling routine upon termination of invocation.

Quote:

>The first case doesn't agree with your statement that "each identifier is
>tied to a unique morph and vice versa", because the same morph is bound to
>different identifiers at the same time.

When the first case is applicable (procedure invocation) it isn't
actually the case that the procedure is operating on "the same morph".
It is actually operating on a copy.  Upon termination the procedure's
version is copied into the calling program's version and the
procedure's version ceases to exist.

Quote:
>The second case doesn't look practical.

Surely you jest.

Quote:

>> The routine to move a subtree (sans error checking and special cases)
>> would look like this:

>> procedure movedir src dest dir
>> set dest.dir src.dir
>> delete src.dir

>Ok, so I don't understand how morphs "are not objects as the term is
>generally understood in the object oriented programming paradigm."
>You said:

>"(a)  Morphs are necessarily bound to identifiers; each identifier
>is tied to a unique morph and vice versa.  Objects, on the other
>hand, may have more than one identifier pointing to it."
>What happens if you skip "delete src.dir" in the above? src.dir and dest.dir
>are two identifiers tied to the same morph, no?

No.  The "set" makes a logical copy.  Presumably the implementation
knows that they are identical in content at the moment and make a
bookkeepping notation somewhere.  The creation of an actual copy
occurs when one of the versions is further altered.  That, however, is
in the implementation and not in the semantics.

Quote:
>And if "set" makes a deep
>copy of the subtree, I wanted to *efficiently* move a subtree, i.e. in time
>indepent of the size of the subtree.

Done.

Quote:
>"(b)  Objects themselves do not (usually) have values in their own
>right nor are they functions in their own right.  Morphs always
>do and always are."

>Objects *are* values.

When I say "object" I mean an instance of an ADT with its own state
data with the ADT commonly (but not necessarily) described by a class
schema.  On the other hand when I say "value" I am referring to an
atomic value (e.g., string, integer, real, boolean, executable
element).  One can stretch this conception to a composition of atomic
values, e.g., arrays, lists, sets, trees, or shopping bags.

One can speak of the current state of an object as its "value".  For
some kinds of objects that is a useful thing to do; in general it
seems obfuscatory to me.

One could say that morphs are objects by virtue of being instances of
an ADT, namely the class of morphs.  However this is scarcely what is
usually meant by "objects" and "object oriented".

Quote:
>One might say that a value is a reference to an
>object, which is roughty the same.

Now this I will take exception to - values and references are
different kinds of things; conflating them is a category error.

[snip objects and values]

Quote:
>In several languages, e.g. Lisp and Python, functions are objects which are
>callable.  I suppose that the difference between that and your language is
>that the function associated with the object is separate from a value
>associated with the object. But this just reminds me of Lisp's two
>namespaces, separate for functions and other values (well, functions can be
>stored in other objects but then they are not applied like functions bound
>to names). Does it work like Lisp? So morphs are just symbols (names), and
>your syntax
>   <function arguments>
>is equivalent to Lisp's
>   (setq function (function arguments))

You can think of it that way if you like, i.e.,

        < ..... >
is equivalent to
        (setq ..... )

It won't do, though, to say that morphs are just names.  Morphs are
recursive data structures that are bound to names.  As a metaphor,
morphs are like swiss army knives that contain swiss army knives ad
infinitum.  Every possibly qualified name binds to a swiss army knife;
it also binds to a suite of blades (screw drivers, whatever).  Which
blade is used is dependent upon context.  Is this the least bit
clearer?

- Show quoted text -

Quote:

>> The real problem, after dispensing with bogus globals, is how to
>> access functions and procedures.

>I have an answer for my language, which is not what most dynamically typed
>languages do - more what the ML family do. A group of consecutive function
>declarations forms a recursive group, where all names introduced there are
>visible in their bodies. Since creation of functions (as opposed to
>applying them) doesn't need to run user code which might need them already
>defined, the compiler can implement that by creating all functions all at
>once, and (if they refer to free local variables) store pointers to them
>inside them before the user code has a chance to use them.

>It's more tricky to get recursion between other values than functions.
>Sometimes you can have lazy bindings: the value of a variable is computed
>the first time it is used. Such bindings can be mutually recursive,
>together with functions. And you can also resort to mutable variables,
>i.e. create a binding now and fill its value later.

Interesting.  I shall have to think about that.
Thanks for the comments.  There are useful.

http://home.tiac.net/~cri, http://www.varinoma.com
Optimists think the glass is half full, pessimists think it's half
empty, and engineers think it's twice as big as it needs to be.


Tue, 10 Jan 2006 02:48:32 GMT  
 Yet Another Scripting Language - Routines

Quote:

> Usage rules, i.e. the syntax determines what aspect is relevant.  Frex
> X := <bageldorf>      <# X is a string>

Why a string? What if the function returns e.g. a number?
I would be surprised if this:
   X := <bageldorf>
   Y := X + 1
computed a different Y than this:
   Y := <bageldorf> + 1

Quote:
> 00    function foo x
> 01            x le? 0 => return foo
> 02            x := x - 1
> 03            <foo x>
> 04            end foo

> The foo that appears in function foo is not the same as the foo
> appearing in the environment that calls foo; instead it ia local
> variable (morph).  Each invocation will reference a separate copy of
> foo.

I don't get it. What does "return foo" return if it hasn't been assigned or
called in the current scope? Could you either translate this function to
some other language or think of an example from real life which retrieves
the value of the last call?

Do you mean that if I enter a function foo which uses function bar, it has
an implicitly declared variable named bar which is assigned each time the
function bar is called from this activation of foo? It would indeed be
thread-safe but this example would not work, because foo is read before its
value is computed.

Quote:
> If we're talking about function invocations the function refers to a
> deep copy.  Please see section 5.1.1 of the original post; function
> invocations are call by copy value.  Procedure invocations are call by
> reference.  More precisely, procedures act on a copy of the argument
> and alter it in the calling routine upon termination of invocation.

What if I want to alter the argument and at the same time return some
result? For example I'm writing a queue and want to have a function or
procedure "take the next element, remove it from the queue and return it".
What would be its interface?

To make things harder, I want this function to be safe when used from
multiple threads, so it wouldn't work to return the next element paired
with the queue with the element removed, because then locking would have
to be done on the usage side.

--
   __("<         Marcin Kowalczyk

    ^^     http://qrnik.knm.org.pl/~qrczak/



Wed, 11 Jan 2006 19:28:33 GMT  
 Yet Another Scripting Language - Routines
On Sat, 26 Jul 2003 13:28:33 +0200, Marcin 'Qrczak' Kowalczyk

Quote:


>> Usage rules, i.e. the syntax determines what aspect is relevant.  Frex

>> X := <bageldorf>      <# X is a string>

>Why a string? What if the function returns e.g. a number?
>I would be surprised if this:
>   X := <bageldorf>
>   Y := X + 1
>computed a different Y than this:
>   Y := <bageldorf> + 1

It wouldn't.  In principle non-executable atoms are always strings.
Strings that look like numbers, e.g., the string "53", may be used as
numbers.  Strings that look like booleans, i.e., true and false, may
be used as booleans.  Character representations and values are
conflated.  Under the hood conversions are done upon demand.

[snip confusing example - I have no idea what I might have been
thinking of.]

Quote:
>Do you mean that if I enter a function foo which uses function bar, it has
>an implicitly declared variable named bar which is assigned each time the
>function bar is called from this activation of foo?

That is correct.

Quote:
>> If we're talking about function invocations the function refers to a
>> deep copy.  Please see section 5.1.1 of the original post; function
>> invocations are call by copy value.  Procedure invocations are call by
>> reference.  More precisely, procedures act on a copy of the argument
>> and alter it in the calling routine upon termination of invocation.

>What if I want to alter the argument and at the same time return some
>result? For example I'm writing a queue and want to have a function or
>procedure "take the next element, remove it from the queue and return it".
>What would be its interface?

This could be done in several ways.  One way would be something like
this:

        procedure queue
                procedure $.add
                        $$.Q[] := [$$.Q[], $args[]]
                        end
                procedure $.get item
                        item, $$.Q[] <- $$.Q[]
                        end
                function $.get
                        $$.get item
                        return item
                        end
        ....
        queue.add foo bar
        print <queue.get> <queue.get>

foo bar

In the above code a single $ denotes the current level of reference,
and $$ the parental level.  We cannot say

                procedure add

within procedure queue because then add would be a local variable
within queue.  Likewise queue.add would be still be a local variable.
$.add, however, is a field of queue, i.e., it is queue.add as seen
from the calling environment.  Similarly queue.Q is the actual queue
and in the procs $$.Q refers to queue.Q as seen form the calling
environment.  

Quote:
>To make things harder, I want this function to be safe when used from
>multiple threads, so it wouldn't work to return the next element paired
>with the queue with the element removed, because then locking would have
>to be done on the usage side.

The above code is thread-safe, albeit only because it is not visible
from any thread other than the one in which it is resident.  What you
want is an agent, aka a light-weight thread.  You can't call agents or
call functions or procedures within agents.  All you can do is send
messages and read the replies.  (This is my current thought on the
matter.)  The code for the queue might look something like this:

        agent queue
                on msg add begin
                        Q[] := [Q[], $args[]]
                        end
                on msg get begin
                        item, Q[] <- Q[]
                        return item
                        end
                end queue

The calling code then might be

        msg queue add foo bar
        print <msg queue get> <msg queue get>

This code isn't cast in stone, but it's in the neighbourhood.


http://home.tiac.net/~cri, http://www.varinoma.com
Television is a medium because it is neither rare nor well done.
- Ernie Kovacs



Mon, 16 Jan 2006 05:28:16 GMT  
 Yet Another Scripting Language - Routines

Quote:

>>I would be surprised if this:
>>   X := <bageldorf>
>>   Y := X + 1
>>computed a different Y than this:
>>   Y := <bageldorf> + 1

> It wouldn't.  In principle non-executable atoms are always strings.
> Strings that look like numbers, e.g., the string "53", may be used as
> numbers.

But you said:

|   print foo             // Prints the string aspect of foo
|   foo := x + y          // The number aspect of foo is set to x+y
| [...]
| Aspects need not have any particular relationship to each [...]

thus implying that string and number aspects of a morph are distinct.
So which aspect of X does "X := <bageldorf>" set?

--
   __("<         Marcin Kowalczyk

    ^^     http://qrnik.knm.org.pl/~qrczak/



Mon, 16 Jan 2006 19:38:42 GMT  
 Yet Another Scripting Language - Routines
On Thu, 31 Jul 2003 13:38:42 +0200, Marcin 'Qrczak' Kowalczyk

Quote:


>>>I would be surprised if this:
>>>   X := <bageldorf>
>>>   Y := X + 1
>>>computed a different Y than this:
>>>   Y := <bageldorf> + 1

>> It wouldn't.  In principle non-executable atoms are always strings.
>> Strings that look like numbers, e.g., the string "53", may be used as
>> numbers.

>But you said:

>|   print foo             // Prints the string aspect of foo
>|   foo := x + y          // The number aspect of foo is set to x+y
>| [...]
>| Aspects need not have any particular relationship to each [...]

>thus implying that string and number aspects of a morph are distinct.
>So which aspect of X does "X := <bageldorf>" set?

And I said earlier in this thread, and I quote:

"In YASL, although not necessarily in other languages using the
morph concept if there should ever be such, all values (except
functions) are strings.  That is, the boolean aspect is either
the string "true" or the string "false".  Likewise the numeric
aspect is a string that represents a valid number.  When either
the numeric aspect or the boolean aspect is set the string aspect
is set to the new assignment."

In general, aspects, need not have any relationship to each other.  In
the particular case of string/number/boolean I have, I hope, been
consistent is saying that those particular aspects are
introconvertible.  In

        X := <bageldorf>

it doesn't matter which of the three (string, number, boolean) is set
because the other two of the three are also set.  This is just a
variant of the "everything is a string" device common in scripting
languages.  As a general rule things that are not syntactically
distinguishable are introconvertible.

Examples of aspects that are not linked include:

        X := <bageldorf>[]        <# The list associated X is set to the
                                   list associated with bageldorf
                                   after the function call has been
                                   completed. >

        X:= <bageldorf>.$f        <# The function body of X is set to
                                   the function body of bageldorf as
                                   it is after invocation.  Yes, a
                                   function can alter itself. >

Is this any clearer?

(Thanks for the comments, btw.)


http://home.tiac.net/~cri, http://www.varinoma.com
Television is a medium because it is neither rare nor well done.
- Ernie Kovacs



Wed, 18 Jan 2006 06:44:35 GMT  
 
 [ 12 post ] 

 Relevant Pages 

1. Yet Another Scripting Language - Reprise

2. Yet another scripting language - Variables, function invocation, and some syntax

3. Yet another scripting language - arithmetic

4. Yet Another Scripting Language - Syntax thoughts

5. Thoughts on (yet another ) scripting language

6. Yet Another Scripting Language crusaders (was: arrays really aren't)

7. New Product: Five-Script, the scripting language for CA-Clipper developers

8. Scripting and Scripting Language

9. new Scripting Language Survey at scripting.com

10. new Scripting Language Survey at scripting.com

11. Yet anothe unusefil script: iniformat.py

 

 
Powered by phpBB® Forum Software