From: rockwell@socrates.umd.edu (Raul Deluth Miller-Rockwell)
Subject: Re: Types, arrays, generality, and notation (was: Fortran90 and Arrays)
Organization: Traveller
	<ROCKWELL.92Apr10011141@socrates.umd.edu>
	<1992Apr10.103445.1055@newshost.lanl.gov>
Date: Sat, 11 Apr 1992 06:18:57 GMT

Jim Giles:
   |>    |>    How would this have been different if I had used the example:
   |>    |>          A(0: :2) = B(1: :2)

Raul Deluth Miller-Rockwell:
   |>     A =. (B {~ 1 + B step 2)   (A step 2) } A

Jim Giles:
   |> [...]

Ahh... you ripped out all my comments.  But the above was the
solution.  X {~ Y is indexing similar to X[Y] -- though I understand
Fortran uses regular parenthesis to denote indexing.  Also, X Y} Z is
similar to Z[Y] = X, except that it doesn't modify Z.

   |>    step   =.  'where: x. $ y. {. 1'     :11
   |>    where: =.  'y. #   i. # y.'          :11

This is one time work -- that's the advantage of declaring functions
by the way, you only need to declare the things once.  Declaring data
needs to be done for each instance of the data.  [Not that this is
bad, but in my opinion it should be as easy as possible to make the
language work for you in building data structures.]

But, since most people don't know J, here's a quicky language summary:

        string  : 11
Essentially, the colon by itself indicates a compiler call.  The
string represents the body of a function, and : converts it from
string representation to an executable function.  Option 11 means
compile to tacit form.  Tacit form has a number of properties, the
most interesting of them is that it's faster than 'explicit form'.
Sorta.  The special cases on this usage make me prefer not to use it.
But it does look a bit more like a traditional language...

In a : defined operation, the token x. refers to the operation's left
argument and the token y. refers to the operation's right argument.
Note that : defined operations can be used without ever giving them a
name.  For example,
     '3 + y.'  : 11  (1 2 3 4)
would give the result  4 5 6 7.  Also note that the parenthesis are
not optional here -- they're being used to make a distinction between
the argument to : and the argument to the : defined function.  

Though I could have put them around the 11 or around the entire
function declaration, instead of around the function argument.  Fully
parenthesized, that statement would have been
     ((('3 + y.') ( :) (11)) (1 2 3 4))
but most of those parenthesis are optional.

Also note that the space to the left of : is not optional.  The
lexical analyzer will bind : to just about any word if there's no
space to the left of it.  [Ok, Ok, this is complicated.  I'll know not
to use : definitions in future examples.]

Anyways,
   x {. 1  will create an x element array whose first element is 1
   (the fill element is 0).

   x $ y will create an array where the items of y have the shape
   given by x.

So,   x. $ y. {. 1    implements an array of length x. with a stride of
y.  Which points out a bug in my code.  I should have said
   step =.  'where:  (#x.) $ y. {. 1'
because I want was using an array as the left argument to step, and
what I needed was the number of items in the array.  The language
would have caught this for me, but it's still embarassing.

The definition
   where: =. 'y. # i. #y.'   :11
uses #y. to get the size of its argument (the bit vector created in
the body of 'step'), then uses the index generator i. to generate
the set of indices for an array of that size, then uses the dyadic
form of # to select the indices corresponding to 1s in the bit vector.

In case anyone is interested (anyone still reading this article?) the
dyadic form of #,
    x # y
makes x copies of each item of y.  If x is an array, then each element
of x says how many copies to make of each element in y.  For example,
   1 0 1 0 1 0 1 0    #     0 1 2 3 4 5 6 7
would return: 0 2 4 6

   An awful lot of work for such a simple concept.  And, once again,
   it's a long way from what you actually want to have happen
   operationally.  So, you optimizer better be really good.  It's also
   an awful long way from the mental model of the operation that users
   have.

No kidding.

The problem is that I'm using J (which has operations designed to
manipulate arrays) to emulate Fortran (which has operations based on
the "DO" loop).

