adding construction to Joy-like languages

View: New views
6 Messages — Rating Filter:   Alert me  

adding construction to Joy-like languages

by John Nowak :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

This may be more of a post-holiday brain dump than anything  
interesting. If so, just ignore.

One of the things I really like about FP is the functional form of  
construction. I've mentioned this in earlier emails, but here's a  
quick summary:

   Here's a construction involving three functions:

     [f, g, h]

   Applying (denoted ':') the construction to a value works as such:

     [f, g, h]:x  ==  <f:x, g:x, h:x>  -- angle brackets denote a list

In short, construction is very similar to the cleave combinators in  
Factor. Unfortunately, the cleave combinators are not nearly as  
convenient as construction for a number of reasons:

1. The single form of construction handles any number of inputs and  
outputs properly. In contrast, a family of cleave combinators is  
needed. Factor has "smart combinators" that rely on the types of the  
functions involved to get around this, but they're quite awkward in  
comparison to FP's approach.

2. FP has functions which, given a list, return a single element from  
that list. Factor has no way (that I know of) to index into a stack  
returning only the element selected.

3. FP uses lists to group more than one element. As such, you can do  
something like '[sq, dup]:5' to get '<25, <5, 5>>'. Emulating this  
behavior is nearly impossible in Factor because all values are carried  
inside a stack. As such, you must either return a flat stack like '25  
5 5' (which is what '[sq] [dup] bi' would do) or return all stacks  
independently like '{25} {5 5}' which makes getting at the values  
painful. I've mentioned this problem earlier but I'm not sure if I'm  
explaining it adequately.

The solution I propose has four parts:

1. Replace the stack with a vector in the style of More's array  
theory. Here, a vector containing a single element is equivalent to  
the element it contains (and vice versa). We'll denote such nestable  
vectors with parentheses, e.g. '1 (2 3)' is a two element vector  
containing '1' and the two element vector '(2 3)'. Note that, as in  
More's array theory, '(1)' is equivalent to '1'. As such, '1 2 3 + *'  
can be written '(1 (2 3 +) *)' without changing the meaning of the  
expression. '()' denotes the empty vector.

2. Add functions for indexing into a vector, e.g. '1 2 3 4 2nd' yields  
'3'.

3. Add a set of functions for grouping values, e.g. '1 2 3 4 2g'  
yields '1 2 (3 4)'.

4. Add a syntax for construction of the form [|f0, f1 .. fN|].

What construction will do for us is take the top value of the vector  
passed to it and apply every function in the construction of N  
functions to it yielding a vector that is the concatenation of the  
vector below the initial value and the N values produced from the  
application. Whew.

Here are some examples:

   1 2 3 [|sq, neg|]  ==  1 2 9 -3  -- works as '3' == '(3)'
   3 4 2g [|+, -|]  ==  7 -1
   5 [|sq, dup, drop|]  ==  25 (5 5) ()
   5 12 [|1st sq, 2nd sq|] +  ==  (5 sq) (12 sq) +  ==  25 144 +  ==  
169

Something marginally more useful:

   discriminant = 3g [|2nd sq, [|1st, 3rd|] * 4 *|] -

Instead of adding a new syntax, we could alternatively offer c2, c3,  
etc, to denote construction over a set number of functions:

   discriminant = 3g [2nd sq] [[1st] [3rd] 2c * 4 *] 2c -

Useful? I'm not sure. The need to group things into a single vector  
all the time means it is still less convenient than the FP version:

   discriminant = - [sq 2nd, * [* [1st, 3rd], 4]]

This is especially if we use an infix syntax (e.g. 'f g h == g:[f,  
h]') like J's monadic hook:

   discriminant = (sq 2nd) - 4 * 1st * 3rd  -- all associate right

- John

Re: adding construction to Joy-like languages

by John Nowak :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Dec 29, 2008, at 10:05 PM, John Nowak wrote:

> Something marginally more useful:
>
>   discriminant = 3g [|2nd sq, [|1st, 3rd|] * 4 *|] -
>
> Useful? I'm not sure. The need to group things into a single vector
> all the time means it is still less convenient than the FP version...
> This is especially if we use an infix syntax (e.g. 'f g h == g:[f,
> h]') like J's monadic hook:
>
>   discriminant = (sq 2nd) - 4 * 1st * 3rd  -- all associate right

I suppose I could be more fair in my comparison; I managed to throw  
out all the benefits of the concatenative programming style in my  
example as I was doing a direct FP translation. Here's a better version:

   3g [|2nd sq, 4 * *|] -

Compare to what you'd probably write in Factor now:

   [sq swap] dip 4 * * -

I think the first version is better. It's clear that you're dealing  
with three values, clear you're squaring the second, clear you're  
getting two results and then taking the difference, etc.

I can't help but think there's some good way to combine FP with Joy-
style languages. I think using More-style vectors as the primary data  
structure may be a good start.

- John

Re: adding construction to Joy-like languages

by stevan apter :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


----- Original Message -----
From: "John Nowak" <john@...>
[:]
>
> 2. FP has functions which, given a list, return a single element from  
> that list. Factor has no way (that I know of) to index into a stack  
> returning only the element selected.

why limit yourself to a single element, or to indexing only at the
top level of a list?

i'll use "S" and "D" to denote the surface and depth indexing functions,
and i'll write them infix.  the left argument is the list to index,
the right argument is the index:

    10 20 30 S 2
  30
    10 20 30 D 2
  30
    10 20 30 S 2 0
  30 10
    (10 20 30)(40 50) S 0
  10 20 30
    (10 20 30)(40 50) D 0(2 1)
  30 20

i.e. the right argument of D describes a path through the list.

    () = X D ()
    () = X S ()

it's useful to have a null of some kind to represent all the
elements.  i'll use "N":

    (10 20 30)(40 50) D N 1
  20 50

detaching the index from the function allows you to compute
the elements to index, e.g.

   10 20 30 40 50 60 S where 10 20 30 40 50 60 > 30
40 50 60

(this is very APLish:  10 20 30 40 50 60 > 30 -> 0 0 0 1 1 1, and
where 0 0 0 1 1 1 -> 3 4 5.)

>
> 3. FP uses lists to group more than one element. As such, you can do  
> something like '[sq, dup]:5' to get '<25, <5, 5>>'. Emulating this  
> behavior is nearly impossible in Factor because all values are carried  
> inside a stack. As such, you must either return a flat stack like '25  
> 5 5' (which is what '[sq] [dup] bi' would do) or return all stacks  
> independently like '{25} {5 5}' which makes getting at the values  
> painful. I've mentioned this problem earlier but I'm not sure if I'm  
> explaining it adequately.

k has a pair of functions which might be useful to consider.  i'll
use "T" and "C" to denote 'take' and 'cut'.  with list on the left,
control argument on the right:

    1 2 3 4 5 6 T 2
 1 2
    1 2 3 4 5 6 T -2
 5 6
    1 2 3 4 5 6 T 2 3
 (1 2 3)(4 5 6)
    1 2 3 4 5 6 T 3 2
 (1 2)(3 4)(5 6)

    1 2 3 4 5 6 C 2
 3 4 5 6
    1 2 3 4 5 6 C -2
 1 2 3 4
    1 2 3 4 5 6 C 0 2
 (1 2)(3 4 5 6)
    1 2 3 4 5 6 C 0 1 3
 1 2(3 4 5 6)

again, detaching the control argument from the function name allows
you to compute the take/cut.

>
> The solution I propose has four parts:
[:]

i'm not sure what the problem is, so my suggestions might be off-
target.  my sense is that you may be starved for examples.  what
i mean is this:  your data universe is the set of nested vectors.
that's your machinery for representing the world.  so you want a
small set of primitives which give you maximum expressivity and
also combine in powerful ways.  that pretty much describes the
history of innovation in the array programming world over the
last 50 years.  

Re: adding construction to Joy-like languages

by John Nowak :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Dec 30, 2008, at 8:41 AM, Stevan Apter wrote:

>> 2. FP has functions which, given a list, return a single element from
>> that list. Factor has no way (that I know of) to index into a stack
>> returning only the element selected.
>
> why limit yourself to a single element, or to indexing only at the
> top level of a list?

The type system, mainly. For indexing within a vector within another  
vector, you'd have to use something equivalent to Joy's "infra". For  
example:

    1 2 (3 (4 5)) [1st] infra 2nd
    1 2 (3 (4 5) 1st) 2nd
    1 2 (3 5) 2nd
    1 2 3

It's possible that some additional special form could be introduced to  
make such indexing a bit easier. For homogeneous vectors of  
indeterminate length, things could be done a bit differently. It's  
sort of like the difference between tuples and lists in something like  
ML; The former is tracked much more closely on the type level while  
the latter admits a much broader set of operations. Right now, I'm  
only dealing with the former.

> i'm not sure what the problem is, so my suggestions might be off-
> target.

I apologize for not explaining it well.

Essentially what I'm trying to do is come up with something like  
"cleave" for a concatenative language that behaves a bit better. To  
illustrate the current problem with the cleave family, take this  
example (where 'x' is an object and 'F' and 'G' are functions):

    x [F] [G] bi

In a language like Factor, it's hard to say much about this. It's  
unclear if 'F' or 'G' will need more values on the stack beyond the  
'x' that is provided. It's unclear how many values will be returned as  
a result. It's unclear if G will access any values produced by F. Etc.

What I want to be able to do for the above example is solve all of  
those problems. I want it to be clear that the 'x' provided is all F  
and G care about. I want it to be clear that F and G will not  
interact. I want it to be clear that the result will be two values.  
Finally, I want this all to be syntactically obvious; I do not want to  
have to employ the type system to do it.

This is already a solved issue in FP. For example:

    [F, G]:x   -- the construction of 'F' and 'G' applied to 'x'
               -- reduces to...
    <F:x, G:x> -- the list of 'F' and 'G' each applied to 'x'

Essentially, I was trying to add this form of construction to a  
concatenative language. My first attempt was as follows:

1. Limit the functions involved to only using the top element of the  
stack
2. Return the result of each function in its own stack

As a trivial example (where braces denote a stack):

    5 [sq] [dup] bi-construct
    {5 sq} {5 dup}
    {25} {5 5}

The problem with this is that I now have two stacks on the stack. To  
get at the 25, I need to manually pull the value out of the second  
stack. This makes the whole thing too awkward to use.

The solution I proposed was to replace the notion of a stack with the  
notion of a vector where a vector containing a single element is  
equivalent to the element it contains. Here's the same example using  
vectors:

    5 [sq] [dup] bi-construct
    (5) [sq] [dup] bi-construct
    (5 sq) (5 dup)
    (25) (5 5)
    25 (5 5)

Because '5 == (5)', I was able to use construction. Because '(25) ==  
25', I can now access the value without manually pulling it out of the  
stack. At the same time, the result of the other function which  
yielded two values is still nicely bundled up. I get all the  
guarantees I was after without the drudgery.

- John

Re: adding construction to Joy-like languages

by LittleDan :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Factor can, in fact, do all of these things within the typical stack
model, though I've never needed any of them in practical programming
problems. Going through your list,

On Mon, Dec 29, 2008 at 10:05 PM, John Nowak <john@...> wrote:
> 1. The single form of construction handles any number of inputs and
> outputs properly. In contrast, a family of cleave combinators is
> needed. Factor has "smart combinators" that rely on the types of the
> functions involved to get around this, but they're quite awkward in
> comparison to FP's approach.

Could you elaborate on why this is insufficient? Maybe it can be
remedied, if there's a specific use case you can show.
>
> 2. FP has functions which, given a list, return a single element from
> that list. Factor has no way (that I know of) to index into a stack
> returning only the element selected.

There's npick, which copies the nth item from the top of the stack on
to the top, given an integer parameter (IIRC). If you follow that by [
clear ] dip, then that'll be all that's on the stack. This seems no
less awkward than your solution (you can of course make a word : th
npick [ clear ] dip ; to make this easier) but I don't see the
motivation.
>
> 3. FP uses lists to group more than one element. As such, you can do
> something like '[sq, dup]:5' to get '<25, <5, 5>>'. Emulating this
> behavior is nearly impossible in Factor because all values are carried
> inside a stack. As such, you must either return a flat stack like '25
> 5 5' (which is what '[sq] [dup] bi' would do) or return all stacks
> independently like '{25} {5 5}' which makes getting at the values
> painful. I've mentioned this problem earlier but I'm not sure if I'm
> explaining it adequately.

A better solution is to use an explicit array for problems like this.
If you want it to be like a stack, you can use the with-datastack
combinator (this is infra). I have not seen a situation where this
would be needed, though.

The general stack philosophy, as I understand it, is that not all
parameters need to be seen by every function. To me, the stack is a
nice representation of this; it is very simple and easy to use. I
don't understand how the dataflow of a complicated function could be
represented with the vector system. I don't remember large functions
in the FP paper, just things like matrix multiplication. It'd be
interesting to see some existing Factor or Joy code translated into
this.

Dan

Re: adding construction to Joy-like languages

by John Nowak :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Jan 1, 2009, at 12:23 AM, Daniel Ehrenberg wrote:

> Factor can, in fact, do all of these things within the typical stack  
> model

Factor is a flexible, reflective language that can be coerced into  
doing almost anything. I'm sure you can embed a nice Lisp or Prolog  
(if you already haven't) that works well with the stack. All you'd be  
doing though is implementing another language inside Factor.

> though I've never needed any of them in practical programming problems

That doesn't mean much. Insert your own "user X of language Y without  
feature Z never needed feature Z" analogy.

>> 1. The single form of construction handles any number of inputs and
>> outputs properly. In contrast, a family of cleave combinators is
>> needed. Factor has "smart combinators" that rely on the types of the
>> functions involved to get around this, but they're quite awkward in
>> comparison to FP's approach.
>
> Could you elaborate on why this is insufficient? Maybe it can be
> remedied, if there's a specific use case you can show.

1. They don't work unless the stack effects are known at the point of  
use.
2. They're meta-level constructs that rely on reflection.
3. They don't have any useful algebraic properties.
4. They can't be typed.
5. Their semantics are very complicated compared to construction.

>> 2. FP has functions which, given a list, return a single element from
>> that list. Factor has no way (that I know of) to index into a stack
>> returning only the element selected.
>
> There's npick, which copies the nth item from the top of the stack on
> to the top, given an integer parameter (IIRC). If you follow that by [
> clear ] dip, then that'll be all that's on the stack.

Good point. I doubt such a style would be remotely efficient in Factor  
though.

>> 3. FP uses lists to group more than one element. As such, you can do
>> something like '[sq, dup]:5' to get '<25, <5, 5>>'. Emulating this
>> behavior is nearly impossible in Factor because all values are  
>> carried
>> inside a stack. As such, you must either return a flat stack like '25
>> 5 5' (which is what '[sq] [dup] bi' would do) or return all stacks
>> independently like '{25} {5 5}' which makes getting at the values
>> painful. I've mentioned this problem earlier but I'm not sure if I'm
>> explaining it adequately.
>
> A better solution is to use an explicit array for problems like this.

Not for the problems I'm looking to address:

1. You'd still need a family of combinators. Smart combinators are not  
a good solution (as I explained above).

2. You're talking about doing something in an ad hoc manner when it's  
convenient. Such an approach doesn't give you any of the guarantees  
that I'm looking for, namely a syntactically obvious number of  
produced and consumed values. Again, with the vector approach I  
suggested, 'x [|f, g, h|]' always yields '(x f) (x g) (x h)'. You  
can't get that sort of rule by altering your programming style.

> I don't understand how the dataflow of a complicated function could be
> represented with the vector system. I don't remember large functions
> in the FP paper, just things like matrix multiplication.

Here's an implementation of FP (with a funny syntax) that comes with a  
number of examples (including a DFT implementation):

    http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/prolog/impl/fp_lp/fp/

Additionally, large programs have been written in J using a tacit  
style. I think that's sufficient evidence that the approach is feasible.

- John