Skip to content

Instantly share code, notes, and snippets.

@cben
Last active October 20, 2020 10:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cben/12b775b8ab26d6089050b33ef490b638 to your computer and use it in GitHub Desktop.
Save cben/12b775b8ab26d6089050b33ef490b638 to your computer and use it in GitHub Desktop.

Point-free style is the default in "concatenative" programming languages like FORTH and PostScript. And, since they use postfix order, with function argument coming before the function, simulating "let" should be easy, right?

Funnily, there are no named-arg lambdas in most of these languages*. Not even the modern Factor (supports named arguments for named functions, but not in anonymous "quotations"). But FORTH and PostScript do have ways to simulate local variables. So we can show the equivalence the other way around: implement "let", then implement "lambda" in terms of "let" 😁.

Try it online!

Or clone this, and run ghostscript named-locals-let-lambda.ps

Output:

(xxyyxxyy)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
(goo of foo of Hello!)
PS<SSSSSSSSS>
% Examples for https://softwareengineering.stackexchange.com/questions/318297/almost-point-free-style
% (parens) are PostScript syntax for strings.
% String concatenation, stolen from https://stackoverflow.com/a/12379557/239657
/++ % Stack: (a) (b) => (ab)
{ exch dup length
2 index length add string
dup dup 4 2 roll copy length
4 -1 roll putinterval
} bind def
%%% Point-free style.
/f { (foo of ) exch ++ } def
/g { (goo of ) exch ++ } def
/h_point_free { f g } def
/h_inlined { (foo of ) exch ++ (goo of ) exch ++ } def
%%% Local variables, PostScript's equivalent of functional "let"
% Background: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
% chapter 4 on dictionaries + cookbook "DICTIONARIES AND LOCAL VARIABLES" page 132
/f_locals % Stack: (s) => (foo of s)
{ % Create a temp dictionary, sized for 1 word.
1 dict
% Push it on the dictionary stack, so "def" writes words into it
% instead of global dict, and word lookup finds these definitions.
begin
% Pop argument from top of stack and put it into local named s:
/s exch def
(foo of ) s ++
% Pop the local dict from the dictionary stack
end
} def
/g_locals % Stack: (s) => (goo of s)
{ 1 dict begin
/s exch def
(goo of ) s ++
end
} def
/h_locals % Stack: (x) => (result)
{ 3 dict begin
/x exch def % name incoming argument
/y (foo of ) x ++ def % name intermediate
/z (goo of ) y ++ def % name intermediate
z
end
} def
%%% Not bad! But verbose. Could we define a "let" helper!?
% Let's expect value before name, so we can use it on incoming argument already on stack.
/let % Stack: value /name => ...
% Dictionary stack: => locals
{ 1 dict begin
exch def
% UNTERMINATED begin !
} def
/endlet % Dictionary stack: locals =>
{ end } def
/h_let % Stack: (x) => (result)
{ /x let
(foo of ) x ++
/y let
(goo of ) y ++
/z let
z
endlet
endlet
endlet
} def
% Oh, well. Works but more verbose than /h_locals.
%%% Defining "lambda" !?!
% Would like:
% /x { 5 x sub } lambda
% to be equivalent to:
% { exch 5 sub }
/lambda % Stack: /name proc => proc
{ /body let
/name let
% Conceptually, we want to build a proc (aka executable array) like:
% { $name let $body exec endlet }
% where $name and $body should vary, so can't use `{ ... }`.
% `[ ... ]` builds an array from computed values, `cvx` marks it executable.
% Non-varying words all have to be quoted with `/... cvx`.
% See https://codesync.global/media/a-postscript-to-functional-geometry/
% for good fast tutorial on PostScript meta-programming, from ~20:00.
[ name
/let cvx
% Just writing `body` would execute it. We want it as a value.
/body load
/exec cvx
/endlet cvx
] cvx
%pstack
endlet
endlet
} def
% TODO: I suspect I've implemented dynamic scoping, not real lexical scoping?
/f_lambda /s { (foo of ) s ++ } lambda def
/g_lambda /s { (goo of ) s ++ } lambda def
/h_lambda % Stack: (x) => (result)
/x {
(foo of ) x ++
/y {
(goo of ) y ++
/z {
z
} lambda exec
} lambda exec
} lambda def
% Well, the postfix placement of "lambda" itself is hard to read to my taste.
% But so are all PostScript control structures :-)
%%% Test them all
(Hello!) h_point_free
(Hello!) h_inlined
% A "concatenative" language means we can also write it flatly without any procedures:
(Hello!) (foo of ) exch ++ (goo of ) exch ++
(Hello!) f_locals g_locals
(Hello!) h_locals
(Hello!) h_let
(Hello!) f_lambda g_lambda
(Hello!) h_lambda
% Do multi-arg lambdas work? Yes!
/abab /b /a % Stack: (a) (b) => (abab)
{ a b ++ a ++ b ++
} lambda lambda def
(xx) (yy) abab
pstack
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment