Re: [stack] sweetening concatenative syntax

Don Groves <[email protected]>
Newsgroups gmane.comp.lang.concatenative
Message-ID <[email protected]>
Hi, John --

Where can we read about Fifth? Several years ago, someone
announced a Fifth language, is yours a continuation of that effort,
or was that you?
--
don


On Mar 5, 2008, at 19:04 , John Nowak wrote:

> I've had some thoughts recently on syntactic additions to
> concatenative languages that seem potentially quite useful. At the
> very least, they would put an end to silly discussions over largely
> imagined deficiencies of postfix syntax and pointfree programming.
> This is perhaps crucial if we expect any significant uptake of
> concatenative languages.
>
> Below are the four extensions that I'm proposing. I'm strongly
> considering one or more in the language I'm currently working on
> (named "Fifth" for what are likely obvious reasons). The first two
> should be fairly uncontroversial. The latter two are simple
> translations I've not seen discussed before that seem potentially very
> useful in practice.
>
> I. Lambda Expressions
>
> Assuming functions have the syntax '[<body>]', it seems useful to
> introduce the syntax '[<bindings> -> <body>], where bindings are
> ordered with the top of the stack towards the right as is familiar.
> For example, the following two functions are equivalent:
>
>      foo :: -> [a b -> b a a]
>      foo = [swap dup]
>      foo = [a b -> b a a]
>
> Note that the type syntax mimics the lambda expression syntax (or vice
> versa if you prefer). The type notation is similar to Cat's with minor
> alterations for the purposes of brevity. In particular, scalar
> variables are all lowercase, row variables are all uppercase, and
> concrete types are title-case and of at least two characters (to avoid
> confusion with row variables).
>
> Variables should, of course, be lexically scoped. A translation of
> expressions with lambdas to efficient pointfree stack-based code is
> not especially difficult. This is the case regardless of if the
> language is linear (which introduces only small complications), typed,
> or "lacking" a retain stack. Of course, I elide the translation here
> for the sake of... space.
>
> II. Local Function Variables
>
> If you permit lambdas, it makes sense to offer a simple syntax for
> declaring functions with named variables. This is similar to how
> Scheme offers '(define (foo <bindings>) <body>)' to mean '(define foo
> (lambda (<bindings>) <body>))'. The declaration syntax proposed here
> mimics use. In particular, the name of the function being defined
> occurs *after* the arguments.
>
> The following two statements are equivalent (where 'i' is the identity
> combinator with the type 'A (A -> B) -> B'):
>
>      a b foo = b a a
>      foo = [a b -> b a a] i
>
> Here's another example in which 'unlist' is a deconstructor for a
> list; the first function provided handled the null case and the second
> handles the cons case:
>
>      unlist :: A [b] (A -> C) (A b [b] -> C) -> C
>
>      map :: [a] (a -> b) -> [b]
>      list f map = list [] [x xs -> x f i xs f map cons] unlist
>
> It should be noted that both lambda expressions and functions declared
> with the local variable syntax may use more items on the stack than
> they explicitly bind. For example, the following definition of map is
> equivalent to the above and likely preferable in practice:
>
>      f map = [] [x xs -> x f i xs f map cons] unlist
>
> III. Prefix Notation
>
> It occurred to me recently that there exists a simple syntactic
> translation from prefix code (a la Scheme), where '(foo a b c)' is the
> application of 'foo' to three arguments, to postfix code. This
> translation can be done without any type information. The translation
> consists of simply removing the parenthesis and placing the first
> element at the end. If that first element is a local binding or a
> lambda expression, an 'i' is prepended to cause it to be evaluated.
> Here are some simple translation examples:
>
>      (cons 1 null) => 1 null cons
>      (+ (- 1 2) 3) => 1 2 - 3 +
>      [f -> (f 10)] => [f -> 10 f i]
>      f foo = (f 5) => f foo = 5 f i
>
> Interestingly, you get partial application for free:
>
>      double = (* 2)
>
> One thing that may seem like a problem is that '(1 2 3)' will be
> translated to '(2 3 1)' when really an error should occur as '1' is
> not a function. However, that's not the case: '1' *is* a function.
> More precisely, it is a row polymorphic function that consumes a stack
> and yields a new stack (1 :: A -> A int). As such, the above syntactic
> translation is completely valid and it would be wrong to reject it.
>
> Prefix notation may seem like a curiosity at first, perhaps existing
> only to lure over those familiar with prefix notation. However, I'd
> argue that prefix notation makes certain code objectively easier to
> read. This is especially the case when dealing with deconstructors
> like 'if' or 'unlist' as the higher order function of primary interest
> is "announced" earlier in the definition. In the following example,
> both functions are equivalent:
>
>      f map = [] [x xs -> x f i xs f map cons] unlist
>
>      f map =
>        (unlist []
>                [x xs -> x f i xs f map cons])
>
> (Perhaps this is not the best example. Substitute a better one with
> nested ifs.)
>
> Prefix notation also has the benefit that you can express "nested"
> code (as above) in a way that allows editors to automatically intent
> properly. This is not at all the case with purely postfix code where
> type information and perhaps some heuristics would need to be involved
> to produce acceptable indentation.
>
> Here's a rewriting of map using *only* prefix notation in a way that
> most any Scheme or Haskell programmer would find comfortable:
>
>      f map =
>        (unlist []
>                [x xs -> (cons (f x) (map xs f))])
>
> IV. Indentation
>
> Prefix notation is often criticized for being overly verbose or noisy.
> The Python solution is to use indentation instead of paired
> delimiters, but this introduces a number of problems: It's harder for
> machines to generate valid code, pasting between sources can be messy,
> editors can't really automatically re-indent code, plenty of people
> (myself included) hate it, etc.
>
> The correct way to address this problem (if you are to address it at
> all) is to do what Haskell does and offer *optional* indentation-
> sensitive syntax that gets translated to the more explicit form. This
> can be implemented as a relatively trivial pre-parser pass and does
> not require modification of the parser itself.
>
> Indentation-sensitive syntax is introduced with a colon. Here are some
> simple examples showing how indentation-sensitive code is translated
> to prefix code which is then translated to "normal" code:
>
>      a b c: d e f  =>  a b (c d e f)        =>  a b d e f c
>
>      a b c:
>        d e f:      =>  a b (c d e (f g h))  =>  a b d e g h f c
>          g h
>
> Finally, here again is our map example, this time even spiffier:
>
>      f map = unlist:
>        []
>        [x xs -> cons: (f x) (map xs f)]
>
> This looks quite close to the Haskell version, except that our
> programs are, after translation, still expressed only in terms of the
> functional forms of composition and quotation! (I was wrong in an
> earlier email when I started that composition is the only functional
> form in Joy. Quotation is also a functional form.) This definition is
> arguably easier to read than the postfix, pointfree alternative, as it
> clearly announces at the start that 'map' is a function that
> destructures a list, details the result of the destructization at the
> start of the relevant line, and indicates directly after that the
> result of the function will be a list via the prefixing of 'cons'.
>
> - John
>
>
>
> Yahoo! Groups Links
>
>
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.