So if I were to use the above technique to solve an arbitrary array
manipulation problem, I'd lose big time, because I'm not tackling the
actual problem (whatever that is) but using these general purpose
array manipulation operators to emulate Fortran.

Still, I don't think it's asking too much of an optimizer (to compile
the above expression to efficient code).  It's a straight forward
translation that you're asking for.  

   In any case (question addressed to other readers), how many users
   really prefer the J version to the Fortran version?

Not even me, actually.

On the other hand, let's see the Fortran code for matrix inversion.
And let me know if you expect your optimizer to translate something
like gauss-jordan reduction into something like householder.

   |> Also, note that although I've not made any explicit declarations
   |> about the sizes of A and B, there are a number of such
   |> assertions implicit in the above code.  The above code will work
   |> unless B has an even number of elements, nor will it work unless
   |> A step 2 is identical to B step 2.

   Well, at least you appear to do the right conformability tests.
   But, since you have no declarations, there's not a way for the
   compiler to apply these constraints until run-time - unless you
   have a _really_ good data flow analysis built into the compiler.
   Of course, the Fortran compiler has the same problem in procedures
   where A and B are passed as arguments with unspecified length.

Well, that's one of the interesting things about compiling APL.
Unless you do data-flow analysis, there's no real point in compiling
at all.  On the other hand, the language has properties which make
global data-flow analysis reasonable.

   |> [...]  Yes.  It gets worse: J doesn't allow you to change a
   |> single element of an array without respecifying the entire
   |> array.

   For those algorithms in which whole array is going to modified in
   essentially the same way, this is actually the desired operation.
   That's why Fortran 90 added the whole array features it has.  It's
   part of the motivation for our proposed extensions as well.  But,
   modifying single elements in an aggregate is another fairly simple
   operation which is fairly common - so it should be simple to
   denote.

It's simple to denote.  It's just not efficient when interpreted.

   |> [... A + B ...]  First off, you are correct.  '+' is never
   |> overloaded for non-numeric data.  So, yes, the compiler can
   |> always tell at compile time that both A and B obey this
   |> constraint.

   Your conclusion doesn't follow from the premise.  The fact that `+'
   is defined only on numeric data tells the compiler that A and B are
   _supposed_ to be numeric.  It does not help in enforcing that.  A
   and B may be assigned to _anything_ and such assignments may be
   run-time dependent.

This depends on the design of the compiler.

Note that the "compiler as a metafunction" paradigm does not demand
that any particular compiler be able to treat all possible constructs
of the language.  You can have one compiler which propagates
constraints throughout the code, and produces extremely fast (but
specific) code, and another which deals with constraints in a lazy
fashion to produce extremely general (but not quite as fast) code.

   |> You can define an identifier to represent   f @only_vector:

   Good.  One down and one to go.  If you can invent a similar
   mechainsm to guard against undesired assignments you've got the
   whole thing.

We've got a bit of a quibble here.  The problem that interests you
seems, to me, to be something of a non-problem.  Your specification
doesn't map well into any problem space (except that of generating a
specific kind of machine code) -- which has nasty implications about
the long-term usefulness of this kind of mechanism.

I could maybe see binding to an identifier some function which must
pre-process the argument being assigned to that identifier.  But I
don't see that as meeting your design goal of "forcibly preventing the
user from making certain kinds of mistakes" -- if anything, it opens
up the possibility for additional mistakes, and could result in truly
obscure code.

On the other hand, without general computational ability, I don't see
how this could be considered to support arbitrary types.

So I think I'll wait for a better specification before I propose any
solution to this particular problem.

   Note: I really am making an effort to cut the size of these
   articles down.

Well, I've blown that out of the water, eh?

I think I'll make an effort to get some sleep...

-- 
Raul Deluth Miller-Rockwell                   <rockwell@socrates.umd.edu>
Newsgroups: comp.lang.misc