Sysop: | Amessyroom |
---|---|
Location: | Fayetteville, NC |
Users: | 27 |
Nodes: | 6 (0 / 6) |
Uptime: | 38:53:17 |
Calls: | 631 |
Calls today: | 2 |
Files: | 1,187 |
D/L today: |
23 files (29,781K bytes) |
Messages: | 174,061 |
[I see BTW that there is a brand-new release of A68G. I don't
yet know what's new or changed, but at least it shows that Marcel is
still active!]
On 01.09.2025 18:20, Andy Walker wrote:
[I see BTW that there is a brand-new release of A68G. I don't
yet know what's new or changed, but at least it shows that Marcel is
still active!]
Yeah. Thanks for the hint! - Maybe I should download a newer version
to see what has changed. [...]
Re your other nearby articles:
-- I've never used "gets" and only once used "associate", so have no
useful comment to make on your query about them.
-- I've not yet even downloaded the latest A68G, so can't comment on
how it works on my computer. I'll get around to it!
I got caught out when Marcel added warnings about declaration hiding
other declarations -- my "composer of the day" script takes the one
line output of the script as the name of the chosen composer, so one
day, after several trouble-free years, the composer was "Warning:
declaration of i ...". Luckily, I caught it before any
article/e-mail went out. Marcel was somewhat unsympathetic.
On Tue, 2 Sep 2025 17:02:28 +0100, Andy Walker wrote:
I got caught out when Marcel added warnings about declaration hiding
other declarations -- my "composer of the day" script takes the one
line output of the script as the name of the chosen composer, so one
day, after several trouble-free years, the composer was "Warning:
declaration of i ...". Luckily, I caught it before any
article/e-mail went out. Marcel was somewhat unsympathetic.
Surely warnings and other diagnostics go to stderr, whereas you would collect output from stdout.
What do you think about that diagnosis behavior?
I'm also curious what your specific suggestions had been in the past.
And which got accepted and which dismissed. (It would give me some
indication where it's worth to formulate a rationale and send a mail
and where not.)
I got caught out when Marcel added warnings about declaration hidingSurely warnings and other diagnostics go to stderr, whereas you would
other declarations [...].
collect output from stdout.
I use 'gets' fairly often. With "associate" I meant the respective implementation function of Genie;
'gets' and 'puts' (IIRC) both
use and rely on that function.
The problem is that this implicitlyOther possible reasons are (a) you may want to read and write
used function uses string refs. A REF STRING is necessary to 'puts'
data into it, but unnecessary - even restricting! - to 'gets' from
a STRING(-value). - It seems that only the implicit dependency to
the function is the reason for the unfortunate function interface
of 'gets'.
My expectation is [...].
On 02/09/2025 21:17, Janis Papanagnou wrote:
I use 'gets' fairly often. With "associate" I meant the respective
implementation function of Genie;
Yes; but "associate" is defined by the RR, so is not really
to be tinkered with lightly. ...
'gets' and 'puts' (IIRC) both
use and rely on that function.
... I can understand Marcel wanting to use pre-existing code
in preference to writing something wholly new and non-trivial.
The problem is that this implicitlyOther possible reasons are (a) you may want to read and write
used function uses string refs. A REF STRING is necessary to 'puts'
data into it, but unnecessary - even restricting! - to 'gets' from
a STRING(-value). - It seems that only the implicit dependency to
the function is the reason for the unfortunate function interface
of 'gets'.
from/to the same string, and this works at no added expense in A68G
"as is" [ie you may want to read what has just been written];
[...]--- Synchronet 3.21a-Linux NewsLink 1.2
On 01/09/2025 23:56, Janis Papanagnou wrote:
[...]
What do you think about that diagnosis behavior?
I got caught out when Marcel added warnings about declaration
hiding other declarations -- my "composer of the day" script takes
the one line output of the script as the name of the chosen composer,
so one day, after several trouble-free years, the composer was
"Warning: declaration of i ...". Luckily, I caught it before any article/e-mail went out. Marcel was somewhat unsympathetic.
Today I noticed that newer releases of Genie (tested with 3.8.5)
do not show these [annoying] warnings. [...] Thanks Marcel!
PS: The sad part is that on my main development computer the 3.9.3
version of Genie doesn't run my programs any more (they fail with a
"memory access violation"), so I can't take advantage of the change
(unless I switch to another more contemporary computer). :-/
I can't keep up with the new versions! There seems to be a new
one every day currently.
I was in the habit of typing
% a68g someprog.a68g - somefilename
to run "someprog.a68g" with "somefilename" as parameter but that has
stopped working; I now get "scanner error" with either "unrecognised
option" or "multiple source file names" depending on what minor tweak
I try to apply.
[...]
On 11.09.2025 00:57, Andy Walker wrote:
I was in the habit of typing
% a68g someprog.a68g - somefilename
Marcel just sent me a tar-file with the memory access violation
issue and the '--' problem (any maybe more things) fixed! :-)
I suppose it will be published soon as release 3.9.4 (or as some
successor of that).
On 10/09/2025 16:15, Janis Papanagnou wrote:
["declaration hides ..." warnings:]
Today I noticed that newer releases of Genie (tested with 3.8.5)
do not show these [annoying] warnings. [...] Thanks Marcel!
-a-a-a-aYay!
PS: The sad part is that on my main development computer the 3.9.3
version of Genie doesn't run my programs any more (they fail with a
"memory access violation"), so I can't take advantage of the change
(unless I switch to another more contemporary computer). :-/
-a-a-a-aI can't keep up with the new versions!-a There seems to be a new
one every day currently.-a I was in the habit of typing
-a % a68g someprog.a68g - somefilename
to run "someprog.a68g" with "somefilename" as parameter but that has
stopped working;-a I now get "scanner error" with either "unrecognised option" or "multiple source file names" depending on what minor tweak
I try to apply.-a Grr!-a I'd like some formulation that works with both current and old versions of the compiler, but no luck thus far.-a I may
have to go to the horse's mouth, but I don't like admitting defeat.
But at least 3.8.7 is another 10% faster than 3.5.4 on my computer by
the Whetstone benchmark;-a that makes it 2200 times faster than A68-R
[which was pretty slick] on the University 1906A mainframe of the 1970s
[not bad for a semi-interpreter!].-a Can't grumble at that.
On 10/09/2025 23:57, I wrote:
But at least 3.8.7 is another 10% faster than 3.5.4 on my computer byI'm pretty good at grumbling!
the Whetstone benchmark;-a that makes it 2200 times faster than A68-R
[which was pretty slick] on the University 1906A mainframe of the 1970s
[not bad for a semi-interpreter!].-a Can't grumble at that.
A68G features in a survey of mostly-interpreted language implementations
that I did earlier this year, in a Reddit post, which all ran the Fibonacci benchmark: > https://www.reddit.com/r/Compilers/comments/1jyl98f/fibonacci_survey/
It doesn't fare too well, being near the bottom end.
That used version 2.8.3 (2016). Your comments suggested it was improving, so I downloaded 3.9.9.
However it was nearly 50% slower! (3.2M calls/sec instead of 4.7M, using the same metric as in that link, and both tested just now.)
That of course is on this particular test, which is all about function calls and little else. Maybe it is a weak spot.
(There is supposedly a fast semi-compiled mode, but I've never managed to findHave you tried "a68g -O somefile.a68g"? Or "-O3", etc. The A68Gdocumentation gives full details. Works for me on Linux.
it, either on Windows or Linux.)
On 27/09/2025 00:41, bart wrote:
On 10/09/2025 23:57, I wrote:
But at least 3.8.7 is another 10% faster than 3.5.4 on my computer byI'm pretty good at grumbling!
the Whetstone benchmark;-a that makes it 2200 times faster than A68-R
[which was pretty slick] on the University 1906A mainframe of the 1970s
[not bad for a semi-interpreter!].-a Can't grumble at that.
-a-a-a-aYes, we've noticed over the years!
A68G features in a survey of mostly-interpreted language implementations
that I did earlier this year, in a Reddit post, which all ran the
Fibonacci
benchmark: > https://www.reddit.com/r/Compilers/comments/1jyl98f/
fibonacci_survey/
It doesn't fare too well, being near the bottom end.
-a-a-a-aActually the top [slower] end, but no matter.
That used version 2.8.3 (2016). Your comments suggested it was
improving, so
I downloaded 3.9.9.
-a-a-a-aThe speed-up noted was between 3.5.4 and 3.8.7.-a I have 2.8.3, but have changed computers since 2016 and I don't propose to install old versions
simply to check your figures.-a I don't [yet] have 3.9.9 -- as noted to Janis,
I can't keep up with almost daily new versions!-a But ...
However it was nearly 50% slower! (3.2M calls/sec instead of 4.7M,
using the
same metric as in that link, and both tested just now.)
-a-a-a-a... I get ~20M calls/sec from 3.8.7.-a Which is meaningless without knowing what computer was used, or how it's configured, or what the aims
and
objectives of A68G are compared with your other languages.
That of course is on this particular test, which is all about function
calls
and little else. Maybe it is a weak spot.
-a-a-a-aOr maybe it's a silly test;-a if anyone really wanted to find Fibonacci
numbers for large arguments they would surely memoise the results or use
one
of the other ways of finding them efficiently?
-a-a-a-aMore interesting would be some figures for the Whetstone benchmark or
similar?-a That is at least /intended/ to reflect real usage, and we have figures
going back over 50 years.-a [There is a version in the source directory
of A68G
downloads.]
(There is supposedly a fast semi-compiled mode, but I've never managed-a-a-a-aHave you tried "a68g -O somefile.a68g"?-a Or "-O3", etc.-a The A68Gdocumentation gives full details.-a Works for me on Linux.
to find
it, either on Windows or Linux.)
just use a fixed table of the first 92 or so values that fit into 64
bits).
They want to know how two implementations compare when both have to
execute so many millions of function calls. It's a benchmark, and a
famous one.
(Also one that should be hard to optimise away, but gcc/clang seem
to manage it. I suspect however they are cheating, by recognising
what it is.
What would normally take 25 machine instructions, get expanded to
250, which is totally over the top.
Imagine if a whole executable
got 10 times bigger using -O2.)
-a-a-a-a-aMore interesting would be some figures for the Whetstone benchmark or
similar?-a That is at least /intended/ to reflect real usage, and we have figures
going back over 50 years.-a [There is a version in the source directory of A68G
downloads.]
I have something called whet.a68; I don't know if it's that one. But I can't do
much with it; it's a lot of work to port many languages. And it's about floating
point, so less interesting for me.
Many speed issues in interpreted languages are to do with integer calculations.
3.9.9 on WSL.)
On 29/09/2025 01:15, bart wrote:
[Fibonacci benchmark]
Recursion is the sort of thing optimisers can optimise away.
[...]
This is, BRD, one of the most benign errors possible [and one I
warned Marcel about years ago]. Your computer is too fast! [As is mine.]
If you look at the source, you will see three lines of form
printf (($zzdx, [...], xzz-d.dx, "MWhets", [...], collections))
The format "xzz-d.dx" limits the speed to 999.9 MWhets. Replace the "zz"
by "zzz" and you gain an extra digit, esp in the first of the "printf"s,
and you're probably OK for a year or three. Or use a floating-point
format and be safe for your lifetime! [I'm not a fan of formats.]
On 29/09/2025 01:15, bart wrote:
[Fibonacci benchmark:]> Nobody is interested in the actual figures (if
they were, they would
just use a fixed table of the first 92 or so values that fit into 64
bits).
They want to know how two implementations compare when both have to
execute so many millions of function calls. It's a benchmark, and a
famous one.
-a-a-a-aYes, but not [IMO] a particularly useful one.-a It's like
measuring a car's speed over the measured mile -- somewhat useful as
a measure of the speed of racing cars, but useless as a measure of
the quality of a family saloon.-a See also below.
-a-a-a-aLoop/recursion unrolling can do that sort of thing.-a Space for
an extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
-a-a-a-aNo-one [sensible] uses an interpreted language for its speed.-a This brings us back to the car analogy above.
-a printf (($zzdx, [...], xzz-d.dx, "MWhets", [...], collections))
The format "xzz-d.dx" limits the speed to 999.9 MWhets.-a Replace the "zz"
by "zzz" and you gain an extra digit, esp in the first of the "printf"s,
and you're probably OK for a year or three.-a Or use a floating-point
format and be safe for your lifetime!-a [I'm not a fan of formats.]
[Fibonacci benchmark]I'm positive for linear recursion, but is that meanwhile true also
Recursion is the sort of thing optimisers can optimise away.
for cascaded recursion (as in calculating [in the simplistic way]
Fibonacci)?
In former times I hadn't used formatted output in Algol 68.
On 29/09/2025 17:57, Janis Papanagnou wrote:
[Fibonacci benchmark]I'm positive for linear recursion, but is that meanwhile true also
Recursion is the sort of thing optimisers can optimise away.
for cascaded recursion (as in calculating [in the simplistic way]
Fibonacci)?
I'm not entirely clear what distinction you're drawing between
different recursions,
but at the minimum ISTM that of the two new frames
apparently needed to calculate simplistic Fibonacci you can at least get
rid of one as a tail-call optimisation. As the other frame is also susceptible to TCO, this would seem at least to reduce exponential
growth in number of frames to linear.
As setting up and tearing down
frames is [naively] the major activity in this problem, this should be a serious gain for any modern optimiser, and specifically for "gcc -O".
It wouldn't entirely surprise me if an aggressive optimiser managed to
notice that "fib(n-2)" is used twice and thereby to memoise its value, potentially making the whole process linear in "n".
I don't think we
need to be as paranoid as Bart and suspect that Clang is rigged to spot
this benchmark!
In former times I hadn't used formatted output in Algol 68.
In the most former times, you /couldn't/ have used formatted
transput as that was the first thing to be dropped by single-pass
compilers [which was most of them]!
I dropped it for my own version
of A68 [a much-modified Pascal] as it was stupidly hard to lex and I
too never used it.
[BTW, don't wait for me to respond about appending to back
channels -- not something I've ever tried! If all else fails, you'll
have to ask Marcel;
I don't think it can be deduced from his book or from the RR, BICBW.]
Loop/recursion unrolling can do that sort of thing. Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
For a C-style HLL, then on x64 you can reckon on about 10 bytes of
machine per line of source. As it happens, the 7-line fib() in my
language generates 72 bytes of machine code.
But in optimised C, the same function (where it is 6 lines),
generates 950 bytes of machine code. That's about 150 bytes per line
of source code, which is unacceptable and will result in bloated
executables that are more likely to slow things down.
measuring elapsed time for 100 loops).
However the behaviour with -O3 was odd. It seems -O3 causes it to
spend 3 seconds optimising first before starting the program proper.
So for some short runtimes, it could well make it slower!
On 29/09/2025 19:08, bart wrote:
Loop/recursion unrolling can do that sort of thing.-a Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
-a-a-a-aIf you have "thousands" of functions in one application, then
you're talking about something more substantial than anything I've
ever written.-a I suspect that such monsters
However the behaviour with -O3 was odd. It seems -O3 causes it to
spend 3 seconds optimising first before starting the program proper.
-a-a-a-aThe optimiser is "gcc"'s, so nothing directly to do with A68G.[But at least it makes sense to optimise /before/ starting the program
rather than after! :-)]
So for some short runtimes, it could well make it slower!
-a-a-a-aIf you're running the program once, that may well be the case.
But of course the intention is that you should optimise once and run
the executable lots of times [if the run-time matters to you].
If you have "thousands" of functions in one application, thenThat's not particularly large. The sourcecode for gcc has 80,000
you're talking about something more substantial than anything I've
ever written. I suspect that such monsters
*files* iirc.
You might agree however that typical programs that do important
things that we all use, are likely to be of substantial size.
So, what is gcc actually optimising, some generated C code? I doubt
it is a direct C representation of the task, since that would run at
least a magnitude faster than even optimised A68G.
Whatever it's doing, 3 seconds is a pretty long time on a modern
machine. That would be like a 1970s machine spending several hours
on optimising a 100-line program. I'm sure they weren't *that* slow!
If all else fails, read the documentation! You would be lookingThat's how it normally works with a compiled language. But I don'tSo for some short runtimes, it could well make it slower!If you're running the program once, that may well be the case. But
of course the intention is that you should optimise once and run
the executable lots of times [if the run-time matters to you].
know if A68G has some discrete AOT compilation step that produces a
binary that you can then invoke multiple times.
and I suspect many of the files are copies of each other.
(b) As an example, it makes my point. It's bloated. It's buggy -- it's regularly updated on my system and most of the updates are bug fixes.
On 29/09/2025 19:08, bart wrote:
Loop/recursion unrolling can do that sort of thing. Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
If you have "thousands" of functions in one application, then
you're talking about something more substantial than anything I've
ever written. I suspect that such monsters are essentially not
maintainable and not understandable by humans, which is why new bugs
are always being discovered, usually only when something catastrophic happens.
And to have some perspective, let me mention discussion of Open Office startup.
Andy Walker <anw@cuboid.co.uk> wrote:
On 29/09/2025 19:08, bart wrote:
Loop/recursion unrolling can do that sort of thing. Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
If you have "thousands" of functions in one application, then
you're talking about something more substantial than anything I've
ever written.
And to have some perspective, let me mention discussion of Open
Office startup. On Linux Open Office used to take substantial
time to start up.
The explantion was: Open Office had about
250000 exported functions spread among tens of shared libraries.
ELF rules meant that at startup each function had to be looked
up sequantiall in available libraries. That led to large number
of lookups which took time. Similarly other large programs
have a lot of functions. Mere thousends means now relatively
small program.
On 01/10/2025 23:00, bart wrote:
So, what is gcc actually optimising, some generated C code? I doubt
it is a direct C representation of the task, since that would run at
least a magnitude faster than even optimised A68G.
-a-a-a-aIf you have write access to the directory where you have the
source, you may find that you have acquired a "*.c"file and/or a "*.so" file.-a That is part of your answer.-a See also below.
-a-a-a-aIf all else fails, read the documentation!-a You would be looking for the "rerun" and "compile" options of A68G.
On 02/10/2025 16:55, Andy Walker wrote:
If all else fails, read the documentation! You would be looking
for the "rerun" and "compile" options of A68G.
Which source is this, the source of A68G (I don't have that on Windows,
and have no idea if or where it might be in Linux, where I just did
apt-get) or my .a68 file?
On 03/10/2025 23:24, Waldek Hebisch wrote:
Andy Walker <anw@cuboid.co.uk> wrote:
On 29/09/2025 19:08, bart wrote:
Loop/recursion unrolling can do that sort of thing. Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
If you have "thousands" of functions in one application, then
you're talking about something more substantial than anything I've
ever written.
And to have some perspective, let me mention discussion of Open
Office startup. On Linux Open Office used to take substantial
time to start up.
I sometimes use LibreOffice on Windows. Everything involves a delay. But until recently I'd only used it to paste stuff and print; I'd never
tried actually typing a document. When I did so, the latency after every keystroke, or sometimes group of keystokes, made it unusable.
This was after turning off every option I could find.
On 04.10.2025 13:38, bart wrote:
On 02/10/2025 16:55, Andy Walker wrote:
If all else fails, read the documentation! You would be lookingWhich source is this, the source of A68G (I don't have that on Windows,
for the "rerun" and "compile" options of A68G.
and have no idea if or where it might be in Linux, where I just did
apt-get) or my .a68 file?
I suggest to just try the Genie web-pages for the documentation... https://jmvdveer.home.xs4all.nl/en.download.algol-68-genie-current.html
On 04/10/2025 13:04, Janis Papanagnou wrote:
On 04.10.2025 13:38, bart wrote:
On 02/10/2025 16:55, Andy Walker wrote:
If all else fails, read the documentation! You would be looking >>>> for the "rerun" and "compile" options of A68G.Which source is this, the source of A68G (I don't have that on Windows,
and have no idea if or where it might be in Linux, where I just did
apt-get) or my .a68 file?
Does "apt-get" not install the "man" page? [I always build
direct from the tar-ball.] Try "man a68g"?
I suggest to just try the Genie web-pages for the documentation...
https://jmvdveer.home.xs4all.nl/en.download.algol-68-genie-current.html
More specifically, the "Learning Algol 68 Genie" PDF from that
page gives you an A68/A68G textbook [not perfect, but better than most
of the actual A68 textbooks], a programming guide telling you how to
install and use A68G, and the RR. Three for the price of one!
On 04/10/2025 13:04, Janis Papanagnou wrote:
On 04.10.2025 13:38, bart wrote:
On 02/10/2025 16:55, Andy Walker wrote:
-a-a-a-a-a If all else fails, read the documentation!-a You would be lookingWhich source is this, the source of A68G (I don't have that on Windows,
for the "rerun" and "compile" options of A68G.
and have no idea if or where it might be in Linux, where I just did
apt-get) or my .a68 file?
-a-a-a-aDoes "apt-get" not install the "man" page?-a [I always build
direct from the tar-ball.]-a Try "man a68g"?
I suggest to just try the Genie web-pages for the documentation...
https://jmvdveer.home.xs4all.nl/en.download.algol-68-genie-current.html
-a-a-a-aMore specifically, the "Learning Algol 68 Genie" PDF from that
page gives you an A68/A68G textbook [not perfect, but better than most
of the actual A68 textbooks], a programming guide telling you how to
install and use A68G, and the RR.-a Three for the price of one!
[...]>> Does "apt-get" not install the "man" page? [I always build directIf all else fails, read the documentation! You would be
looking for the "rerun" and "compile" options of A68G.
[...]> I was hoping that you could, you know, just tell me!from the tar-ball.] Try "man a68g"?
So I don't have> to wade through various tons of irrelevant material, ending up in
rabbit holes, or performing fruitless experiments.
The options shown by A68G --help are incomplete (there is no --
compile for example), and generally it is not useful. [...]
[I wrote:]
[...]>> Does "apt-get" not install the "man" page?-a [I always build directIf all else fails, read the documentation!-a You would be
looking for the "rerun" and "compile" options of A68G.
[...]> I was hoping that you could, you know, just tell me!from the tar-ball.]-a Try "man a68g"?
-a-a-a-a"Teach a man to fish ..." and all that jazz.
Try
-a a68g -compile fred.a68
-a a68g -rerun fred.a68g
[replace "fred" by a suitable file name, and note that "-O3" invokes
the "-compile" option automatically].
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a So I don't have> to wade through
various tons of irrelevant material, ending up in
rabbit holes, or performing fruitless experiments.
-a-a-a-aI didn't tell you to wade down rabbit holes, I suggested
"man a68g".-a Which [on my system] includes entries for both "rerun"
and "compile".-a YMMV.
The options shown by A68G --help are incomplete (there is no --
compile for example), and generally it is not useful. [...]
-a-a-a-aThere is "--compile" [on my system], and while I agree that the one-liners are both too many and too short to be equivalent to a proper manual entry or manual [both of which come with the downloads if not
with the "apt-get" (I haven't checked)], there's enough there to suggest
that the "compile" option switches compilation on and that "rerun" re-runs
a previous compilation [thus saving the compilation time, including your
3s of optimisation].-a I thought you were interested in "compile once, run many" to save all those seconds rather than wanting to wade through the generated C!
I sometimes use LibreOffice on Windows. Everything involves a delay. But until recently I'd only used it to paste stuff and print; I'd never
tried actually typing a document. When I did so, the latency after every keystroke, or sometimes group of keystokes, made it unusable.
Now, Algol68, the language, supports some very hairy features behind the scenes, associated with higher-order functions and such.
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind the
scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than what? What is the rCLorderrCY of a function?
On Sat, 4 Oct 2025 12:19:28 +0100, bart wrote:
I sometimes use LibreOffice on Windows. Everything involves a delay. But
until recently I'd only used it to paste stuff and print; I'd never
tried actually typing a document. When I did so, the latency after every
keystroke, or sometimes group of keystokes, made it unusable.
DoesnrCOt happen on my Linux system. How big a document did you try to create?
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind the
scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than what? What is the rCLorderrCY of a function?
On 03/10/2025 23:24, Waldek Hebisch wrote:
Andy Walker <anw@cuboid.co.uk> wrote:
On 29/09/2025 19:08, bart wrote:
Loop/recursion unrolling can do that sort of thing. Space for anYou're right, if the program was just this 7-line function plus one
extra 225 m/c instructions is neither here nor there if it saves a
few jumps or frames, esp with caches measured in megabytes.
more line to call it.
But why go to those lengths for this one tiny function out of all
the thousands across an application?
If you have "thousands" of functions in one application, then
you're talking about something more substantial than anything I've
ever written.
And to have some perspective, let me mention discussion of Open
Office startup. On Linux Open Office used to take substantial
time to start up.
I sometimes use LibreOffice on Windows. Everything involves a delay. But until recently I'd only used it to paste stuff and print; I'd never
tried actually typing a document. When I did so, the latency after every keystroke, or sometimes group of keystokes, made it unusable.
This was after turning off every option I could find.
This is on the same machine where I can build my entire 400KB compiler
from scratch in between the keystrokes of a 60wpm typist, and probably twice! (Build time is 90ms, keystrokes are 200ms apart.)
So something is obviously badly wrong, when I have to do my typing in my
own crappy editor then paste the text. (Thunderbird had a similar
problem a few years back; that was eventually fixed.)
Keeping on top of such things in any user application seems to be
nobody's job.
The explantion was: Open Office had about
250000 exported functions spread among tens of shared libraries.
ELF rules meant that at startup each function had to be looked
up sequantiall in available libraries. That led to large number
of lookups which took time. Similarly other large programs
have a lot of functions. Mere thousends means now relatively
small program.
I actually have to do something similar in the backend of my compiler
that generates EXE/DLL files. The same backend is used for a C front-end
as well.
Both languages have a list of dynamically imported functions, but none
is attached to a specific import library.
When building, a list of libraries needs to be specified. The EXE format requires each imported function to be listed under a specific library.
But the process will look for each function in each library until found.
It sounds inefficient, but I haven't noticed that it is a problem. Maybe
the numbers are just small.
(The backend does have the possibility of adding library info to each function name, so if the name is "msvcrt.printf" instead of just
"printf", it knows to look only in "msvcrt.dll" instead of all DLLs. But
the front-end doesn't take advantage.)
When the EXE is loaded, the process should be fast as each function is listed within its specific DLL table.
Does ELF not do that? Or is it slow because there are 250,000 functions, even if it knows where to find them?
(I have an interpreted language where imported FFI functions *are*
linked to specific DLL. Further, they are loaded on demand, so only when each is called.
But I don't know how practical it would be to add that behaviour on top
of EXE/ELF formats, without changing either the format, or the way the
OS loader works. I suspect it would involve extra indirection for
function calls (even after they are fixed up), so a slight slow-down.)
On 04/10/2025 22:05, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 12:19:28 +0100, bart wrote:
I sometimes use LibreOffice on Windows. Everything involves a delay.
But until recently I'd only used it to paste stuff and print; I'd
never tried actually typing a document. When I did so, the latency
after every keystroke, or sometimes group of keystokes, made it
unusable.
DoesnrCOt happen on my Linux system. How big a document did you try to
create?
Starting from an empty document; is that small enough?
I played with it and can now see what it is doing, but I don't know why:
The screen doesn't update while you're typing. Only when it detects a
pause.
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind
the scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than >> what? What is the rCLorderrCY of a function?
It's all the complicated stuff normally associated with functional programming ...
On Sun, 5 Oct 2025 01:52:54 +0100, bart wrote:
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind
the scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than >>> what? What is the rCLorderrCY of a function?
It's all the complicated stuff normally associated with functional
programming ...
I know about what you mentioned (I have done it all in my own programs,
and more), but where does the rCLorderrCY of a function come in?
On Sun, 5 Oct 2025 01:52:54 +0100, bart wrote:
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind
the scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than >>> what? What is the rCLorderrCY of a function?
It's all the complicated stuff normally associated with functional
programming ...
I know about what you mentioned (I have done it all in my own programs,
and more), but where does the rCLorderrCY of a function come in?
On 05/10/2025 04:15, Lawrence DrCOOliveiro wrote:
... but where does the rCLorderrCY of a function come in?
I don't know; they have to call them something I suppose. Google says
this:
"Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions."
While 'first-order functions' are the kind that comprise 99.9% of my own coding. I just call them 'functions'.
On Sun, 5 Oct 2025 11:05:17 +0100, bart wrote:
On 05/10/2025 04:15, Lawrence DrCOOliveiro wrote:
... but where does the rCLorderrCY of a function come in?
I don't know; they have to call them something I suppose. Google says
this:
"Functions that operate on other functions, either by taking them as
arguments or by returning them, are called higher-order functions."
What does it say about functions that return classes?
While 'first-order functions' are the kind that comprise 99.9% of my own
coding. I just call them 'functions'.
HererCOs another term for a distinction-without-a-difference, I think common among C++ aficionados: rCLfunctorrCY.
On 05/10/2025 22:20, Lawrence DrCOOliveiro wrote:
What does it say about functions that return classes?
Good question. I have never heard a specific name for that kind of
thing. [...]
On 06.10.2025 09:44, David Brown wrote:
On 05/10/2025 22:20, Lawrence DrCOOliveiro wrote:
What does it say about functions that return classes?
Good question. I have never heard a specific name for that kind of
thing. [...]
Assuming it's not class/type-"instances" meant but classes; what would
some practical applications be here?
As a particular form you could probably see the C++ templates as such; applying a parameter value or type/class to a class with <...> syntax
(which can logically be perceived as a function) resulting in another
class (or typed function).
Hmm..
On Sun, 5 Oct 2025 11:05:17 +0100, bart wrote:
On 05/10/2025 04:15, Lawrence DrCOOliveiro wrote:
... but where does the rCLorderrCY of a function come in?
I don't know; they have to call them something I suppose. Google says
this:
"Functions that operate on other functions, either by taking them as
arguments or by returning them, are called higher-order functions."
What does it say about functions that return classes?
On 05/10/2025 21:20, Lawrence DrCOOliveiro wrote:
On Sun, 5 Oct 2025 11:05:17 +0100, bart wrote:
On 05/10/2025 04:15, Lawrence DrCOOliveiro wrote:
... but where does the rCLorderrCY of a function come in?
I don't know; they have to call them something I suppose. Google says
this:
"Functions that operate on other functions, either by taking them as
arguments or by returning them, are called higher-order functions."
What does it say about functions that return classes?
That sounds like a regular function that returns a type.
Whether that's an existing type created the usual way, or it is
synthesised by the function, would be a different kind of language
feature.
On Mon, 6 Oct 2025 11:03:59 +0100, bart wrote:
On 05/10/2025 21:20, Lawrence DrCOOliveiro wrote:
On Sun, 5 Oct 2025 11:05:17 +0100, bart wrote:
On 05/10/2025 04:15, Lawrence DrCOOliveiro wrote:
... but where does the rCLorderrCY of a function come in?
I don't know; they have to call them something I suppose. Google says
this:
"Functions that operate on other functions, either by taking them as
arguments or by returning them, are called higher-order functions."
What does it say about functions that return classes?
That sounds like a regular function that returns a type.
Whether that's an existing type created the usual way, or it is
synthesised by the function, would be a different kind of language
feature.
Each call returns a new type. Not (usually) much point in having a
function which could only return one type.
Given that a class includes its own collection of methods (i.e. functions
by another name), would that make such a class factory an rCLeven-higher- orderrCY function?
On 06/10/2025 23:01, Lawrence DrCOOliveiro wrote:
Given that a class includes its own collection of methods (i.e.
functions by another name), would that make such a class factory an
rCLeven-higher- orderrCY function?
It depends. If the function includes the new class in itself, and any
code inside the class refers to captured locals in the enclosing
function (including having parametised elements according to any
arguments passed to the function), then it crosses the line.
In this case it wouldn't be a mere type that is returned, but callable
code; it's even more complex than a function factory.
It also crosses the line in other ways, in that I wouldn't have anything
to do with such code.
On Mon, 6 Oct 2025 23:46:56 +0100, bart wrote:
On 06/10/2025 23:01, Lawrence DrCOOliveiro wrote:
Given that a class includes its own collection of methods (i.e.
functions by another name), would that make such a class factory an
rCLeven-higher- orderrCY function?
It depends. If the function includes the new class in itself, and any
code inside the class refers to captured locals in the enclosing
function (including having parametised elements according to any
arguments passed to the function), then it crosses the line.
In this case it wouldn't be a mere type that is returned, but callable
code; it's even more complex than a function factory.
A class or function object includes both code and data. The code is
typically generated once, at compile time, but the actual object (binding
of code plus data) is created dynamically, on every onvocation.
It also crosses the line in other ways, in that I wouldn't have anything
to do with such code.
If yourCOre talking about generating code at run time, compilers do that all the time. You wouldnrCOt have anything to do with compilers??
If you have "thousands" of functions in one application, thenOne project that I am working on has more than 8000 exported functions
you're talking about something more substantial than anything I've
ever written. I suspect that such monsters are essentially not
maintainable and not understandable by humans, which is why new bugs
are always being discovered, usually only when something catastrophic
happens.
and of order 15000 in total (that is including internal functions
which can be only called within modules). I find it maintainable
partially because there is a lot of small functions. More precisely
main part of the program is about 210 thousends of wc lines
(about 120 kloc, that is after removing empty lines and commensts).
There is a lot of 1 line functions.
Concerning "what I have written", I only written part (few percent) of
it, the rest is due to other people. And yes, there are bugs. This
is mainly because program is doing a lot of complex things. One
certainly could simplify some parts, but I do not think that major
reduction in size it possible without dropping functionality. [...]
And to have some perspective, let me mention discussion of Open
Office startup. On Linux Open Office used to take substantial
time to start up. The explantion was: Open Office had about
250000 exported functions spread among tens of shared libraries.
You have to show him how to catch the first fish!-a-a-a-a-a"Teach a man to fish ..." and all that jazz.Try "man a68g"?I was hoping that you could, you know, just tell me!
It does vary: the start of it looks like this:
SYNOPSIS
-a-a-a-a a68g [-a-a-a-a-a-a-a-a-a |-a-a-a-a-a-a-a |-a-a-a-a-a-a-a [string]] [-a-a-a-a-a-a-a-a-a-a-a-a | -a-a-a-a-a-a-a ]
It turns out that all option names in this summary and in the
elaborations later on, are blanked. Apparently they are displayed
in a light-grey text, which is also the background colour of my
console window! > I get similar problems with Clang when it displays error messages;
I have to capture to a file to find what's wrong.> I don't get that with gcc, since it sets the background to black,
but then doesn't bother restoring it afterwards. You'd think these
programs would get the easy stuff right! Especially given the vast
numbers of users that can give feedback.
If you do use --listing etc, then it first /runs/ the program before
it generates the output files, which is unexpected [...].
On 03/10/2025 23:24, Waldek Hebisch wrote:
[I wrote:]
-a-a-a-a-a-a-a If you have "thousands" of functions in one application, thenOne project that I am working on has more than 8000 exported functions
you're talking about something more substantial than anything I've
ever written.-a I suspect that such monsters are essentially not
maintainable and not understandable by humans, which is why new bugs
are always being discovered, usually only when something catastrophic
happens.
and of order 15000 in total (that is including internal functions
which can be only called within modules).-a I find it maintainable
partially because there is a lot of small functions.-a More precisely
main part of the program is about 210 thousends of wc lines
(about 120 kloc, that is after removing empty lines and commensts).
There is a lot of 1 line functions.
-a-a-a-aLater you say "I only written part (few percent) of it, the rest
is due to other people."-a If, for the sake of a number, we assume that
"few" is around 5, then your part of it is ~400 exported functions out
of ~750 total, and around 6 kloc.-a That suggests that your part is a
much more reasonably-sized program, but there are absurdly too many functions.-a Someone seems to have made a fetish out of one-liners!
-a-a-a-aYou are left with two problems:-a (a) By implication, your
project needs ~20 programmers;-a Brooks's "Mythical Man Month" addresses
the problems caused thereby;-a no need for me to elaborate.-a (b) No
normal person can use 8000 functions;-a not really even your own 400.
It's too many to keep in your head [see also a nearby article which I
will soon get around to writing (:-)].
Concerning "what I have written", I only written part (few percent) of
it, the rest is due to other people.-a And yes, there are bugs.-a This
is mainly because program is doing a lot of complex things.-a One
certainly could simplify some parts, but I do not think that major
reduction in size it possible without dropping functionality. [...]
-a-a-a-aYes, but the Unix philosophy /used/ to be to write tools rather
then monsters.-a These days, people seem to insist on monstrosities.-a I don't see that as an improvement.
And to have some perspective, let me mention discussion of Open
Office startup.-a On Linux Open Office used to take substantial
time to start up.-a The explantion was: Open Office had about
250000 exported functions spread among tens of shared libraries.
-a-a-a-aI believe you, but that's ridiculous.-a Something somewhere
has lost its way.
On 04/10/2025 19:46, bart wrote:
[I wrote:]
You have to show him how to catch the first fish!-a-a-a-a-a"Teach a man to fish ..." and all that jazz.Try "man a68g"?I was hoping that you could, you know, just tell me!
-a-a-a-aI, perhaps over-optimistically, assumed that you are
a sufficiently experienced programmer to be able to understand
simple manual/guide entries with no more than a pointer to the
most relevant key words.
[...]
It does vary: the start of it looks like this:
SYNOPSIS
-a-a-a-a-a a68g [-a-a-a-a-a-a-a-a-a |-a-a-a-a-a-a-a |-a-a-a-a-a-a-a [string]] [-a-a-a-a-a-a-a-a-a-a-a-a |
-a-a-a-a-a-a-a ]
It turns out that all option names in this summary and in the
elaborations later on, are blanked. Apparently they are displayed
in a light-grey text, which is also the background colour of my
console window! > I get similar problems with Clang when it displays
error messages;
I have to capture to a file to find what's wrong.> I don't get that
with gcc, since it sets the background to black,
but then doesn't bother restoring it afterwards. You'd think these
programs would get the easy stuff right! Especially given the vast
numbers of users that can give feedback.
-a-a-a-aAll the above suggests to me that the problem is nothing to
do with "a68g", nor with Clang nor Gcc, but with something in your
set-up;
fancy parameters or settings] white on my usual black background,
black on a white background, and a contrast colour on several other backgrounds that I tried.-a I'd be very surprised if the "a68g" manual
entry is doing anything special to the colours.
[... Most gripes snipped ...]
If you do use --listing etc, then it first /runs/ the program before
it generates the output files, which is unexpected [...].
-a-a-a-aIf you don't want to run the program, use the "--check" or "--no-run" option.-a It's all in the manual [and the guide].
On 04/10/2025 19:46, bart wrote:
[I wrote:]
You have to show him how to catch the first fish!-a-a-a-a-a"Teach a man to fish ..." and all that jazz.Try "man a68g"?I was hoping that you could, you know, just tell me!
-a-a-a-aI, perhaps over-optimistically, assumed that you are
a sufficiently experienced programmer to be able to understand
simple manual/guide entries with no more than a pointer to the
most relevant key words.
I, perhaps over-optimistically, assumed that you are
a sufficiently experienced programmer
(b) No normal person can use 8000 functions; not really even your
own 400. It's too many to keep in your head ...
The problem was with 'man' (and the contents of the A68G manual which
chose those text colours), in setting a text foreground colour [...].
-a-a-a-a-aIf you don't want to run the program, use the "--check" orIt shouldn't need those extra options; it should just do the most
"--no-run" option.-a It's all in the manual [and the guide].
obvious thing without being told.
Or more importantly, without users
have to hunt across multiple different sources (website, PDF, 'man',
--help) which all seem to give conflicting info.
On 07/10/2025 16:37, Andy Walker wrote:
[...]
The problem was with 'man' (and the contents of the A68G manual which
chose those text colours), in setting a text foreground colour oblivious
to the fact that it clashed with the screen background colour. [...]
The end result was that 'man a68g' gave me gobbledygook: lots of empty
sets of square brackets.
[...] It's all in the manual [and the guide].
It shouldn't need those extra options; it should just do the most
obvious thing without being told.
Or more importantly, without users
have to hunt across multiple different sources (website, PDF, 'man',
--help) which all seem to give conflicting info.
On 07/10/2025 17:16, bart wrote:
The problem was with 'man' (and the contents of the A68G manual which
chose those text colours), in setting a text foreground colour [...].
The "a68g" manual entry does nothing at all to choose text or
any other colours. On my system "man" also does nothing to choose or
set colours. If you get anything different from the colours you have
chosen for foreground and background, it's nothing at all to do with
A68G [I've checked on my system] and probably not to do with "man" [I
don't have source for "man" so can't check in detail]. [...]
It turns out that all option names in this summary and in the
elaborations later on, are blanked. Apparently they are displayed in a >light-grey text, which is also the background colour of my console window!
On 07/10/2025 00:05, Lawrence DrCOOliveiro wrote:
On Mon, 6 Oct 2025 23:46:56 +0100, bart wrote:
It also crosses the line in other ways, in that I wouldn't have
anything to do with such code.
If yourCOre talking about generating code at run time, compilers do
that all the time. You wouldnrCOt have anything to do with
compilers??
Remember you were asking about higher order functions?
Man is effectively a nroff formatted text [...].
You may already know that (my hint wasn't meant disrespectfully),
but just that there's no need to examine the 'man' source code.
And yes, bart's problems are different from what he thinks it is.I couldn't possibly comment!
On 08.10.2025 00:58, Andy Walker wrote:
On 07/10/2025 17:16, bart wrote:
The problem was with 'man' (and the contents of the A68G manual which
chose those text colours), in setting a text foreground colour [...].
The "a68g" manual entry does nothing at all to choose text or
any other colours. On my system "man" also does nothing to choose or
set colours. If you get anything different from the colours you have
chosen for foreground and background, it's nothing at all to do with
A68G [I've checked on my system] and probably not to do with "man" [I
don't have source for "man" so can't check in detail]. [...]
Man is effectively a nroff formatted text using the macro package
'an' predefined for manuals; the option to choose macros is -m so
this results in '-man'. Just try nroff -man a68g.1 | less
to get an equivalent output to man a68g
You may already know that (my hint wasn't meant disrespectfully),
but just that there's no need to examine the 'man' source code.
And yes, bart's problems are different from what he thinks it is.
I donrCOt know how many functions/methods/classes/whatever the standard Python library <https://docs.python.org/3/library/> provides, but I
suspect itrCOs more than 400, though much less than 8000.
On 05/10/2025 05:15, Lawrence DrCOOliveiro wrote:
On Sun, 5 Oct 2025 01:52:54 +0100, bart wrote:
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind >>>>> the scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than >>>> what? What is the rCLorderrCY of a function?
It's all the complicated stuff normally associated with functional
programming ...
I know about what you mentioned (I have done it all in my own programs,
and more), but where does the rCLorderrCY of a function come in?
It comes from the similar terms in logic.
"zero-order logic", or "prepositional logic", deals with statements
about variables - "x > 0". First-order logic deals with statements
about statements - "for all x > 0, x*x > 0".
Second-order logic deals
with statements about these . "there exists a predicate P such that for
all x, x > 0 implies P(x)". And so on. Once you get beyond first-order logic, you usually just talk about "higher-order logic" - I don't think there is much interest in thinking about, say, fourth-order logic by itself.
In terms of programming, "zero-order functions" would be statements or expressions. "First-order functions" are normal functions in a language like C or Pascal (and presumably also Algol, though I have little familiarity with that language). "Second-order functions" or "Higher
order functions" are functions that deal with functions. As Bart said, these are very common in functional programming languages. But they are also very common in more modern or advanced imperative languages.
Somewhat contrary to certain other posters, higher order functions are
not "complicated stuff", and language features such as lambdas,
closures, and nested functions do not imply or require higher order functions - though any language that supports higher order functions
will support these features.
For example, C does not support higher order functions - and will still
not do so even if the proposal for introducing lambdas to the language
is accepted. Pascal has nested functions, but not higher order functions.
C++, on the other hand, has lambdas and higher-order functions, but not nested functions.
To support higher order functions, a language has to be able to handle functions as first-class objects - it has to be able to take them as function parameters, and return them as functions. It is not sufficient
to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
Some people tried to make much more detailed distinctions (for example
Russel and Whitehead), but those are rather special and less popular.
OTOH higher order functions are used as propagande/advertising term ...
AFAICS one can create sensible language which supports
higher order functions but has no closures or nested functions.
BTW: people doing functional programming sometimes claim that to support
it one needs garbage collection. I arrived at the idea above thinking
which parts of functional programming can work well without garbage collection.
David Brown <david.brown@hesbynett.no> wrote:
To support higher order functions, a language has to be able to handle
functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient
to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one. Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C. Lambdas in C and C++ are problematic as they are not usual way to represent functions in C or
C++.
OTOH a tiny extention to C could give reasonable support. Namely,
while other languages may implicitly allocate heap memory, C requires explicit calls to 'malloc' (and 'free'). Similarly, natural way
to create new function in C would be appropriate allocation function,
say called 'falloc' (an matching 'ffree'). 'falloc' should take
pointer to a function 'f', a value 'v' and probably number of arguments
'n' and return poiter 'g' so that call
(*g)(a_1,\dots, a_n)
is equvalent to
(*f)(v, a_1,\dots, a_n)
taking a 'v' a pointer this allows passing arbitrary amount of extra
data to 'f'.
With such extention C would allow reasonably natural expression of
things done in other languages claiming to support higher order functions. People using other languages may disregard this as too primitive,
but IMO it would very natural in C.
$ find /gar/packages/python3/lib/python3.14 -name '[^_]*.py' \
-execdir grep '^\(def\|class\) [^_]' {} + | wc -l
3707
If you count methods within classes as well, it's a bit over 10000.
There will also be names exported by modules implemented in C, and some dynamically-generated names, so the actual numbers will be a bit higher,
but the order of magnitude seems correct.
[...]
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one.
Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C. Lambdas in C and C++ are problematic as they are not usual way to represent functions in C or
C++.
[...]
BTW: people doing functional programming sometimes claim that to
support it one needs garbage collection. I arrived at the idea
above thinking which parts of functional programming can work
well without garbage collection.
I don't care about the problems with man.
But people are telling me to
use 'man' to find out the answer to a question
instead of just telling me.
[...]
So this is someone's fault; I doubt it is mine.
If my choice of screen
background is bona fide, then it is crass to display text that clashes
with the background.
The question BTW was how to view the C generated by A68G when it
compiles code (this after it was established that it does so).
And the answer is rather involved, and would not have been directly
answered by whatever 'man a68g' shows, even if visible. It involves 4 combined options plus a process.
On 03/10/2025 23:24, Waldek Hebisch wrote:
[I wrote:]
If you have "thousands" of functions in one application, thenOne project that I am working on has more than 8000 exported functions
you're talking about something more substantial than anything I've
ever written. I suspect that such monsters are essentially not
maintainable and not understandable by humans, which is why new bugs
are always being discovered, usually only when something catastrophic
happens.
and of order 15000 in total (that is including internal functions
which can be only called within modules). I find it maintainable
partially because there is a lot of small functions. More precisely
main part of the program is about 210 thousends of wc lines
(about 120 kloc, that is after removing empty lines and commensts).
There is a lot of 1 line functions.
Later you say "I only written part (few percent) of it, the rest
is due to other people." If, for the sake of a number, we assume that
"few" is around 5, then your part of it is ~400 exported functions out
of ~750 total, and around 6 kloc. That suggests that your part is a
much more reasonably-sized program, but there are absurdly too many functions. Someone seems to have made a fetish out of one-liners!
You are left with two problems: (a) By implication, your
project needs ~20 programmers; Brooks's "Mythical Man Month" addresses
the problems caused thereby; no need for me to elaborate.
(b) No
normal person can use 8000 functions; not really even your own 400.
It's too many to keep in your head [see also a nearby article which I
will soon get around to writing (:-)].
Concerning "what I have written", I only written part (few percent) of
it, the rest is due to other people. And yes, there are bugs. This
is mainly because program is doing a lot of complex things. One
certainly could simplify some parts, but I do not think that major
reduction in size it possible without dropping functionality. [...]
Yes, but the Unix philosophy /used/ to be to write tools rather
then monsters. These days, people seem to insist on monstrosities. I
don't see that as an improvement.
On 08.10.2025 18:41, bart wrote:
I don't care about the problems with man.
Which "problems with man"? - In decades I've never had problems with
man. - Are you again making things up?
But people are telling me to
use 'man' to find out the answer to a question
I'd suggest to follow that advice
You may be lazy (and that's okay), and want others do your homework;
On 08.10.2025 22:21, Waldek Hebisch wrote:
[...]
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making
explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one.
Classic Pascal can make new functions,
Can you explain that. (I don't recall to be able to do composition
of "new" functions other than by explicitly defining that function.
Or if you meant something else please elaborate.)
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C. Lambdas in C and C++ are
problematic as they are not usual way to represent functions in C or
C++.
[...]
BTW: people doing functional programming sometimes claim that to
support it one needs garbage collection. I arrived at the idea
above thinking which parts of functional programming can work
well without garbage collection.
Can you elaborate on that. (It never occurred to me that [technical]
garbage collection would be necessary for the functional programming paradigm.)
f having access to v and using it to produce result, call to f
will give different results depending on value of v. So effectively
BTW2: Now people frequently write C++ code which returns objects
having reference to dynamically allocated memory. Destructor
takes case of freeing memory, so this is correct. But efficiency
depends on copy constractors and compiler ability to optimize them.
If code is too complex to optimize there may be a lot of
copying and consequently such code may be quite inefficient.
antispam@fricas.org (Waldek Hebisch) wrote or quoted:
f having access to v and using it to produce result, call to f
will give different results depending on value of v. So effectively
Nested functions or procedures in standard Pascal (ISO 7185:1990
or ISO 10206:1990) may support "downward funargs", but they
do not support "upward funargs" AFAIK.
If I understand this correct, it means you cannot get two different
closures (which are differing at the same time) from the same
nested function or procedure,
because the enclosing function or
procedure only ever provides one single stack frame
and the behavior
of the closure would not be defined after the end of the lifetime of
its enclosing function or procedure.
Stefan Ram <ram@zedat.fu-berlin.de> wrote:
antispam@fricas.org (Waldek Hebisch) wrote or quoted:Yes, that is frequently used terminalogy. But it means a bit
f having access to v and using it to produce result, call to for ISO 10206:1990) may support "downward funargs", but they
will give different results depending on value of v. So effectively >>Nested functions or procedures in standard Pascal (ISO 7185:1990
do not support "upward funargs" AFAIK.
different thing than you appear to think.
Garbage collection solves this problem: allocate memory when needed
and take care to pass/return correct values, but you do not need to
worry when to free memory. This makes quite a big difference in
practice.
One can imagine alternative solutions. By considering "ownership" of
memory and using someting like Rust borrow checker it should be
possible for compiler to automatically insert 'free' in needed
places. But in such case possible transfers of ownership effectively
are part of function type, so there are unnatural from user point of
view restrictions on what can be called.
BTW: I first time met this problem many years ago when my
[colleague] came to me asking for advice. He wanted to do operations
on matrices via function calls. IIRC he wanted to allocate them
dynamically and return allocated value. Actual operations could be
easily written in Pascal. But there were no way to safely free
allocated matrices, defeating the purpose of returning allocated
matrices.
On 08/10/2025 21:21, Waldek Hebisch wrote:
David Brown <david.brown@hesbynett.no> wrote:
To support higher order functions, a language has to be able to handle
functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient >>> to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making
explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one. Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C. Lambdas in C and C++ are
problematic as they are not usual way to represent functions in C or
C++.
OTOH a tiny extention to C could give reasonable support. Namely,
while other languages may implicitly allocate heap memory, C requires
explicit calls to 'malloc' (and 'free'). Similarly, natural way
to create new function in C would be appropriate allocation function,
say called 'falloc' (an matching 'ffree'). 'falloc' should take
pointer to a function 'f', a value 'v' and probably number of arguments
'n' and return poiter 'g' so that call
(*g)(a_1,\dots, a_n)
is equvalent to
(*f)(v, a_1,\dots, a_n)
taking a 'v' a pointer this allows passing arbitrary amount of extra
data to 'f'.
With such extention C would allow reasonably natural expression of
things done in other languages claiming to support higher order functions. >> People using other languages may disregard this as too primitive,
but IMO it would very natural in C.
Last week I posted a link to a 'man or boy' test; this a version in C:
https://rosettacode.org/wiki/Man_or_boy_test#C
This test requires local functions with full closures to implement
natively. The first C version emulates what is needed.
I tweaked that version so that is used N=13 instead of N=10, and
evaluates the function 10,000 times. The second version uses C
extensions to provide the higher level functionality.
These are timings I got for the two versions:
First C version (gcc -O2: 0.8 seconds
(Tiny C): 1.8 seconds
Second C version (gcc -O2): 590 seconds (extrapolated from 1K loops)
(gcc -O0): 940 seconds
So avoiding those complex functional features makes even TCC's poor code
300 times that optimised C. And nearly 800:1 better comparing -O2 with -O2.
bart <bc@freeuk.com> wrote:
On 08/10/2025 21:21, Waldek Hebisch wrote:
David Brown <david.brown@hesbynett.no> wrote:
To support higher order functions, a language has to be able to handle >>>> functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient >>>> to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making
explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one. Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C. Lambdas in C and C++ are
problematic as they are not usual way to represent functions in C or
C++.
OTOH a tiny extention to C could give reasonable support. Namely,
while other languages may implicitly allocate heap memory, C requires
explicit calls to 'malloc' (and 'free'). Similarly, natural way
to create new function in C would be appropriate allocation function,
say called 'falloc' (an matching 'ffree'). 'falloc' should take
pointer to a function 'f', a value 'v' and probably number of arguments
'n' and return poiter 'g' so that call
(*g)(a_1,\dots, a_n)
is equvalent to
(*f)(v, a_1,\dots, a_n)
taking a 'v' a pointer this allows passing arbitrary amount of extra
data to 'f'.
With such extention C would allow reasonably natural expression of
things done in other languages claiming to support higher order functions. >>> People using other languages may disregard this as too primitive,
but IMO it would very natural in C.
Last week I posted a link to a 'man or boy' test; this a version in C:
https://rosettacode.org/wiki/Man_or_boy_test#C
This test requires local functions with full closures to implement
natively. The first C version emulates what is needed.
I tweaked that version so that is used N=13 instead of N=10, and
evaluates the function 10,000 times. The second version uses C
extensions to provide the higher level functionality.
These are timings I got for the two versions:
First C version (gcc -O2: 0.8 seconds
(Tiny C): 1.8 seconds
Second C version (gcc -O2): 590 seconds (extrapolated from 1K loops)
(gcc -O0): 940 seconds
So avoiding those complex functional features makes even TCC's poor code
300 times that optimised C. And nearly 800:1 better comparing -O2 with -O2.
It is not clear to me what you measure. I used:
#include <stdio.h>
#define INT(body) ({ int lambda(){ body; }; lambda; })
int
main(){
int a(int k, int xl(), int x2(), int x3(), int x4(), int x5()){
int b(){
return a(--k, b, xl, x2, x3, x4);
}
return k<=0 ? x4() + x5() : b();
}
int res = 0;
int i;
for(i = 0; i < 10000; i++) {
res += a(13, INT(return 1), INT(return -1), INT(return -1), INT(return
1), INT(return 0));
}
printf("res = %d\n", res);
}
Compiling this using:
gcc -O -Wall man1.c -o man1
I get 0.895s. Using -O2 I get 0.693s. Version without loop, but using
13 as an argument (as above) takes 0.002s. This is on a fast machine,
on really slow mini-PC I get 10.117s at -O2. I suspect that your time is
due to operationg system, the results above are for Linux on x86_64.
On Linux on 1GHz Risc-V at -O2 I get:
real 0m28.910s
user 0m14.512s
sys 0m14.381s
which means that about half of execution time is in system calls.
Looking at assembly shows that the machine code is calling '__riscv_flush_icache', that is it flushing cache to make sure
that processor will execute freshly generated instructions
(not needed on PC-s). System calls are expensive, and flushing
cache also adds slowdown, so this adds substantial cost. Still,
the Risc-V machine is quite slow, and can do this much faster
than your estimate of execution time.
For me on fast machine first (emulated) version takes 0.331s when
compiled by gcc at -O2 and 0.767s when compiled by tcc. On Risc-V
emulated version (compiled by gcc -O2) needs 3.553s.
So emulated version is clearly faster, especially if version
using nested functions need support from operating system.
But the point of using nested functions/closures is that code
is simpler.
The test is doing nonsense operation, so one
can miss simplicty, but clearly emulated version is longer.
More important, in bigger program all involved calls must be
dane in nonstandard way, which adds work (potentially quite a lot).
In version with nested functions only efort is in definition
of nested functions, calls are standard calls and rest of program
can be written as if there were no use of nested functions.
As you can see, when properly supported extra cost of nested
functions is moderate.
BTW: Some language implementation use approach which is
equivalent to emulated version. For heavy use of nested
functions this may lead to faster code (like in this example).
But when "emulation" is part of language implementation
all calls would need to do extra work, slowing down normall
calls. In language like C this is deemed unacceptable,
nested function are rarely use so they must pay extra,
while normal call work at maximal speed.
David Brown <david.brown@hesbynett.no> wrote:
On 05/10/2025 05:15, Lawrence DrCOOliveiro wrote:
On Sun, 5 Oct 2025 01:52:54 +0100, bart wrote:
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
Now, Algol68, the language, supports some very hairy features behind >>>>>> the scenes, associated with higher-order functions and such.
I never understood that rCLhigher-order functionsrCY business. Higher than
what? What is the rCLorderrCY of a function?
It's all the complicated stuff normally associated with functional
programming ...
I know about what you mentioned (I have done it all in my own programs,
and more), but where does the rCLorderrCY of a function come in?
It comes from the similar terms in logic.
"zero-order logic", or "prepositional logic", deals with statements
about variables - "x > 0". First-order logic deals with statements
about statements - "for all x > 0, x*x > 0".
To say it mildly this is rather nontradaditional exposition of logic.
Usual treatement is that "prepositional logic" deals with atomic
logical facts and connectives, like "A or not A". In practice
people instead of 'A' above write things like 'x > 0', but
prepositional logic does not deal with meaning of 'x > 0',
it is just treated as an opaque thing that have some logical value.
First order logic deals with variables and quantifiers: without
variables quantifiers make no sense. One can do some reasonings
without explicitely using quantifiers, but that uses rules which
go beyond prepositional logic and in fact intuitive meaning of
such rules involves quantifiers.
Second-order logic deals
with statements about these . "there exists a predicate P such that for
all x, x > 0 implies P(x)". And so on. Once you get beyond first-order
logic, you usually just talk about "higher-order logic" - I don't think
there is much interest in thinking about, say, fourth-order logic by itself.
Actually, there were some attempts to define third order logic, but
it is not clear if there is any sensible definition. And before
one goes to higher level it would be better to know if third level
make sense. Second order logic is equivalent to including in logic
part of set theory: in set theory one can define relations (as a special
kind of sets) and use quantifiers about them. If you want to do some reasoning, then second order logic without set theory is weaker than
first order logic + set theory. Note that in set theory you can talk
about set which have elements which are sets and so on. Similarly
you can talk about relatoions between relations and so on. In fact transfinite induction allows to consider this well beyond countable
infinity. In usual formulation second order logic is weaker, as
it does not allow actual infinity, one just deals with finite nesting.
Some people tried to make much more detailed distinctions (for
example Russel and Whitehead), but those are rather special and
less popular.
In terms of programming, "zero-order functions" would be statements or
expressions. "First-order functions" are normal functions in a language
like C or Pascal (and presumably also Algol, though I have little
familiarity with that language). "Second-order functions" or "Higher
order functions" are functions that deal with functions. As Bart said,
these are very common in functional programming languages. But they are
also very common in more modern or advanced imperative languages.
Actually, in simplest form they appear quite early in developement
of programming languages. Namely, "deals" may mean various things.
Simplest thing is to call function passed as an argument, that is
very old thing.
Some people treat this as too simple and request
ability to create new functions and store them in variables.
In classic Pascal local functions cupture their environment, so
in this sense are new ones. They are limited, because they become
invalid after return from enclosing function. And classic Pascal
does not allow storing functions on variables.
I think that natural defintion of higher order function should
include also simple cases, so functions which just take a function
as an argument and call it should count as higher order functions.
OTOH higher order functions are used as propagande/advertising
term, so people who use this term want to exclude classic usage
and apply it only to more complicated cases. So I understand
why they give different definition, but IMO such definitions are
misleading.
Somewhat contrary to certain other posters, higher order functions are
not "complicated stuff", and language features such as lambdas,
closures, and nested functions do not imply or require higher order
functions - though any language that supports higher order functions
will support these features.
I disagree. AFAICS one can create sensible language which supports
higher order functions but has no closures or nested functions.
For example, C does not support higher order functions - and will still
not do so even if the proposal for introducing lambdas to the language
is accepted. Pascal has nested functions, but not higher order functions. >>
C++, on the other hand, has lambdas and higher-order functions, but not
nested functions.
You probably can give some argument that C++ supports higher-order
functions, but it is quite unclear to me how you can do this without acceptiong that C or Pascal support higher-order functions.
To support higher order functions, a language has to be able to handle
functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient
to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one.
Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C.
Lambdas in C and C++ are
problematic as they are not usual way to represent functions in C or
C++.
OTOH a tiny extention to C could give reasonable support. Namely,
while other languages may implicitly allocate heap memory, C requires explicit calls to 'malloc' (and 'free').
Similarly, natural way
to create new function in C would be appropriate allocation function,
say called 'falloc' (an matching 'ffree'). 'falloc' should take
pointer to a function 'f', a value 'v' and probably number of arguments
'n' and return poiter 'g' so that call
(*g)(a_1,\dots, a_n)
is equvalent to
(*f)(v, a_1,\dots, a_n)
taking a 'v' a pointer this allows passing arbitrary amount of extra
data to 'f'.
With such extention C would allow reasonably natural expression of
things done in other languages claiming to support higher order functions. People using other languages may disregard this as too primitive,
but IMO it would very natural in C.
BTW: people doing functional programming sometimes claim that to
support it one needs garbage collection. I arrived at the idea
above thinking which parts of functional programming can work
well without garbage collection.
On 08/10/2025 22:21, Waldek Hebisch wrote:
I disagree.-a AFAICS one can create sensible language which supports
higher order functions but has no closures or nested functions.
For languages that support a limited subset of higher order functions,
that would be true - by the definitions that you have been using, C
would be such a language.-a But by the time you have a language that can return new functions and assign them to variables, then you have come
most of the way towards closures and nested functions - there is no real difference between a nested function and a lambda assigned to a local variable.-a So I don't see much point in a language that supports general higher order functions and does not support closures and nested
functions.-a But perhaps you know of some examples?
I've yet to see examples [of closures] that have real uses and that
don't make code harder to grasp.
On Fri, 10 Oct 2025 20:04:14 +0100, bart wrote:
I've yet to see examples [of closures] that have real uses and that
don't make code harder to grasp.
I used a class factory in my Python wrapper for Cairo. It was
convenient to define higher-level Python wrappers around lower-level C structs, where there was a lot of commonality across those different wrappers, to save having multiple copies of the same code in the
source. Thus, defining a rCLFontExtentsrCY wrapper class around the rCLfont_extents_trCY struct became as simple as
FontExtents = def_struct_class \
(
name = "FontExtents",
ctname = "font_extents_t"
)
And rCLTextExtentsrCY as a wrapper around rCLtext_extents_trCY; but here it became convenient to add extra attributes, to return entire Rect and
Vector components as single objects, where the C code had the coordinates
as separate fields. So I define the derived attributes in an additional temporary helper class:
class TextExtentsExtra :
# extra members for TextExtents class.
@property
def bounds(self) :
"returns the bounds of the text_extents as a Rect."
return \
Rect(self.x_bearing, self.y_bearing, self.width, self.height)
#end bounds
@property
def advance(self) :
"returns the x- and y-advance of the text_extents as a Vector."
return \
Vector(self.x_advance, self.y_advance)
#end advance
#end TextExtentsExtra
and then merge that into the wrapper:
TextExtents = def_struct_class \
(
name = "TextExtents",
ctname = "text_extents_t",
extra = TextExtentsExtra
)
del TextExtentsExtra
The definition of rCLdef_struct_classrCY is 79 lines of code. For more details, see <https://gitlab.com/ldo/qahirah>.
This is a comparatively simple example; I have more complex ones
elsewhere, if you want them.
On 10/10/2025 05:18, Waldek Hebisch wrote:<snip>
bart <bc@freeuk.com> wrote:
It is not clear to me what you measure. I used:
Last week I posted a link to a 'man or boy' test; this a version in C:
https://rosettacode.org/wiki/Man_or_boy_test#C
This test requires local functions with full closures to implement
natively. The first C version emulates what is needed.
I tweaked that version so that is used N=13 instead of N=10, and
evaluates the function 10,000 times. The second version uses C
extensions to provide the higher level functionality.
These are timings I got for the two versions:
First C version (gcc -O2: 0.8 seconds
(Tiny C): 1.8 seconds
Second C version (gcc -O2): 590 seconds (extrapolated from 1K loops) >>> (gcc -O0): 940 seconds
So avoiding those complex functional features makes even TCC's poor code >>> 300 times that optimised C. And nearly 800:1 better comparing -O2 with -O2. >>
#include <stdio.h>
#define INT(body) ({ int lambda(){ body; }; lambda; })
int
main(){
int a(int k, int xl(), int x2(), int x3(), int x4(), int x5()){
int b(){
return a(--k, b, xl, x2, x3, x4);
}
return k<=0 ? x4() + x5() : b();
}
int res = 0;
int i;
for(i = 0; i < 10000; i++) {
res += a(13, INT(return 1), INT(return -1), INT(return -1), INT(return
1), INT(return 0));
}
printf("res = %d\n", res);
}
Compiling this using:
gcc -O -Wall man1.c -o man1
I get 0.895s. Using -O2 I get 0.693s. Version without loop, but using
13 as an argument (as above) takes 0.002s. This is on a fast machine,
on really slow mini-PC I get 10.117s at -O2. I suspect that your time is
due to operationg system, the results above are for Linux on x86_64.
On Linux on 1GHz Risc-V at -O2 I get:
real 0m28.910s
user 0m14.512s
sys 0m14.381s
which means that about half of execution time is in system calls.
Looking at assembly shows that the machine code is calling
'__riscv_flush_icache', that is it flushing cache to make sure
that processor will execute freshly generated instructions
(not needed on PC-s). System calls are expensive, and flushing
cache also adds slowdown, so this adds substantial cost. Still,
the Risc-V machine is quite slow, and can do this much faster
than your estimate of execution time.
For me on fast machine first (emulated) version takes 0.331s when
compiled by gcc at -O2 and 0.767s when compiled by tcc. On Risc-V
emulated version (compiled by gcc -O2) needs 3.553s.
So emulated version is clearly faster, especially if version
using nested functions need support from operating system.
But the point of using nested functions/closures is that code
is simpler.
Well, the code is smaller; that doesn't mean it is simpler! It can be
harder to know what's going on with a cryptic bit of code like this.
(I believe it was Knuth who failed to correctly predict the result of
N=10 (?), at some point in history when computing it was not practical.)
The test is doing nonsense operation, so one
can miss simplicty, but clearly emulated version is longer.
More important, in bigger program all involved calls must be
dane in nonstandard way, which adds work (potentially quite a lot).
In version with nested functions only efort is in definition
of nested functions, calls are standard calls and rest of program
can be written as if there were no use of nested functions.
As you can see, when properly supported extra cost of nested
functions is moderate.
BTW: Some language implementation use approach which is
equivalent to emulated version. For heavy use of nested
functions this may lead to faster code (like in this example).
But when "emulation" is part of language implementation
all calls would need to do extra work, slowing down normall
calls. In language like C this is deemed unacceptable,
nested function are rarely use so they must pay extra,
while normal call work at maximal speed.
I'm testing the version show below (I added the += to make it match
yours). Results on Windows are:
c:\c>gc d opt
Invoking: gcc -s -O2 d.c -o d.exe -lm -ldl -fno-strict-aliasing
Compiled d.c to d.exe
c:\c>tm d
-64200
TM: 6.01
So 6 seconds to do 100 iterations. If I try it in WSL, using 10000 iterations, then I get:
c:\c>wsl
root@DESKTOP-11:/mnt/c/c# gcc -O2 d.c -od
root@DESKTOP-11:/mnt/c/c# time ./d
-6420000
real 0m1.184s
user 0m1.180s
sys 0m0.001s
So under WSL it's 500 times faster. I've no idea why.
However, I work under Windows! So do lots of people. And there, the fact that these exotic features might make programs significantly slower has
to be taken into consideration.
I tried a Python version too. There, 200 iterations took 5.4 seconds on
both OSes. PyPy on Windows was twice as fast.
On 10/10/2025 22:45, Lawrence DrCOOliveiro wrote:
The definition of rCLdef_struct_classrCY is 79 lines of code. For more
details, see <https://gitlab.com/ldo/qahirah>.
I can't see any use of closures above, and I can't find those 79 lines
of code in that link. Not that it would help.
But I've found in the past with languages like Python and C++ which
offer a plethora of advanced features, that people will use them because they're there.
On Sat, 11 Oct 2025 00:29:55 +0100, bart wrote:
On 10/10/2025 22:45, Lawrence DrCOOliveiro wrote:
The definition of rCLdef_struct_classrCY is 79 lines of code. For more
details, see <https://gitlab.com/ldo/qahirah>.
I can't see any use of closures above, and I can't find those 79 lines
of code in that link. Not that it would help.
ThatrCOs where closures are used. I could copy and paste the whole thing, if you like.
But I've found in the past with languages like Python and C++ which
offer a plethora of advanced features, that people will use them because
they're there.
You could prove your point by doing what my code does more simply, in C
say, without using that rCLplethora of advanced featuresrCY. But you canrCOt manage that, can you?
I'm not quite clear what you code does, or whether I would need to do
the same.
I need simple, solid, intuitive features that anyone can understand
and use confidenly. Or if necessary, implement a different way.
On Sat, 11 Oct 2025 01:59:28 +0100, bart wrote:
I'm not quite clear what you code does, or whether I would need to do
the same.
You said you couldnrCOt see a need for this sort of functionality. This
is an example of this sort of functionality.
I need simple, solid, intuitive features that anyone can understand
and use confidenly. Or if necessary, implement a different way.
Hint:
def def_struct_class(name, ctname, extra = None) :
# defines a class with attributes that are a straightforward mapping
# of a ctypes struct. Optionally includes extra members from extra
# if specified.
ctstruct = getattr(CAIRO, ctname)
class result_class :
...
#end def_struct_class
Can you see the closure yet?
bart <bc@freeuk.com> wrote:
On 10/10/2025 05:18, Waldek Hebisch wrote:<snip>
bart <bc@freeuk.com> wrote:
Last week I posted a link to a 'man or boy' test; this a version in C: >>>>
https://rosettacode.org/wiki/Man_or_boy_test#C
This test requires local functions with full closures to implement
natively. The first C version emulates what is needed.
I tweaked that version so that is used N=13 instead of N=10, and
evaluates the function 10,000 times. The second version uses C
extensions to provide the higher level functionality.
These are timings I got for the two versions:
First C version (gcc -O2: 0.8 seconds
(Tiny C): 1.8 seconds
Second C version (gcc -O2): 590 seconds (extrapolated from 1K loops) >>>> (gcc -O0): 940 seconds
So avoiding those complex functional features makes even TCC's poor code >>>> 300 times that optimised C. And nearly 800:1 better comparing -O2 with -O2.
It is not clear to me what you measure. I used:
#include <stdio.h>
#define INT(body) ({ int lambda(){ body; }; lambda; })
int
main(){
int a(int k, int xl(), int x2(), int x3(), int x4(), int x5()){
int b(){
return a(--k, b, xl, x2, x3, x4);
}
return k<=0 ? x4() + x5() : b();
}
int res = 0;
int i;
for(i = 0; i < 10000; i++) {
res += a(13, INT(return 1), INT(return -1), INT(return -1), INT(return
1), INT(return 0));
}
printf("res = %d\n", res);
}
Compiling this using:
gcc -O -Wall man1.c -o man1
I get 0.895s. Using -O2 I get 0.693s. Version without loop, but using
13 as an argument (as above) takes 0.002s. This is on a fast machine,
on really slow mini-PC I get 10.117s at -O2. I suspect that your time is >>> due to operationg system, the results above are for Linux on x86_64.
On Linux on 1GHz Risc-V at -O2 I get:
real 0m28.910s
user 0m14.512s
sys 0m14.381s
which means that about half of execution time is in system calls.
Looking at assembly shows that the machine code is calling
'__riscv_flush_icache', that is it flushing cache to make sure
that processor will execute freshly generated instructions
(not needed on PC-s). System calls are expensive, and flushing
cache also adds slowdown, so this adds substantial cost. Still,
the Risc-V machine is quite slow, and can do this much faster
than your estimate of execution time.
For me on fast machine first (emulated) version takes 0.331s when
compiled by gcc at -O2 and 0.767s when compiled by tcc. On Risc-V
emulated version (compiled by gcc -O2) needs 3.553s.
So emulated version is clearly faster, especially if version
using nested functions need support from operating system.
But the point of using nested functions/closures is that code
is simpler.
Well, the code is smaller; that doesn't mean it is simpler! It can be
harder to know what's going on with a cryptic bit of code like this.
(I believe it was Knuth who failed to correctly predict the result of
N=10 (?), at some point in history when computing it was not practical.)
As I wrote below, this test is doing nonsense operation. Most real
uses of higher order functions are simpler (some higher order may
even disregard them as too simple). For example, you want to
perform some operation on all elements of an aggregate. For a list
one can have mostly standard C code like:
typedef struct gen_lst gen_lst;
struct gen_lst {gen_lst * next;};
void
gen_map(void (*f)(gen_lst * lst, void * extra), gen_lst * lst, void * extra) {
for(; lst; lst = lst->next) {
f(lst, extra);
}
}
typedef struct my_lst my_lst;
struct my_lst {gen_lst * next; int val;};
void
adder1(gen_lst * lst, void * extra) {
int * aux = extra;
struct my_lst * ml = (struct my_lst *)lst;
aux += ml->val;
}
int
my_sum(my_lst * lst) {
int aux = 0;
gen_map(adder1, (gen_lst *) lst, (void *)&aux);
return aux;
}
or you can use gcc extention like:
void
gen_map2(void (*f)(gen_lst * lst), gen_lst * lst) {
for(; lst; lst = lst->next) {
f(lst);
}
}
int
my_sum2(my_lst * lst) {
int aux = 0;
void
adder(gen_lst * lst) {
struct my_lst * ml = (struct my_lst *)lst;
aux += ml->val;
}
gen_map2(adder, (gen_lst *) lst);
return aux;
}
Note that argument 'extra' is now gone.
Of course, you my ask why I do computations in this way? If all what
needs to be done is computing sum of elements on a list, than a single function would do. However, if there are more operations, then
using several looping functions spead knowledge how 'gen_lst' is
build and how to traverse it. Which may be not that bad if you
are sure that it will always be a list and all you need is summing.
But you may need more complex aggregate or multiple kinds of aggregates. Typically there are several operations one wants to do for each
kind of aggregate. And general part may be much larger than type
specific part. With approach as above you only need to write
type specific operations for each kind of aggregate, general parts
may be reused. And depending on details it may be possible to
share operations for different kinds of aggregates.
I do not know if you believe that this kind of coding is useful,
but I hope that it is clear that variant using nested functions
will always be simpler than variant which passes extra arguments.
So under WSL it's 500 times faster. I've no idea why.
I see. It is up to you to decide if you want to investigate deeper.
Linux version generates small pieces of executable code on the
stack. Currently security folks hate such code and want to ban
it. In Linux gcc specially marks executable needing executable
stack and if executable is marked kernel allow executing code
from the stack. Otherwise attempts to execute code from the
stack lead to exception and kernel signals error in such case.
There was some talk to use different method, and code generated
on Windows may be different. One possible implementation would
be to attempt execution on the stack, but also install signal
handler. Signal handler can then effectively interpret was in
on the stack. Clearly this would lead to much worse performance.
However, I work under Windows! So do lots of people. And there, the fact
that these exotic features might make programs significantly slower has
to be taken into consideration.
I tried a Python version too. There, 200 iterations took 5.4 seconds on
both OSes. PyPy on Windows was twice as fast.
Once you have interpreted language you get slowdown from the
interpreter, but no extra trouble due executable stack: CPU treats
bytecode as data, so in this way one can bypass security restrictions.
IIUC PyPy may generate machine code, but probably can defer anything
it can not handle to interpreter.
On 11/10/2025 01:05, Waldek Hebisch wrote:
[...]
I haven't implemented closures in a static language. (I got partway in
the dynamic one, but decided it wasn't worth the effort or extra
complexity just to run silly examples like manboy() or twice().)
[...]
On 11/10/2025 01:05, Waldek Hebisch wrote:
bart <bc@freeuk.com> wrote:
On 10/10/2025 05:18, Waldek Hebisch wrote:<snip>
bart <bc@freeuk.com> wrote:
Last week I posted a link to a 'man or boy' test; this a version in C: >>>>>
https://rosettacode.org/wiki/Man_or_boy_test#C
This test requires local functions with full closures to implement
natively. The first C version emulates what is needed.
I tweaked that version so that is used N=13 instead of N=10, and
evaluates the function 10,000 times. The second version uses C
extensions to provide the higher level functionality.
These are timings I got for the two versions:
First C version (gcc -O2: 0.8 seconds
(Tiny C): 1.8 seconds
Second C version (gcc -O2): 590 seconds (extrapolated from 1K loops) >>>>> (gcc -O0): 940 seconds
So avoiding those complex functional features makes even TCC's poor code >>>>> 300 times that optimised C. And nearly 800:1 better comparing -O2 with -O2.
It is not clear to me what you measure. I used:
#include <stdio.h>
#define INT(body) ({ int lambda(){ body; }; lambda; })
int
main(){
int a(int k, int xl(), int x2(), int x3(), int x4(), int x5()){
int b(){
return a(--k, b, xl, x2, x3, x4);
}
return k<=0 ? x4() + x5() : b();
}
int res = 0;
int i;
for(i = 0; i < 10000; i++) {
res += a(13, INT(return 1), INT(return -1), INT(return -1), INT(return
1), INT(return 0));
}
printf("res = %d\n", res);
}
Compiling this using:
gcc -O -Wall man1.c -o man1
I get 0.895s. Using -O2 I get 0.693s. Version without loop, but using >>>> 13 as an argument (as above) takes 0.002s. This is on a fast machine, >>>> on really slow mini-PC I get 10.117s at -O2. I suspect that your time is >>>> due to operationg system, the results above are for Linux on x86_64.
On Linux on 1GHz Risc-V at -O2 I get:
real 0m28.910s
user 0m14.512s
sys 0m14.381s
which means that about half of execution time is in system calls.
Looking at assembly shows that the machine code is calling
'__riscv_flush_icache', that is it flushing cache to make sure
that processor will execute freshly generated instructions
(not needed on PC-s). System calls are expensive, and flushing
cache also adds slowdown, so this adds substantial cost. Still,
the Risc-V machine is quite slow, and can do this much faster
than your estimate of execution time.
For me on fast machine first (emulated) version takes 0.331s when
compiled by gcc at -O2 and 0.767s when compiled by tcc. On Risc-V
emulated version (compiled by gcc -O2) needs 3.553s.
So emulated version is clearly faster, especially if version
using nested functions need support from operating system.
But the point of using nested functions/closures is that code
is simpler.
Well, the code is smaller; that doesn't mean it is simpler! It can be
harder to know what's going on with a cryptic bit of code like this.
(I believe it was Knuth who failed to correctly predict the result of
N=10 (?), at some point in history when computing it was not practical.)
As I wrote below, this test is doing nonsense operation. Most real
uses of higher order functions are simpler (some higher order may
even disregard them as too simple). For example, you want to
perform some operation on all elements of an aggregate. For a list
one can have mostly standard C code like:
typedef struct gen_lst gen_lst;
struct gen_lst {gen_lst * next;};
void
gen_map(void (*f)(gen_lst * lst, void * extra), gen_lst * lst, void * extra) {
for(; lst; lst = lst->next) {
f(lst, extra);
}
}
typedef struct my_lst my_lst;
struct my_lst {gen_lst * next; int val;};
void
adder1(gen_lst * lst, void * extra) {
int * aux = extra;
struct my_lst * ml = (struct my_lst *)lst;
aux += ml->val;
}
int
my_sum(my_lst * lst) {
int aux = 0;
gen_map(adder1, (gen_lst *) lst, (void *)&aux);
return aux;
}
or you can use gcc extention like:
void
gen_map2(void (*f)(gen_lst * lst), gen_lst * lst) {
for(; lst; lst = lst->next) {
f(lst);
}
}
int
my_sum2(my_lst * lst) {
int aux = 0;
void
adder(gen_lst * lst) {
struct my_lst * ml = (struct my_lst *)lst;
aux += ml->val;
}
gen_map2(adder, (gen_lst *) lst);
return aux;
}
Note that argument 'extra' is now gone.
Of course, you my ask why I do computations in this way? If all what
needs to be done is computing sum of elements on a list, than a single
function would do. However, if there are more operations, then
using several looping functions spead knowledge how 'gen_lst' is
build and how to traverse it. Which may be not that bad if you
are sure that it will always be a list and all you need is summing.
But you may need more complex aggregate or multiple kinds of aggregates.
Typically there are several operations one wants to do for each
kind of aggregate. And general part may be much larger than type
specific part. With approach as above you only need to write
type specific operations for each kind of aggregate, general parts
may be reused. And depending on details it may be possible to
share operations for different kinds of aggregates.
I do not know if you believe that this kind of coding is useful,
but I hope that it is clear that variant using nested functions
will always be simpler than variant which passes extra arguments.
TBH I found both the C and extended versions hard-going. (The spacing
either side of "*" didn't help!)
I think here you genuinely need better language features, but intuitive
ones that everyone can get their head around.
For this sort of stuff I'd prefer using a higher level language, not a
lower level one with hairy-looking synyax that has higher order
functions bolted on. For example in mine (which is both dynamic and intepreted) I can sum lists like this:
a := (10,20,30,40)
println reduce(+, a) # shows 100
I think this is typical of scripting languages. In mine however,
'reduce' isn't built-in, I have to implement it in the language itself
like this:
export func reduce(op, a)=
x := head(a)
for y in tail(a) do
x := mapss(op, x, y)
od
x
end
Only 'mapss' is built-in. There is some functional stuff going on here,
but there are no closures in the language.
So under WSL it's 500 times faster. I've no idea why.
I see. It is up to you to decide if you want to investigate deeper.
Linux version generates small pieces of executable code on the
stack. Currently security folks hate such code and want to ban
it. In Linux gcc specially marks executable needing executable
stack and if executable is marked kernel allow executing code
from the stack. Otherwise attempts to execute code from the
stack lead to exception and kernel signals error in such case.
There was some talk to use different method, and code generated
on Windows may be different. One possible implementation would
be to attempt execution on the stack, but also install signal
handler. Signal handler can then effectively interpret was in
on the stack. Clearly this would lead to much worse performance.
I haven't implemented closures in a static language. (I got partway in
the dynamic one, but decided it wasn't worth the effort or extra
complexity just to run silly examples like manboy() or twice().)
Anyway, I wouldn't have expected to have to use executable memory.
I *might* have expected to do whatever the emulated version does, which doesn't need executable memory either!
--However, I work under Windows! So do lots of people. And there, the fact >>> that these exotic features might make programs significantly slower has
to be taken into consideration.
I tried a Python version too. There, 200 iterations took 5.4 seconds on
both OSes. PyPy on Windows was twice as fast.
Once you have interpreted language you get slowdown from the
interpreter, but no extra trouble due executable stack: CPU treats
bytecode as data, so in this way one can bypass security restrictions.
IIUC PyPy may generate machine code, but probably can defer anything
it can not handle to interpreter.
The interesting thing about the interpreted version, is that my
emulation of closures is so much faster than other interpreted languages where they are built-in. Because I have to implement them in bytecode,
but those others can use the underlying C or whatever.
Using LuaJIT, a well-regarded and usually well-performing tracing-JIT product, the 10K x manbody(13) test takes 32 seconds, twice as long as
my own pure interpreter.
(Because the workings are so obscure, I don't know Python/Lua enough to
do emulated versions to see if they are any faster.)
On 11.10.2025 12:25, bart wrote:
On 11/10/2025 01:05, Waldek Hebisch wrote:
[...]
I haven't implemented closures in a static language. (I got partway in
the dynamic one, but decided it wasn't worth the effort or extra
complexity just to run silly examples like manboy() or twice().)
You obviously missed the point.
The function twice() that someone
suggested was a simple example for a _functional composition_, a
principle that (in the more general case) is a fundamental "higher
order" functional principle. The somewhat more general case,
instead of f(f(x)), would be f(g(x)) - without parameter typically
written as f o g - or (f o g)(x) . It's certainly not "silly" (or
only to the folks who didn't understand its purpose here). So the
question was basically whether a language supports [features for]
functional composition or not. Say, with functions as first class
objects, for example something like funct h = f o g; print h(x)
And as that special case funct twice = f o f; print twice(x)
For mathematics - and for abstrated representation in programming
languages' context - it's a fundamental concept and quite useful.
On 08/10/2025 22:21, Waldek Hebisch wrote:<snip>
David Brown <david.brown@hesbynett.no> wrote:
On 05/10/2025 05:15, Lawrence DrCOOliveiro wrote:
On Sun, 5 Oct 2025 01:52:54 +0100, bart wrote:
On 04/10/2025 23:00, Lawrence DrCOOliveiro wrote:
On Sat, 4 Oct 2025 15:07:23 +0100, bart wrote:
In terms of programming, "zero-order functions" would be statements or
expressions. "First-order functions" are normal functions in a language >>> like C or Pascal (and presumably also Algol, though I have little
familiarity with that language). "Second-order functions" or "Higher
order functions" are functions that deal with functions. As Bart said,
these are very common in functional programming languages. But they are >>> also very common in more modern or advanced imperative languages.
Actually, in simplest form they appear quite early in developement
of programming languages. Namely, "deals" may mean various things.
Simplest thing is to call function passed as an argument, that is
very old thing.
That is usually handled by passing a pointer to a function. Pointers
(or references of some kind) to functions are a much simpler concept
than "dealing" with functions themselves.
Some people treat this as too simple and request
ability to create new functions and store them in variables.
In classic Pascal local functions cupture their environment, so
in this sense are new ones. They are limited, because they become
invalid after return from enclosing function. And classic Pascal
does not allow storing functions on variables.
I think that natural defintion of higher order function should
include also simple cases, so functions which just take a function
as an argument and call it should count as higher order functions.
OTOH higher order functions are used as propagande/advertising
term, so people who use this term want to exclude classic usage
and apply it only to more complicated cases. So I understand
why they give different definition, but IMO such definitions are
misleading.
Maybe. It is not unreasonable to say that functions taking parameters
of function pointer type are "higher order". But I would only say that
a language supports higher order functions if it can also return new functions from functions - and assign these to variables and pass them around.
It is not enough to simply support function pointers, such as
C. Under the hood, of course, these returned functions will, in a
compiled language, be handled with structs for closures and pointers to function code. But to be a language that supports higher order
functions, this needs to be hidden in the syntax of the language.
Somewhat contrary to certain other posters, higher order functions are
not "complicated stuff", and language features such as lambdas,
closures, and nested functions do not imply or require higher order
functions - though any language that supports higher order functions
will support these features.
I disagree. AFAICS one can create sensible language which supports
higher order functions but has no closures or nested functions.
For languages that support a limited subset of higher order functions,
that would be true - by the definitions that you have been using, C
would be such a language. But by the time you have a language that can return new functions and assign them to variables, then you have come
most of the way towards closures and nested functions - there is no real difference between a nested function and a lambda assigned to a local variable. So I don't see much point in a language that supports general higher order functions and does not support closures and nested
functions. But perhaps you know of some examples?
For example, C does not support higher order functions - and will still
not do so even if the proposal for introducing lambdas to the language
is accepted. Pascal has nested functions, but not higher order functions. >>>
C++, on the other hand, has lambdas and higher-order functions, but not
nested functions.
You probably can give some argument that C++ supports higher-order
functions, but it is quite unclear to me how you can do this without
acceptiong that C or Pascal support higher-order functions.
template <typename F>
auto do_twice(F f) {
return [f](auto x) {
return f(f(x));
};
}
int add_one(int x) { return x + 1; }
int test_add_two(int x) {
auto add_two = do_twice(add_one);
return add_two(x);
}
You can't write something equivalent to the higher order function
"do_twice" in C or Pascal, taking a function and returning a new one.
Of course C++'s support and syntax here is somewhat suboptimal, compared
to functional programming languages or other modern languages that have
had such features from the start.
To support higher order functions, a language has to be able to handle
functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient >>> to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making
explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one.
Yes, that is the main limitation of C (in this context) - and the
limitation is because C only supports functions through function
pointers. Technically, C only supports declaring and defining
functions, and taking their address - even when you "call" a function in
C, you are applying the call operator to a function pointer. In order
to support more advanced higher order functions - such as returning new functions - a language must be able to support larger underlying data structures while maintaining the syntax of function calls. It has to
handle captures, closures and/or other details. A "function" is not
merely a pointer to an address in code memory.
Classic Pascal can make new functions,
but one can not legaly return freshly created function, so in this
aspect is only marginally better than C.
It's a good while since I programmed in Pascal, but I was not aware of
any such facility in Pascal. It has nested functions, but that is not
the same thing.
Lambdas in C and C++ are
problematic as they are not usual way to represent functions in C or
C++.
It is correct that lambdas in C++ (C does not have lambdas, though there
is a proposal to add them to the language) are not as simple as normal functions. They are basically an alternative syntax for functors, which
are classes with an overridden call operator.
OTOH a tiny extention to C could give reasonable support. Namely,
while other languages may implicitly allocate heap memory, C requires
explicit calls to 'malloc' (and 'free').
Requiring memory allocation to be explicit is considered a strength of
C. Changing that is not going to go down well. (Lambdas in C++ do not require implicit memory allocation. C++ lambdas and functions that
return new functions have certain visibility limitations in C++ - they cannot cross boundaries of independently compiled units, as each lambda
has a unique type. Having separate compilation and independence while retaining full flexibility is supported in the language and standard library, with std::function<>, but it comes with a considerable overhead
- including heap allocations.)
Similarly, natural way
to create new function in C would be appropriate allocation function,
say called 'falloc' (an matching 'ffree'). 'falloc' should take
pointer to a function 'f', a value 'v' and probably number of arguments
'n' and return poiter 'g' so that call
(*g)(a_1,\dots, a_n)
is equvalent to
(*f)(v, a_1,\dots, a_n)
taking a 'v' a pointer this allows passing arbitrary amount of extra
data to 'f'.
With such extention C would allow reasonably natural expression of
things done in other languages claiming to support higher order functions. >> People using other languages may disregard this as too primitive,
but IMO it would very natural in C.
BTW: people doing functional programming sometimes claim that to
support it one needs garbage collection. I arrived at the idea
above thinking which parts of functional programming can work
well without garbage collection.
Garbage collection is not, AFAIK, necessary for common functional programming techniques. But it might make some of them more convenient
or efficient to use.
On 10/10/2025 16:40, David Brown wrote:
On 08/10/2025 22:21, Waldek Hebisch wrote:
I disagree.-a AFAICS one can create sensible language which supports
higher order functions but has no closures or nested functions.
For languages that support a limited subset of higher order functions,
that would be true - by the definitions that you have been using, C
would be such a language.-a But by the time you have a language that
can return new functions and assign them to variables, then you have
come most of the way towards closures and nested functions - there is
no real difference between a nested function and a lambda assigned to
a local variable.-a So I don't see much point in a language that
supports general higher order functions and does not support closures
and nested functions.-a But perhaps you know of some examples?
There are advantages to having local, nested functions when the
alternative is:
* Defining them as separate, ordinary functions at module level...
* Where their names are now part of the global name space which
-a can clash and can't be reused, or could accidentally be exported
-a from the module when you forget 'static'
* Where those functions could be called from places where they are
-a not intended to be called
* Where they may not be localised to what would be their enclosing
-a function, so could be located anywhere in the module, or even
-a elsewhere
* Losing the close relationship between them
None of that needs closures.
Further, the above applies to C; local
functions can implemented that can refer to useful entities defined in
the enclosing function such as static variables, types, structs and enums.
Closures are only needed for access to stack-frame entities of the
enclosing functions, including parameters.
Of course, you will need closures to run all the silly examples of
higher order functions that abound on the internet.
I've yet to see examples that have real uses and that don't make code
harder to grasp.
I don't however, use local functions; I've never gotten into the habit.
I couldn't even tell you if my languages support them and how well.
When
I tried to run the program below right now, it had trouble resolving
names 'a b vector' from the nested function. I had to put in qualifiers
to make it work.
--------------------------
proc main=
-a-a-a const a = 100
-a-a-a static int b
-a-a-a type vector = [4]int
-a-a-a proc fred(int c) =
-a-a-a-a-a-a-a println "Fred", main.a, main.b, c, main.vector.typestr
-a-a-a end
-a-a-a b := 200
-a-a-a fred(300)
end
Output is:
Fred 100 200 300 [4]i64
David Brown <david.brown@hesbynett.no> wrote:
On 08/10/2025 22:21, Waldek Hebisch wrote:
David Brown <david.brown@hesbynett.no> wrote:
For example, C does not support higher order functions - and will still >>>> not do so even if the proposal for introducing lambdas to the language >>>> is accepted. Pascal has nested functions, but not higher order functions. >>>>
C++, on the other hand, has lambdas and higher-order functions, but not >>>> nested functions.
You probably can give some argument that C++ supports higher-order
functions, but it is quite unclear to me how you can do this without
acceptiong that C or Pascal support higher-order functions.
template <typename F>
auto do_twice(F f) {
return [f](auto x) {
return f(f(x));
};
}
int add_one(int x) { return x + 1; }
int test_add_two(int x) {
auto add_two = do_twice(add_one);
return add_two(x);
}
You can't write something equivalent to the higher order function
"do_twice" in C or Pascal, taking a function and returning a new one.
But 'do_twice' returns a lambda, not a function. You can not pass
result of 'do_twice' to a function expecting normal C++ function
(pointer) as an argument. If what you return does not need to be
a function, than you can do equvalent in C: return approproate
structure and provide "call" operation. Only advantage of C++
here is that in C++ one can overload call, so syntatically this
looks like a function call. But semantially it is not a function
call (because called thing is not a function).
Of course C++'s support and syntax here is somewhat suboptimal, compared
to functional programming languages or other modern languages that have
had such features from the start.
To support higher order functions, a language has to be able to handle >>>> functions as first-class objects - it has to be able to take them as
function parameters, and return them as functions. It is not sufficient >>>> to take and return function pointers, like C - it has to be something
that is treated in the language as a function.
I strongly disagree with this statement about pointers. Using pointers
to represent functions is usual in C and pretty consistent with making
explicit things that other languages do behind the scenes. Rather,
main limitation in C is that one can only use existing functions, there
is no way to make a new one.
Yes, that is the main limitation of C (in this context) - and the
limitation is because C only supports functions through function
pointers. Technically, C only supports declaring and defining
functions, and taking their address - even when you "call" a function in
C, you are applying the call operator to a function pointer. In order
to support more advanced higher order functions - such as returning new
functions - a language must be able to support larger underlying data
structures while maintaining the syntax of function calls. It has to
handle captures, closures and/or other details. A "function" is not
merely a pointer to an address in code memory.
When using trampolines closure is a pointer to an address of
freshly generated code. Note that on some OS-es (IIUC AIX and
Alpha/IA64 VMS) C function is a pointer to descriptor. Descriptor
contains both pointer to code and pointer to environment. IIUC VMS
on x86_64 decided to use trampolines. IIUC on all architectures
you can pass closures to VMS C and VMS C will call them correctly.
So fact that typical C implementation uses address of code is
just implementation datail.
BTW: people doing functional programming sometimes claim that to
support it one needs garbage collection. I arrived at the idea
above thinking which parts of functional programming can work
well without garbage collection.
Garbage collection is not, AFAIK, necessary for common functional
programming techniques. But it might make some of them more convenient
or efficient to use.
Some functional techniques work well without garbage collection.
But some basically loose all advantates without garbage collection
or equivalent automatic technique. In particular, passing
complex dynamically allocated data structures trough chain of
calls becomes problematic due to need for deallocation.
On 10/10/2025 21:04, bart wrote:
There was a discussion elsewhere about gcc's handling of nested
functions sometimes needing an executable stack, or other complications.
-aThis occurs when you have a nested function that has a closure - thus needing a hidden struct pointer - but where it needs to be passed
somewhere as though it were a pointer to a function without this extra parameter.-a One way to handle this is with a small function created directly on the stack.
Let me try to describe a possible real-world use, using an imaginary language to avoid any bias or complicating things with real-world syntax details.
You have a marvellous string sorting function, taking an array of
strings and a comparison function as a parameter :
-a-a-a-asort_strings(string s[], function comp(string, string))
You can use this with comparison functions that are case-sensitive, reverse-order case-insensitive, or whatever you like.
-a-a-a-adefine make_sort_func(string country) :
-a-a-a-a-a-a-a sort_func = lambda(string s1, string s2) :
-a-a-a-a-a-a-a-a-a-a-a return compare_names(s1, s2, country)
-a-a-a-a-a-a-a return sort_func
Now you can do your sorting with :
-a-a-a-adefine sort_names(string s[], string country) :
-a-a-a-a-a-a-a sort_strings(s[], make_sort_func(country))
Somehow, you have got to create a function that compares two strings,
with that function depending on an independent input (the country). That
is a closure - you've made a new function that is based on the three- parameter compare_names function and a bit of captured data. Whether you want to do that with a lambda (an anonymous function), a named lambda, a function that returns a new function, or a local function, is just a
matter of style and taste (assuming the language supports the feature).
Of course there are always ways to avoid this - such as making your
original "sort_strings" function take a "void *" parameter that is
passed to the comparison function as an environment, or making an array
of sort functions indexed by country.-a But I hope that example shows
that closures and higher-order functions can be useful in simple
examples that are not purely theoretical, and that it is clear what the
code above is doing.
I couldn't even tell you if my languages support them and how well.
That is a rather interesting thing to say!-a People often don't know all
the details of languages they use, but /usually/ the authors of those languages have a fair idea of what they can do :-)-a (Yes, I know that
some of your languages were made a long time ago.)
On 13/10/2025 19:58, David Brown wrote:
On 10/10/2025 21:04, bart wrote:
There was a discussion elsewhere about gcc's handling of nested
functions sometimes needing an executable stack, or other
complications. -a-aThis occurs when you have a nested function that has
a closure - thus needing a hidden struct pointer - but where it needs
to be passed somewhere as though it were a pointer to a function
without this extra parameter.-a One way to handle this is with a small
function created directly on the stack.
Thanks for clearing that up. I think I would have simply disallowed such uses.
Why should a special function with a hidden extra parameter be
able to masquerade as an ordinary function? The same point comes up
later on.
Let the user make their own arrangements for this.
(This is actually a similar problem to what happens when an interpreted language has to pass a function reference via an FFI to be used as a callback function.
Obviously, native code functions can't directly call a function pointer
that refers to some byte code rather than machine instructions. There
needs to be something inbetween.
I've never fully solved that for the general case. But I had in mind a
fixed number of predefined, intermediate, native code functions that sit between the external library, and a special reentry point to the interpreter.
I /think/ it can be done with data rather than new executable code.)
Let me try to describe a possible real-world use, using an imaginary
language to avoid any bias or complicating things with real-world
syntax details.
You have a marvellous string sorting function, taking an array of
strings and a comparison function as a parameter :
-a-a-a-a-asort_strings(string s[], function comp(string, string))
You can use this with comparison functions that are case-sensitive,
reverse-order case-insensitive, or whatever you like.
-a-a-a-a-adefine make_sort_func(string country) :
-a-a-a-a-a-a-a-a sort_func = lambda(string s1, string s2) :
-a-a-a-a-a-a-a-a-a-a-a-a return compare_names(s1, s2, country)
-a-a-a-a-a-a-a-a return sort_func
Now you can do your sorting with :
-a-a-a-a-adefine sort_names(string s[], string country) :
-a-a-a-a-a-a-a-a sort_strings(s[], make_sort_func(country))
Somehow, you have got to create a function that compares two strings,
with that function depending on an independent input (the country).
That is a closure - you've made a new function that is based on the
three- parameter compare_names function and a bit of captured data.
Whether you want to do that with a lambda (an anonymous function), a
named lambda, a function that returns a new function, or a local
function, is just a matter of style and taste (assuming the language
supports the feature).
Of course there are always ways to avoid this - such as making your
original "sort_strings" function take a "void *" parameter that is
passed to the comparison function as an environment, or making an
array of sort functions indexed by country.-a But I hope that example
shows that closures and higher-order functions can be useful in simple
examples that are not purely theoretical, and that it is clear what
the code above is doing.
You've used static typing for your examples: the sort function clearly
takes two arguments, not optionally an extra argument!
So, is this an example where a 'trampoline' is needed, to pretend your
true sort function is a simple 2-operand one? I don't think this is
going to be help performance any, not on Windows (I understand Linux
does something special here).
But as you say, this example isn't that compelling, as there are any
number of workarounds.
I couldn't even tell you if my languages support them and how well.
That is a rather interesting thing to say!-a People often don't know
all the details of languages they use, but /usually/ the authors of
those languages have a fair idea of what they can do :-)-a (Yes, I know
that some of your languages were made a long time ago.)
This can happen with little-used features, or ones you've forgotten
about or never used. You tend to find them when overhauling code.
However I think I first had nested functions by accident, by neglecting
to disallow them.