• Algol 68 / Genie - new release (was Re: Algol 68 - formatting with fixed point format)

    From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Tue Sep 2 00:56:02 2025
    From Newsgroup: comp.lang.misc

    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. A previous download - where I think he fixed
    at least a "diagnosis bug" I reported (or maybe more) - wasn't reason
    enough for me to get it. But together with the newer reported "%.f"
    bug and some issues in the documentation... - not sure.

    There's issues I'd be more concerned about, but when I saw what would
    be necessary to "fix"/change that I'm not too optimistic. (I already
    mentioned that I didn't get responses concerning some of the feedback
    I gave.)


    BTW, I generally *avoid* all those _Genie specific_ features that emit
    annoying warning messages. - My opinion on that is that if I want to
    get hints for use of non-standard features I'd add a warning-switch
    that I could activate for such checks but I don't want to get bothered
    by such messages with every run of a program. And selective disabling
    is also not possible. - I was reluctant to suggest that to Marcel. -
    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.)

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Tue Sep 2 01:15:34 2025
    From Newsgroup: comp.lang.misc

    On 02.09.2025 00:56, Janis Papanagnou wrote:
    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. [...]

    Did you already compile it? - I got this diagnostic message...

    ~/algol68g-3.8.5/./src/a68g/a68g-bits.c:253:
    undefined reference to `clock_gettime'

    (I've already sent an email to Marcel.)

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Tue Sep 2 22:17:42 2025
    From Newsgroup: comp.lang.misc

    On 02.09.2025 18:02, Andy Walker wrote:

    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 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 implicitly
    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 to have access to it, say, like this

    gets (argv (argind), var);

    (or any constant string or one defined with an identity relation).

    But I have to provide an "unnecessary" (against the spirit of A68)
    variable, like

    STRING optarg := argv (argind);
    gets (optarg, var);

    or (as Marcel suggested) a local (unnamed, implicit) variable like

    gets (LOC STRING := argv (argind), var)


    -- 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!

    It seems that Marcel uses a function that is "not yet" part of the
    standard "C"-library [on my system], and make's 'configure' doesn't
    get that; "man clock_gettime" says on my system:

    Link with -lrt.

    Feature Test Macro Requirements for glibc [...]

    The tragicomic element of all that is that the feature's presence
    is a consequence of a former bug report; the previously existing
    three time functions returned all the same response,[*], and they
    were also documented wrongly to provide wall-clock time. For the
    wall-clock time there was actually no function existing and Marcel
    decided to provide one.

    Janis

    [*] And using 'clock' with a call of 'system ("sleep 200")' had
    returned just a runtime of "0" [seconds]. - I would have hoped
    for a built-in 'sleep' function but needed to resort to 'system'.
    And then it turned out that 'clock' doesn't provide the desired
    time in such contexts.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Tue Sep 2 23:04:59 2025
    From Newsgroup: comp.lang.misc

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Wed Sep 3 01:46:43 2025
    From Newsgroup: comp.lang.misc

    On 03.09.2025 01:04, Lawrence DrCOOliveiro wrote:
    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.

    Sure.

    And if your Algol 68 application program writes its diagnostics
    also to stderr you cannot separate the Genie warnings from your
    application's warnings. - That's why the programmer should have
    control about generation of the Genie interpreter's warnings!

    IIUC, Andy wrote his own wrapper to get rid of (or write a hack
    around) the issue. - That's a way we - individually! - can go.

    And since I anyway have a ~/bin/genie script[*] that I'm using
    I might implement there also my own filters for something that,
    IMHO, should be inherent part of the Genie interpreter system
    interface.

    Janis

    [*] Just because I consider "genie" easier to type than "a68g".

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Sep 2 17:02:28 2025
    From Newsgroup: comp.lang.misc

    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.

    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.)

    He has always corrected any actual bugs that I've reported, inc
    some better described as infelicities. Beyond that, he implemented
    partial parametrisation and Torrix. As far as I recall, modals are the
    only major thing that I asked for and didn't get.

    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!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Boccherini
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Thu Sep 4 00:30:24 2025
    From Newsgroup: comp.lang.misc

    On 03/09/2025 00:04, Lawrence DrCOOliveiro wrote:
    I got caught out when Marcel added warnings about declaration hiding
    other declarations [...].
    Surely warnings and other diagnostics go to stderr, whereas you would
    collect output from stdout.

    They do now; warnings [possibly as opposed to errors -- I haven't checked] used to go to stdout.
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Nevin
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Thu Sep 4 17:01:35 2025
    From Newsgroup: comp.lang.misc

    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 implicitly
    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'.
    Other possible reasons are (a) you may want to read and write
    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]; (b)
    A68 defines what happens if you try to read off the end of the input,
    which gives you a chance to take action, perhaps involving changing
    the content of the string. I can understand Marcel wanting to give
    users a simple way to re-use C code without having to worry about
    possible nuances of this kind.

    My expectation is [...].

    Always dangerous in the dusty corners of A68 and A68G!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Nevin
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Fri Sep 5 04:32:57 2025
    From Newsgroup: comp.lang.misc

    On 04.09.2025 18:01, Andy Walker wrote:
    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.

    Yes, sure. That's why I haven't stressed the topic any more when
    I noticed that, and sent him a followup to my mail acknowledging
    this (presumed) motivation. (Before that I asked for a rationale
    to understand the decision, but he couldn't remember his thoughts
    back 20+ years.)


    The problem is that this implicitly
    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'.
    Other possible reasons are (a) you may want to read and write
    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];

    This is *not* a stringent reason in my opinion. If you have such
    a demand to do that just use that variable! A variable in a value
    context will be dereferenced anyway!

    The problem is appearing only the other way round; that a value
    cannot be used in reference contexts. You break the symmetry that
    you have in other contexts - in "read"-type procedures you have
    references, in "white"-type procedures you have values specified;
    and that makes sense. The gets/puts extensions are the exception.

    Janis

    [...]
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Wed Sep 10 17:15:18 2025
    From Newsgroup: comp.lang.misc

    On 02.09.2025 18:02, Andy Walker wrote:
    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. If I've observed correctly
    some "warnings" have got "notices", and both will not show up per
    default (unless --notice and --warning, respectively, is set). The
    two previously appearing "declaration hides..." and "extension..."
    diagnostics are now not shown any more. Good news. Thanks Marcel!

    Janis

    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). :-/

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Wed Sep 10 23:57:59 2025
    From Newsgroup: comp.lang.misc

    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!

    Yay!

    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. Grr! I'd like some formulation that works with both
    current and old versions of the compiler, but no luck thus far. 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; 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!]. Can't grumble at that.
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Coleridge-Taylor --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Thu Sep 11 07:27:50 2025
    From Newsgroup: comp.lang.misc

    On 11.09.2025 00:57, Andy Walker wrote:

    I can't keep up with the new versions! There seems to be a new
    one every day currently.

    Yes, sadly. - I suffer from the same contemporary habits of OSes.

    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.

    That's one of the first things I also noticed.

    What (sort of) "works" for me is formulating --exit instead of -- or - .

    But then I'd have to reformulate quite some of my Algol 68 programs,
    because I'm parsing that string (and I also differentiate -- from - ).

    The documentation still mentions '--' so I guess that this is a bug;
    either a feature removed by accident or the docs being left unadjusted.

    I didn't want to report it currently because I'm more concerned with
    the memory access issue, and Marcel is looking into that at the moment.

    Janis

    [...]

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Thu Sep 11 09:42:31 2025
    From Newsgroup: comp.lang.misc

    On 11.09.2025 07:27, Janis Papanagnou wrote:
    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).

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Thu Sep 11 17:16:15 2025
    From Newsgroup: comp.lang.misc

    On 11/09/2025 08:42, Janis Papanagnou wrote:
    Marcel just sent me a tar-file with the memory access violation
    issue and the '--' problem (any maybe more things) fixed! :-)

    Ah. That's good! Thanks for the info.

    I suppose it will be published soon as release 3.9.4 (or as some
    successor of that).

    3.9.4 is already out. I think I'll wait for the current daily
    releases to settle a little.
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Bucalossi
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Sep 27 00:41:48 2025
    From Newsgroup: comp.lang.misc

    On 10/09/2025 23:57, Andy Walker wrote:
    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.


    I'm pretty good at grumbling!

    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 find it, either on Windows or Linux.)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Sat Sep 27 14:38:09 2025
    From Newsgroup: comp.lang.misc

    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 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.
    I'm pretty good at grumbling!

    Yes, 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.

    Actually 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.

    The speed-up noted was between 3.5.4 and 3.8.7. 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. I don't [yet] have 3.9.9 -- as noted to Janis,
    I can't keep up with almost daily new versions! 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.)

    ... I get ~20M calls/sec from 3.8.7. 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.

    Or maybe it's a silly test; 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?

    More interesting would be some figures for the Whetstone benchmark or similar? That is at least /intended/ to reflect real usage, and we have figures
    going back over 50 years. [There is a version in the source directory of A68G downloads.]

    (There is supposedly a fast semi-compiled mode, but I've never managed to find
    it, either on Windows or Linux.)
    Have you tried "a68g -O somefile.a68g"? Or "-O3", etc. The A68Gdocumentation gives full details. Works for me on Linux.
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Goodban
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Mon Sep 29 01:15:30 2025
    From Newsgroup: comp.lang.misc

    On 27/09/2025 14:38, Andy Walker wrote:
    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 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.
    I'm pretty good at grumbling!

    -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?

    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.

    (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-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.

    (There is supposedly a fast semi-compiled mode, but I've never managed
    to find
    it, either on Windows or Linux.)
    -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.


    I tried it now and it at least acknowledges that option on Windows: 'optimisation has no effect on this platform'.

    Under WSL, it makes a useful speedup to Fibonacci (from 4.4M
    calls/second to nearly 7M).

    With the Fannkuch benchmark (N=10), it makes a significant difference:
    from 48s runtime down to 13 seconds. On Windows, it took 50 seconds, or
    80 seconds with 3.9.9. This benchmark makes no calls!

    On WHET, -O/-O3 caused a transput error (this is 2.8.4; I haven't tried
    3.9.9 on WSL.)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Mon Sep 29 16:05:13 2025
    From Newsgroup: comp.lang.misc

    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.

    Yes, but not [IMO] a particularly useful one. 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. See also below.

    (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.

    Recursion is the sort of thing optimisers can optimise away.
    It saves setting up new stack frames, may well improve cache usage,
    and may even allow for a certain amount of memoising.

    What would normally take 25 machine instructions, get expanded to
    250, which is totally over the top.

    Loop/recursion unrolling can do that sort of thing. 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.

    Imagine if a whole executable
    got 10 times bigger using -O2.)

    Optimisation is usually for speed rather than space!

    -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.

    Almost certainly "that one"; there are several versions around, but AFAIK they all have the same basic code. For me the interest is as above -- designed to reflect real usage [at least, as of the 1970s], and has a 50-year historical timeline. For me, at least, it's interesting that I can take programs that I wrote decades ago for my PhD thesis, and that occupied Atlas [by some measures, the fastest computer in the world in the '60s] for several hours each weekend, and run them in less than a minute on my desktop. This
    is, of course, a niche interest! But see also below.

    Many speed issues in interpreted languages are to do with integer calculations.

    No-one [sensible] uses an interpreted language for its speed. This brings us back to the car analogy above. You don't buy your family saloon because it has a top speed of 250mph [unless you're mega-rich and have a different show-off car for each day of the week]. Normal people are more interested in things like comfort, safety, gadgetry, reliability, economy, style, and so on. In the case of computing languages, what /should/ matter
    is how hard it is to develop and test programs -- an hour saved in writing
    the code pays for thousands of extra seconds of runtime.

    [...]> On WHET, -O/-O3 caused a transput error (this is 2.8.4; I haven't tried
    3.9.9 on WSL.)

    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.]
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Blumenthal
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Mon Sep 29 18:57:06 2025
    From Newsgroup: comp.lang.misc

    On 29.09.2025 17:05, Andy Walker wrote:
    On 29/09/2025 01:15, bart wrote:
    [Fibonacci benchmark]

    Recursion is the sort of thing optimisers can optimise away.

    I'm positive for linear recursion, but is that meanwhile true also
    for cascaded recursion (as in calculating [in the simplistic way]
    Fibonacci)?

    [...]

    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.]

    In former times I hadn't used formatted output in Algol 68. - Now
    that I now it much better the first thing I did was abandoning all
    those z/d formats that just drove me mad to get things robust with
    them. - It seems you can typically just replace them by the g(...)
    types of format specifiers.

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Mon Sep 29 19:08:43 2025
    From Newsgroup: comp.lang.misc

    On 29/09/2025 16:05, Andy Walker wrote:
    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.

    The speed of a car is still important. One that can only move at walking
    pace is not much good!

    But when you do compare cars A and B, they HAVE to do exactly the same
    task, otherwise the comparison is useless and misleading.

    -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.

    You're right, if the program was just this 7-line function plus one 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.

    -a-a-a-aNo-one [sensible] uses an interpreted language for its speed.-a This brings us back to the car analogy above.

    Everybody loves interpreted languages because are usually simpler and
    tend to have a fast development cycle.

    But everybody also hates that they are so slow. That's why so many
    resources have been expended over decades on trying to make them faster.

    And yet, ones like Lua and Python still resort to using external C
    libraries for real work.

    I understand that over 40-50 years, hardware speeds have increased by
    3-4 magnitudes.

    While the difference between compiled and interpreted may be only 1-2 magnitudes (so interpreted will still be faster than a 1970s mainframe).

    However people also want programs as fast as possible because it means
    fewer (and cheaper) hardware resources, and these days also less power.

    If you compare a simple compiler (like one of mine) and a complex one
    (like LLVM-based Clang), the difference in size may be several
    magnitudes, compile-time 1-2 magnitudes, but the speed of the generated
    may differ by only 2:1 (ie. 0.3 magnitudes I think).

    So quite insignificant IMV compared to the changes you and I have seen.
    Yet most people think it is of vital importance, even to squeeze the
    last 1% of speed out.

    -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.]


    OK, I did that, and I think I got an 8.5x speed-up (15s vs. 1.75s
    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!

    (My interpreted language does that test in 2.6s, with no delay.

    I'm not quite as vocal about slow-performing interpreters of static
    languages, as I was, not since I tried it myself!

    I've tried two such projects, and haven't been able to to get them
    faster than my dynamic interpreter.)


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Sep 30 15:58:59 2025
    From Newsgroup: comp.lang.misc

    On 29/09/2025 17:57, Janis Papanagnou wrote:
    [Fibonacci benchmark]
    Recursion is the sort of thing optimisers can optimise away.
    I'm positive for linear recursion, but is that meanwhile true also
    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.]
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Kirnberger
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Tue Sep 30 17:48:41 2025
    From Newsgroup: comp.lang.misc

    On 30.09.2025 16:58, Andy Walker wrote:
    On 29/09/2025 17:57, Janis Papanagnou wrote:
    [Fibonacci benchmark]
    Recursion is the sort of thing optimisers can optimise away.
    I'm positive for linear recursion, but is that meanwhile true also
    for cascaded recursion (as in calculating [in the simplistic way]
    Fibonacci)?

    I'm not entirely clear what distinction you're drawing between
    different recursions,

    As said, cascaded vs. linear. Semi-formally explained as (for a
    simplified example): f(g(f(x)),h(f(x))) vs. f(g(f(x)))
    functions like fib(), ack(), and similar (but you know that).

    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.

    I've had done such a formal transformation myself with fib() in
    an awk obfuscation post before. But I didn't know such sorts of
    transformations are done by optimizers. - In recent times (past
    decades) I've not any more followed the optimizers' progresses.

    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".

    Can't tell. - Is there anyone around here who knows that?

    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".

    Maybe. Can't tell.

    I don't think we
    need to be as paranoid as Bart and suspect that Clang is rigged to spot
    this benchmark!

    (Well, Bart. He's focused on his specific themes (his own tools,
    number of CPU commands, execution speed, and such) and he often
    drags discussions to that direction. He regularly misses other
    [more important] project requirements, or just ignores them.
    I'm fine with that unless it gets too penetrant at some point.)


    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'm sure you are about 5+ years older than I am. :-)
    I meant the somewhat *later* "former times". ;-)

    But, frankly, I cannot tell whether the Algol 68 we had at our
    university supported formatted transput; I just haven't tried
    back then. (But many of our professors had been engaged in the
    Algol 68 definition, so I'd suppose they had provided something
    elaborated on the TR440 we used.)

    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.

    Yes, I could feel from your posts your dislike and suspected it
    came from such practical experiences and suffering.

    (But Genie supports it, and I see no reasons to not use 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 already did, but from own experience I can say chances are not
    too high that I'll get an answer.

    I don't think it can be deduced from his book or from the RR, BICBW.]

    This was also my impression. - That's why I already reduced my
    requirement from a random "stand back" to an "append" function
    in write-only mode; then I could just do it in two passes. Or
    I do it with two files and renaming. Or use "system" to let the
    shell perform those tasks. Etc. etc. - but all kludges. :-/
    I'm a bit frustrated to have a language with powerful features
    and need to resort to such workarounds.

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Sep 30 23:23:35 2025
    From Newsgroup: comp.lang.misc

    On 29/09/2025 19:08, bart wrote:
    Loop/recursion unrolling can do that sort of thing. 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.
    You're right, if the program was just this 7-line function plus one
    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. I'm all for keeping things simple! There's a lot to be
    said for the original "software tools" approach of Unix; pity it
    doesn't entirely scale to major projects. [OTOH, it's surprising
    what you can do with shell scripts, edit scripts and small programs
    linked by pipes.]
    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.

    I two-thirds agree. But the way storage and cache are going,
    the slow-down soon won't be noticeable [if it is even now].

    [Unoptimised A68G vs "-O3":]> [...] I think I got an 8.5x speed-up (15s vs. 1.75s
    measuring elapsed time for 100 loops).

    Perhaps worth noting that the Whetstone test in A68G is in
    three parts; using standard reals, using long reals [for the same
    code otherwise] and using long long reals [ditto]. These three parts
    optimise differently -- on my machine there is a 9x speed-up for
    reals, 15% for long reals and 2% for long long reals [as measured
    by the "MWhets" figures]. If you have an old copy of the benchmark
    you may have only the single-length real results, and of course if
    you have different word lengths the results are incomparable anyway.
    [If you have recent source, then the Whetstone source is in ".../src/test-set/34-whetstones.a68" where "..." is where you put
    the installation, and "34" may vary with version.]
    However the behaviour with -O3 was odd. It seems -O3 causes it to
    spend 3 seconds optimising first before starting the program proper.

    The 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!

    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].
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Kirnberger
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Wed Oct 1 23:00:41 2025
    From Newsgroup: comp.lang.misc

    On 30/09/2025 23:23, Andy Walker wrote:
    On 29/09/2025 19:08, bart wrote:
    Loop/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.
    You're right, if the program was just this 7-line function plus one
    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

    That's not particularly large. The sourcecode for gcc has 80,000 *files*
    iirc.

    You might agree however that typical programs that do important things
    that we all use, are likely to be of substantial size.

    A typical LLVM-based compiler is now over 100MB. That's a million times
    bigger than a Fibonacci test that might be 100 bytes.

    The latest A68G.EXE (4MB) is 40,000 times bigger!

    Even my own projects, which range from 100KB-400KB, would be 1000-4000
    times bigger.

    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, 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!


    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].

    That's how it normally works with a compiled language. But I don't know
    if A68G has some discrete AOT compilation step that produces a binary
    that you can then invoke multiple times.

    So


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Thu Oct 2 16:55:29 2025
    From Newsgroup: comp.lang.misc

    On 01/10/2025 23:00, bart wrote:
    [I wrote:]
    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
    That's not particularly large. The sourcecode for gcc has 80,000
    *files* iirc.

    I don't know whether YRC. But (a) "gcc" is no longer "Gnu
    C Compiler", but "Gnu Compiler Collection", covering more than a
    dozen languages [inc, incidentally, A68 as of this year, but that
    seems to be a port of A68G -- I haven't checked or used it], several
    dozen machine architectures and many optimisation levels/options.
    It's not really one application, but hundreds, arguably thousands,
    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.
    The same applies, of course to Linux as a whole, and even more so to
    Windows.

    You might agree however that typical programs that do important
    things that we all use, are likely to be of substantial size.

    "Likely to be" is not the same as "needs to be"!

    [...]> The latest A68G.EXE (4MB) is 40,000 times bigger!
    My copy of "a68g" is "only" 1.48MB, having grown from ~500KB when
    I first used it. But I haven't bothered to install SQL or R.

    [...]
    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.

    If 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. That is part of your answer. See also below.

    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!

    In the 1970s, optimisation was very limited. If they had tried
    to do the optimisations that are routine today, programs might well have
    taken hours to compile, esp with multi-pass stuff.

    So 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].
    That's how it normally works with a compiled language. But I don't
    know if A68G has some discrete AOT compilation step that produces a
    binary that you can then invoke multiple times.
    If all else fails, read the documentation! You would be looking
    for the "rerun" and "compile" options of A68G.
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Byrd
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Thu Oct 2 21:51:46 2025
    From Newsgroup: comp.lang.misc

    On Thu, 2 Oct 2025 16:55:29 +0100, Andy Walker wrote:

    and I suspect many of the files are copies of each other.

    Not Likely.

    (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.

    Did any of those fixes deal with bugs you had encountered?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Fri Oct 3 22:24:55 2025
    From Newsgroup: comp.lang.misc

    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 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.
    You're right, if the program was just this 7-line function plus one
    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.

    One project that I am working on has more than 8000 exported functions
    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 functionality present is reasonably natural from users point of
    view, making it more natural requires more complexity in the
    program. And not having a program would mean not doing the
    work at all (bad) or doing it by hand and almost surely getting
    a lot of errors compared to using program output.

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sat Oct 4 03:39:02 2025
    From Newsgroup: comp.lang.misc

    On Fri, 3 Oct 2025 22:24:55 -0000 (UTC), Waldek Hebisch wrote:

    And to have some perspective, let me mention discussion of Open Office startup.

    Please, nobody should be using Open Office any more. <https://www.libreoffice.org/discover/libreoffice-vs-openoffice/>
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 4 12:19:28 2025
    From Newsgroup: comp.lang.misc

    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 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.
    You're right, if the program was just this 7-line function plus one
    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.)




    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 4 12:38:51 2025
    From Newsgroup: comp.lang.misc

    On 02/10/2025 16:55, Andy Walker wrote:
    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.


    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?

    I tried lots of combinations of options, and either got a binary .sh
    file, or an annotated .l file of the a68 source.

    So do you happen to know how I can reliably obtain a persistent .c
    output file?

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Sat Oct 4 14:04:12 2025
    From Newsgroup: comp.lang.misc

    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?

    I suggest to just try the Genie web-pages for the documentation...

    https://jmvdveer.home.xs4all.nl/en.download.algol-68-genie-current.html

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Sat Oct 4 14:16:47 2025
    From Newsgroup: comp.lang.misc

    On 04.10.2025 13:19, bart wrote:
    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 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.
    You're right, if the program was just this 7-line function plus one
    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.

    (I rarely use office software at home.)

    On my "legacy" (sort of) Linux on my 15+ years-old machine the Libre
    Office Writer starts in less than a second, and there are no delays
    when typing. - And this is while my system is constantly under load.


    This was after turning off every option I could find.

    Could it be that your Win OS is the source of the problem? - Frankly,
    I wouldn't be surprised if they had code in their OS to slow down
    non-MS products.

    Good luck.

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Sat Oct 4 13:47:07 2025
    From Newsgroup: comp.lang.misc

    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!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Rimsky-Korsakov --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Sat Oct 4 15:03:27 2025
    From Newsgroup: comp.lang.misc

    On 04.10.2025 14:47, Andy Walker wrote:
    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"?

    Indeed. (Myself I always use the PDF for more information and details.)

    I had installed a former version from a tar-file and the man page got
    installed as well. But it doesn't install (say in /usr/share/doc) the
    more comprehensive PDF file below, nor is it part of the tar-file. It
    needs a separate download. I think it wouldn't be bad to have it also
    bundled with the tar-file; to have a document that is consistent with
    the source (and in one download step).

    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!

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 4 15:07:23 2025
    From Newsgroup: comp.lang.misc

    On 04/10/2025 13:47, Andy Walker wrote:
    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 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?

    -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!



    I was hoping that you could, you know, just tell me! 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.

    'man a68g' shows some weird stuff. While these lines from the 700-page PDF:

    rCo --extensive

    Generates an extensive listing, including source listing, syntax tree,
    cross reference, generated C code et cetera. This listing can be quite
    bulky.

    promise generated C, but it is nowhere to be seen in the generated .l
    file. Or so I thought. I went over it much more carefully, and finally
    spotted the 94 lines shown below (5% of the .l file), corresponding to
    this fib.a68:

    PROC fib=(INT n)INT:
    IF n<3 THEN 1 ELSE fib(n-1)+fib(n-2) FI;

    FOR i FROM 1 TO 36 DO
    print((i," ",fib(i), newline))
    OD


    So as I expected, it is not direct C code for the task, but a series of function calls that presumably correspond to what is done within the interpreter.

    Now, Algol68, the language, supports some very hairy features behind the scenes, associated with higher-order functions and such. And to enable
    those, I believe that efficient implementations are harder.



    ---------------------------------------------------
    Object listing
    ------ -------
    /* "fib.c" algol68g 2.8.4 */

    #include <algol68g/a68g-config.h>
    #include <algol68g/a68g.h>

    #define _CODE_(n) PROP_T n (NODE_T * p) {\
    PROP_T self;

    #define _EDOC_(n, q) UNIT (&self) = n;\
    SOURCE (&self) = q;\
    (void) p;\
    return (self);}

    #define DIV_INT(i, j) ((double) (i) / (double) (j))
    #define _N_(n) (node_register[n])
    #define _S_(z) (STATUS (z))
    #define _V_(z) (VALUE (z))

    /* fib.a68: 2: n < 3 */
    _CODE_ (_formula_74)
    A68_INT * n_77;
    GET_FRAME (n_77, A68_INT, 5, 0);
    PUSH_PRIMITIVE (p, (_V_ (n_77) < 3), A68_BOOL);
    _EDOC_ (_formula_74, _N_ (74))

    /* fib.a68: 2: 1 */
    _CODE_ (_denotation_90)
    PUSH_PRIMITIVE (p, 1, A68_INT);
    _EDOC_ (_denotation_90, _N_ (90))

    /* fib.a68: 2: n - 1 */
    _CODE_ (_formula_108)
    A68_INT * n_111;
    GET_FRAME (n_111, A68_INT, 5, 0);
    PUSH_PRIMITIVE (p, (_V_ (n_111) - 1), A68_INT);
    _EDOC_ (_formula_108, _N_ (108))

    /* fib.a68: 2: n - 2 */
    _CODE_ (_formula_129)
    A68_INT * n_132;
    GET_FRAME (n_132, A68_INT, 5, 0);
    PUSH_PRIMITIVE (p, (_V_ (n_132) - 2), A68_INT);
    _EDOC_ (_formula_129, _N_ (129))

    /* fib.a68: 4: 1 */
    _CODE_ (_denotation_156)
    PUSH_PRIMITIVE (p, 1, A68_INT);
    _EDOC_ (_denotation_156, _N_ (156))

    /* fib.a68: 4: 36 */
    _CODE_ (_denotation_164)
    PUSH_PRIMITIVE (p, 36, A68_INT);
    _EDOC_ (_denotation_164, _N_ (164))

    /* fib.a68: 5: i */
    _CODE_ (_unite_194)
    A68_INT * i_195;
    ADDR_T _pop_0_194;
    _pop_0_194 = stack_pointer;
    PUSH_UNION (_N_ (194), MODE (INT));
    GET_FRAME (i_195, A68_INT, 5, 0);
    PUSH_PRIMITIVE (p, _V_ (i_195), A68_INT);
    stack_pointer = _pop_0_194 + 56;
    _EDOC_ (_unite_194, _N_ (194))

    /* fib.a68: 5: " " */
    _CODE_ (_unite_201)
    ADDR_T _pop_0_201;
    _pop_0_201 = stack_pointer;
    PUSH_UNION (_N_ (201), MODE (CHAR));
    PUSH_PRIMITIVE (p, ' ', A68_CHAR);
    stack_pointer = _pop_0_201 + 56;
    _EDOC_ (_unite_201, _N_ (201))

    /* fib.a68: 5: i */
    _CODE_ (_identifier_220)
    A68_INT * i_220;
    GET_FRAME (i_220, A68_INT, 5, 0);
    PUSH_PRIMITIVE (p, _V_ (i_220), A68_INT);
    _EDOC_ (_identifier_220, _N_ (220))

    /* prelude: 0: BEGIN PROC fib = (INT n) INT: IF n < 3 THEN 1 ELSE fib(n
    ... */
    _CODE_ (_closed_41)
    ADDR_T _pop_41;
    _pop_41 = stack_pointer;
    OPEN_STATIC_FRAME (_N_ (43));
    FRAME_CLEAR (80);
    global_pointer = frame_pointer;
    initialise_frame (_N_ (43));
    EXECUTE_UNIT_TRACE (_N_ (141)); /* FOR i FROM 1 TO 36 DO print(( ... */
    CLOSE_FRAME;
    _EDOC_ (_closed_41, _N_ (41))



    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Sat Oct 4 18:40:59 2025
    From Newsgroup: comp.lang.misc

    [I wrote:]
    If all else fails, read the documentation! You would be
    looking for the "rerun" and "compile" options of A68G.
    [...]>> Does "apt-get" not install the "man" page? [I always build direct
    from the tar-ball.] Try "man a68g"?
    [...]> I was hoping that you could, you know, just tell me!

    "Teach a man to fish ..." and all that jazz. TLDR version:
    Try

    a68g -compile fred.a68
    a68g -rerun fred.a68g

    [replace "fred" by a suitable file name, and note that "-O3" invokes
    the "-compile" option automatically].

    So I don't have> to wade through various tons of irrelevant material, ending up in
    rabbit holes, or performing fruitless experiments.

    I didn't tell you to wade down rabbit holes, I suggested
    "man a68g". Which [on my system] includes entries for both "rerun"
    and "compile". YMMV.

    The options shown by A68G --help are incomplete (there is no --
    compile for example), and generally it is not useful. [...]

    There 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]. I thought you were interested in "compile once, run
    many" to save all those seconds rather than wanting to wade through the generated C!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Rimsky-Korsakov --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 4 19:46:09 2025
    From Newsgroup: comp.lang.misc

    On 04/10/2025 18:40, Andy Walker wrote:
    [I wrote:]
    If all else fails, read the documentation!-a You would be
    looking for the "rerun" and "compile" options of A68G.
    [...]>> Does "apt-get" not install the "man" page?-a [I always build direct
    from the tar-ball.]-a Try "man a68g"?
    [...]> I was hoping that you could, you know, just tell me!

    -a-a-a-a"Teach a man to fish ..." and all that jazz.

    You have to show him how to catch the first fish!


    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.

    It does vary: the start of it looks like this:

    SYNOPSIS
    a68g [ | | [string]] [ |
    ]

    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.



    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 think the available options are messy. '--rerun' is used with A68G
    with a .a68 input, which somehow lets it use a cached version, but the
    details are not clear.

    --compile is used to create a persistent intermediate version, in the
    form of a binary .sh file, which is run directly without needing A68G
    (or not needing to type it).

    If you do use --listing etc, then it first /runs/ the program before it generates the output files, which is unexpected (and a little annoying
    if it takes some time to finish).

    So I think the options could be presented more cleanly and perhaps
    segregated. Here is example help text from one of my compilers when
    invoked as 'mm -help':

    ------------------------------------------------
    c:\mx>mm -help
    M Compiler for 64-bit Windows

    Normal use: Compiles lead module prog.m to:

    mm prog prog.exe (default)
    mm -r prog in-memory native code then execute
    mm -i prog in-memory IL then interpret

    mm -exe prog prog.exe
    mm -dll prog prog.dll
    mm -obj prog prog.obj
    mm -a prog prog.asm (aa/nasm/gas, depends on config)
    mm -mx prog prog.mx (private executable format)
    mm -p prog prog.pcl (textual IL)
    mm -ma prog prog.ma (single amalgamated source file)
    mm -c prog prog.c (single C source file)

    Other options:
    ....
    ------------------------------------------------

    (There are half a dozen more options that might be useful to someone
    other than the developer.

    I added that -c option just now. Normally a separate program is used to transpile to C; using that here will just invoke that other program.)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sat Oct 4 21:05:45 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sat Oct 4 22:00:03 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Sun Oct 5 00:31:00 2025
    From Newsgroup: comp.lang.misc

    On 05.10.2025 00: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 just a terminus to describe something...

    Allowing functions as parameters and return types of procedures and
    functions.

    (See also: first-class functions/procedures for a related terminus.)

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sun Oct 5 01:40:22 2025
    From Newsgroup: comp.lang.misc

    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. So you type 2-3 words and nothing happens, but stop typing and
    they all appear at once.

    The problem briefly went away after changing font, but I couldn't
    recreate that fix after restarting it.

    It's a bug, rather than being slow.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sun Oct 5 01:52:54 2025
    From Newsgroup: comp.lang.misc

    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:

    - Anonymous (lambda) functions

    - Nested functions

    - Accessing locals of enclosing functions (including when the local
    function is returned and is called when the enclosing function's stack
    frame no longer exists)

    - Closures

    - Function factories

    - Currying

    - Probably, continuations, co-routines and generators

    Most languages seem to have them actually, even ones you thought were
    simple, even Pascal.

    (In my thread about the A68G stack, see the rosetta-code link for the
    task, which needs some of this stuff to implement it natively.)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Sun Oct 5 01:29:36 2025
    From Newsgroup: comp.lang.misc

    bart <bc@freeuk.com> wrote:
    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 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.
    You're right, if the program was just this 7-line function plus one
    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.

    That is different problem. What I described was strictly startup
    problem, once done did not affect runtime speed. And it affected
    Open Office and only on Linux (startup on Windows was faster), when
    you use newer thing like LibreOffice it is supposed to be fixed
    (or at least worked around).

    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.)

    Yes, something was wrong. IIUC Open Office/LibreOffice is doing rather
    complex things after insertion of each character, but this is supposed
    to be reasonably fast.

    Keeping on top of such things in any user application seems to be
    nobody's job.

    Well, this is open source developement, many independent parties
    cooperate to make it happen. No single entity is responsible for
    the whole. So it may happen that something does not work and
    nobody reacts.

    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.

    You want super-fast compilation so this may be a problem for you,
    but most people do not expect large programs like Open Office
    to compile very fast. But the problem I describle happened
    on every startup, there was someting like extra 1.25s spent on each
    startup. 1.25s added to build time of a large program is not
    that much, but many users (including me) expect such program to
    start up much faster.

    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.

    The difference is that ELF normally requires searching all libraries
    at runtime. I am not sure if one can say that a function comes from
    a specific library, but the default is to search all libraries.

    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.

    For programs using less functions time spent in search is shorter.
    And, as I wrote above, developers do not expect to compile large
    programs very fast. So, if done at compile/link time it would be
    not a problem.

    (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.

    Yes, Open Office on Windows started up measurably faster than on Linux.

    Does ELF not do that? Or is it slow because there are 250,000 functions, even if it knows where to find them?

    As I wrote, normal (expected) behaviour with ELF is to search all
    libraries at runtime. So, time is essentially c*M*N when c is cost of
    single hashtable lookup, M is number of libraries and N is number of
    symbols. More precisely, one expects on average to search trough about
    half of libraries which lowers proportionality constant, but it
    is much more than c*N expected when each function is searched only
    in a single library.

    (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.)

    ELF specification is rather large with various optional parts. AFAIK
    program may request a non-standard dynamic linker and attach extra
    data to executable so technically one can change the behaviour.
    More precisly, Linux kernel looks at field called "program interpreter"
    in program headers. Normally this field is something like "/lib64/ld-linux-x86-64.so.2" (that is standard ELF loader).
    Kernel loads it into RAM and passes control to it. Rest is
    "user mode" operation and if you put in program header path to
    your own loader you can get quite different behaviour.

    Concerning "practical": standard ELF loader is doing a bunch of
    somewhat tricky things and compiled code depends on loader doing
    those things. So you either loose compatiblity with compilers
    or reimplement needed stuff. Standard loader is about 210 kB
    of binary code. Source of standard loader is available so one
    can reuse part of it. I would say that writing replacement
    loader is doable, but needs some effort.

    AFAIK Open Office/LibreOffice used somewhat different path.
    Namely, most Open Office symbols are C++ methods. Those
    must be visible in some other parts of a library, but normally
    there is no need to make them visible outside given library.
    Previously, everthing visible in other files was made visible
    outside given library. IIUC now most of those symbols are not
    subject to ELF dynamic linking: they are resolved when linking
    a library and not visible outside.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sun Oct 5 03:13:29 2025
    From Newsgroup: comp.lang.misc

    On Sun, 5 Oct 2025 01:40:22 +0100, bart wrote:

    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?

    That worked fine for me. I also filled a whole page with rCLThe Quick Brown Fox ...rCY, then tried typing right at the start, so the entire text was forced to reflow on each keystroke. Worked fine -- no lag that I could
    notice.

    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.

    Definitely not seeing that, even with several keystrokes per second.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sun Oct 5 03:15:52 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sun Oct 5 11:05:17 2025
    From Newsgroup: comp.lang.misc

    On 05/10/2025 04: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?

    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'.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Sun Oct 5 13:24:41 2025
    From Newsgroup: comp.lang.misc

    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.

    So this is a higher-order function in Python :

    def do_twice(f) :
    return lambda x : return f(f(x))

    It is a function that takes a function and returns a new function.


    You could, if you like, talk about specific orders of functions, distinguishing between second-order and higher levels, but I don't know
    of any programming language that does so.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sun Oct 5 20:20:27 2025
    From Newsgroup: comp.lang.misc

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Mon Oct 6 09:44:07 2025
    From Newsgroup: comp.lang.misc

    On 05/10/2025 22: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?

    Good question. I have never heard a specific name for that kind of
    thing. But I suppose everything beyond "first-order functions" - or "functions", as most people call them in practice, would count as
    "higher order".


    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.

    A "functor" in C++ is a class with an overridden call operator (). So
    under the hood, it is a class, but you can treat it like a function in
    the syntax of the language (with some restrictions - pointers to a
    functor instance will not be compatible with pointers to "normal"
    functions). They can happily have methods, members, other operators, be created, copied, and so on.

    Lambdas in C++ are basically a syntax for creating functor classes
    without needing to write all the boilerplate.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Mon Oct 6 10:01:02 2025
    From Newsgroup: comp.lang.misc

    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..

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Mon Oct 6 10:56:03 2025
    From Newsgroup: comp.lang.misc

    On 06/10/2025 10:01, Janis Papanagnou wrote:
    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..


    The key practical uses would be in metaprogramming of various sorts -
    either explicitly (like C++ templates) or with reflection (like Python
    class decorator functions). I think the biggest benefit could be to
    reduce boiler-plate code or repetition. Imagine, for example, a
    function that took a list of strings and returned a class that wrapped a strong enumeration type along with string-to-enum and enum-to-string
    functions for convenience for IO and debugging. Or a function that took
    a fixed-size integer type (with arithmetic operations), and returned a
    type that was twice the size (with the same operations). Combine that
    with a higher-order "apply_n_times" function, and now you can write :

    Int1024 = apply_n_times(4, double_int, Int64)

    I think you'd normally want to restrict this sort of function to compile
    time in pre-compiled languages - I would not like to think what you'd
    end up with if you tried to support it at run-time !

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Mon Oct 6 11:03:59 2025
    From Newsgroup: comp.lang.misc

    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.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Mon Oct 6 22:01:23 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Mon Oct 6 23:46:56 2025
    From Newsgroup: comp.lang.misc

    On 06/10/2025 23:01, Lawrence DrCOOliveiro wrote:
    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?

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Mon Oct 6 23:05:53 2025
    From Newsgroup: comp.lang.misc

    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??
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Tue Oct 7 10:32:03 2025
    From Newsgroup: comp.lang.misc

    On 07/10/2025 00:05, Lawrence DrCOOliveiro wrote:
    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.

    In which (incredibly inefficient-sounding) language is this?

    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? There's a reason
    why they are so-called, because they are much more complicated to work
    with and for other people to try and follow.

    It is also almost nothing to do with how an AOT compiler works.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Oct 7 15:25:00 2025
    From Newsgroup: comp.lang.misc

    On 03/10/2025 23:24, Waldek Hebisch wrote:
    [I wrote:]
    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.
    One project that I am working on has more than 8000 exported functions
    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.

    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.

    I believe you, but that's ridiculous. Something somewhere
    has lost its way. It comes from trying to make one program do
    everything that anyone anywhere could possibly want to do in the
    general area of publication [similarly with mail, or with browsing,
    or with compilation]. Every programmer and his dog piles in with
    new functionality; no-one is responsible for saying that some new
    facility is costing more in other ways than it is adding. "Other
    ways" includes code size, start-up time, ease of use, more and
    longer documentation, extra parameters, .... The result is that
    we have [eg] mailers that can do "everything", but in reality we
    all [apart from a handful of "experts"] stick to a small common
    subset. See also the same nearby article that I will soon get
    round to writing!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Necke
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Oct 7 16:37:28 2025
    From Newsgroup: comp.lang.misc

    On 04/10/2025 19:46, bart wrote:
    [I wrote:]
    Try "man a68g"?
    I was hoping that you could, you know, just tell me!
    -a-a-a-a-a"Teach a man to fish ..." and all that jazz.
    You have to show him how to catch the first fish!

    I, 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 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.

    All 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; "man a68g" works fine for me, printing [with no need for any
    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. 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 [...].

    If you don't want to run the program, use the "--check" or
    "--no-run" option. It's all in the manual [and the guide].
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Necke
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Tue Oct 7 17:03:31 2025
    From Newsgroup: comp.lang.misc

    On 07/10/2025 15:25, Andy Walker wrote:
    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, then
    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.
    One project that I am working on has more than 8000 exported functions
    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.

    These are applications not little Unix utilities.

    The last such app of mine is from last century, and it was considered
    small: the binary would fit on one floppy, uncompressed.

    Yet it comprised some 2000 functions, in the compiled core, plus 2000 functions spread around 100 scripting modules which are loaded on demand.

    It needed that many for that interactive GUI app to be able to do its job.

    On top of those are the functions within the add-on modules created by
    third parties. I don't include functions in imported libraries.

    My current projects (interpreters, compilers, assemblers and so on) are
    on a smaller scale, but still have 600-1200 functions each.

    Yeah, full-stack compilers are complicated. But my stuff is still
    1/1000th the size of products like LLVM.

    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.

    Those functions are from numerous shared libraries; they are not the
    core app. Such libraries are big: GTK for example may export 10,000
    functions.

    That wouldn't be a problem (other than for the poor chap who has to
    learn them), but there appears to be an inefficiency in doing the
    dynamic linking. I suggested elsewhere that it could be done on-demand,
    since it is unlikely that all 250,000 functions would be needed during
    one session.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Tue Oct 7 17:16:35 2025
    From Newsgroup: comp.lang.misc

    On 07/10/2025 16:37, Andy Walker wrote:
    On 04/10/2025 19:46, bart wrote:
    [I wrote:]
    Try "man a68g"?
    I was hoping that you could, you know, just tell me!
    -a-a-a-a-a"Teach a man to fish ..." and all that jazz.
    You have to show him how to catch the first fish!

    -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;

    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. This is
    like GUI Design 101.

    The end result was that 'man a68g' gave me gobbledygook: lots of empty
    sets of square brackets.

    -a "man a68g" works fine for me, printing [with no need for any
    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].

    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.

    Most programs do work as expected; this one is decidely unexpected and unintuitive.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Tue Oct 7 22:27:13 2025
    From Newsgroup: comp.lang.misc

    On 07/10/2025 16:37, Andy Walker wrote:
    On 04/10/2025 19:46, bart wrote:
    [I wrote:]
    Try "man a68g"?
    I was hoping that you could, you know, just tell me!
    -a-a-a-a-a"Teach a man to fish ..." and all that jazz.
    You have to show him how to catch the first fish!

    -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.

    No, it's not simple, the UI is poor. My experience of A68G is that it's
    mostly a case of trial and error.

    You may recall that I wanted to see the C generated by A68G, but I can't
    tell you off-hand what it is even after doing it. I need to do more experiments....

    So, to get a C listing, you actual need FOUR options:

    --compile to get it to produce C code, but you also need:

    --listing to create a .l file, but you also need:

    --extensive to include the C code in that file. Plus:

    --no-run so that it doesn't run the program first

    That is:

    a68g --compile --listing --extensive --no-run prog.a68

    It's so obvious! But we're not there yet:

    * The above options will still invoke a C compiler on the output,
    extra delay for a large program.

    * The C code will be buried inside a listing file (with extension .l,
    which it neglects to mention) which is mostly full of other junk that
    you don't want. You will need a tool or a process to extract it.

    It's pretty crap, sorry.

    This is how a typical product of mine works; here I use -a tell it to
    produce an assembly listing:

    c:\mx>mm -a hello
    Compiling hello.m to hello.asm

    Notice that it tells you exactly what it's doing, including the name of
    the file it generates.

    I, perhaps over-optimistically, assumed that you are
    a sufficiently experienced programmer

    I'm sufficiently experienced to identify poorly-designed tools



    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Tue Oct 7 22:09:50 2025
    From Newsgroup: comp.lang.misc

    On Tue, 7 Oct 2025 15:25:00 +0100, Andy Walker wrote:

    (b) No normal person can use 8000 functions; not really even your
    own 400. It's too many to keep in your head ...

    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. I have used
    quite a few of them. No, I donrCOt bother retaining much of them in my
    head: I just keep referring to the docs as necessary.

    The equivalent Java libraries, on the other hand, I can well believe
    need close to 8000 functions/methods -- just to provide the same
    functionality. If there is one word I would use to describe Java, itrCOs rCLbureaucraticrCY.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Tue Oct 7 23:58:00 2025
    From Newsgroup: comp.lang.misc

    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]. Look elsewhere
    for your bug.

    -a-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].
    It shouldn't need those extra options; it should just do the most
    obvious thing without being told.

    It is doing the most obvious thing. You asked "a68g" to give
    you a program listing; you didn't tell it that you didn't want to
    run the program. You got the behaviour I would expect [never having
    myself used either of those options].

    Or more importantly, without users
    have to hunt across multiple different sources (website, PDF, 'man',
    --help) which all seem to give conflicting info.

    AFAIK, the info is the same in all those sources, and any one
    of them will tell you what to do to compile the source, to rerun without re-compiling [if possible], to get a program listing, and to avoid the
    actual running of the program. The only difference there is expected
    to be is whether the info is as concise as possible, or more complete,
    or complete with examples. If you find an actual inconsistency, then
    of course it's a bug and should be reported to Marcel. Experienced
    Linux users are surely familiar with differing uses of "man", "--help"
    and web sites to get information and help?
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Necke
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Wed Oct 8 01:09:26 2025
    From Newsgroup: comp.lang.misc

    On 07.10.2025 18:16, bart wrote:
    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. [...]

    You are making things up! (Or are just lying?) Why?

    Instead of posting misinformation you could verify yourself by looking
    into a68g's man page file (~/src/doc/a68g.1); there's no "setting" of
    colors. The 'man' information is created for display using a standard
    *roff macro by 'man', and it has nothing to do with the Genie package.

    And the 'man' command itself or the *noff macros have also no problem
    with colors; certainly not in the simple man a68g application context.

    My own native terminals are white on black, but also using reverse
    video (tested with xterm) is no problem;

    $ xterm -e 'man a68g'
    $ xterm -r -e 'man a68g'

    all well readable with its formatting and accentuations.


    The end result was that 'man a68g' gave me gobbledygook: lots of empty
    sets of square brackets.

    As mentioned before; could it be that you see the effects of your OS?
    Or have you just mis-configured your system?

    [...] 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.

    (Your opinion - luckily! - isn't the measure of all things!)

    Or more importantly, without users
    have to hunt across multiple different sources (website, PDF, 'man',
    --help) which all seem to give conflicting info.

    There's no hunt whatsoever.

    Every type of information (with its specific purpose) is here in the
    [commonly] expected place, with appropriate form, size, and content.

    A _manual page_ should not have the 700 page documentation, and if I
    want only to quickly see the usage with its options, I need not the
    full documentation but a quick help on the terminal I'm already in.

    Your problems obviously lie primarily in front of your keyboard (not
    in the Genie system).

    Myself haven't noticed "conflicting info" - that's doesn't mean that
    there isn't any, just that it's a huge system with lots of information
    and I may have also just missed it. But if there is any you could just
    file a bug report instead of complaining (or purporting things without providing any evidence).

    (If you don't like the terse options overview output of --help then
    just don't use it. - I never used it; formatted 'man a68g' is better
    readable, IMO. But mostly I prefer anyway the detailed information
    in the manual. - Choose what you like, there's many options; and you
    complain about that! You always complain. Boring.)

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Wed Oct 8 01:20:03 2025
    From Newsgroup: comp.lang.misc

    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.

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From richard@richard@cogsci.ed.ac.uk (Richard Tobin) to comp.lang.misc on Wed Oct 8 00:20:43 2025
    From Newsgroup: comp.lang.misc

    In article <10brq1i$2ldtu$1@dont-email.me>, bart <bc@freeuk.com> wrote:

    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!

    Sounds like you're using a version of nroff or a pager that's set up
    to map what would normally be bold to some colour.

    -- Richard
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Wed Oct 8 04:57:09 2025
    From Newsgroup: comp.lang.misc

    On Tue, 7 Oct 2025 10:32:03 +0100, bart wrote:

    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?

    Yes, I was. In what way do they rCLcross the line in other waysrCY, such
    that you rCLwouldnrCOt have anything to dorCY with them?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andy Walker@anw@cuboid.co.uk to comp.lang.misc on Wed Oct 8 16:10:27 2025
    From Newsgroup: comp.lang.misc

    On 08/10/2025 00:20, Janis Papanagnou wrote:
    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.

    I did know that -- two colleagues were closely involved in
    the development of Troff -- but I thought it was just about possible
    that Bart is not running a standard Man, so that either his "man" or
    his Man macros are somehow tinkering with colours.
    And yes, bart's problems are different from what he thinks it is.
    I couldn't possibly comment!
    --
    Andy Walker, Nottingham.
    Andy's music pages: www.cuboid.me.uk/andy/Music
    Composer of the day: www.cuboid.me.uk/andy/Music/Composers/Valentine
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Wed Oct 8 17:41:14 2025
    From Newsgroup: comp.lang.misc

    On 08/10/2025 00:20, Janis Papanagnou wrote:
    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 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.

    I merely reported that the output of 'man' was weird. I later found it
    was due to essential info being invisible.


    This is what I see if I type 'man python' under WSL:

    https://github.com/sal55/langs/blob/master/light.png

    And this what I see if I first change the background from light grey to
    dark grey:

    https://github.com/sal55/langs/blob/master/dark.png

    On a differently configured console. those missing elements are shown in
    bold.

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Adam Sampson@ats@offog.org to comp.lang.misc on Wed Oct 8 18:22:21 2025
    From Newsgroup: comp.lang.misc

    Lawrence DrCOOliveiro <ldo@nz.invalid> writes:

    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.

    That looks about right for Python 3.14, just counting public functions
    and classes written in Python, and excluding _-prefixed private files
    and names:

    $ 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.
    --
    Adam Sampson <ats@offog.org> <http://offog.org/>
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Wed Oct 8 20:21:04 2025
    From Newsgroup: comp.lang.misc

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Wed Oct 8 21:30:21 2025
    From Newsgroup: comp.lang.misc

    On Wed, 8 Oct 2025 20:21:04 -0000 (UTC), Waldek Hebisch wrote:

    Some people tried to make much more detailed distinctions (for example
    Russel and Whitehead), but those are rather special and less popular.

    As I understood it, they were trying to avoid self-reference (and its potential for paradoxes) by creating a hierarchy of logic such that each
    level could only make references to those below, not to itself or those
    above.

    They failed.

    OTOH higher order functions are used as propagande/advertising term ...

    I would agree with this. Consider something simple like

    f = ++x.x

    Presumably if x is a function, then the order of f must be higher than
    that of x. So what is the order of

    f(f)

    ? Clearly it must mean that the order of f is greater than the order of f!

    AFAICS one can create sensible language which supports
    higher order functions but has no closures or nested functions.

    Do you mean, no lexical binding? That would be a poverty-stricken language indeed ...

    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.

    Some kind of automatic storage management does get very convenient when dealing with highly dynamic data structures, such as when you have
    functions (and possibly also classes/types) as first-class objects.
    Otherwise the housekeeping headache is too prone to lead to bugs.

    That doesnrCOt mean it all has to be done with garbage collection, Java-
    style. Python is going to a lot of trouble to keeping reference-counting
    as a first resort, and only falling back to garbage collection when that
    is no longer enough.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Wed Oct 8 22:40:39 2025
    From Newsgroup: comp.lang.misc

    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.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Wed Oct 8 21:48:32 2025
    From Newsgroup: comp.lang.misc

    On Wed, 08 Oct 2025 18:22:21 +0100, Adam Sampson wrote:

    $ 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.

    My count is not quite that, albeit for not quite the same Python version:

    ldo@theon:~> find /usr/lib/python3.13 -name '[^_]*.py' -execdir grep \
    '^[[:space:]]*\(def\|class\)[[:space:]][[:space:]]*[^_]' {} + |
    wc -l
    9234

    (Why doesnrCOt rCL[[:space:]]+rCY work? The man page for grep says it should.)

    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.

    Whatever Python is, Java will be several times higher. ;)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Thu Oct 9 00:34:00 2025
    From Newsgroup: comp.lang.misc

    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.)

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Thu Oct 9 00:53:04 2025
    From Newsgroup: comp.lang.misc

    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

    Inspecting the standard resources [before posting] is always good
    advice! (I'd suggest to follow that advice. It's never too late.)

    instead of just telling me.

    You may be lazy (and that's okay), and want others do your homework;
    but then accept if folks don't feed you.

    [...]

    So this is someone's fault; I doubt it is mine.

    That's a good hypothesis to keep your mind sane!

    (It may not be a good approach to track errors in your environment.)

    If my choice of screen
    background is bona fide, then it is crass to display text that clashes
    with the background.

    Most people won't be interested to find errors in your system and
    how you configured it, I'd guess, so you have to live with that.

    Hint: 'man a68g' should not behave different to (e.g.) 'man man';
    and both should (of course) be readable; as it is in other system
    environments. (Don't blame Marcel, Genie/a68g.1, man, or *roff.)


    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.

    I think that was sensibly answered already. (Was it Andy's post?)

    Given that you're repeating your personal view and obviously still
    insisting on an answer that confirms your personal view won't lead
    you anywhere.

    Janis

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Wed Oct 8 23:45:17 2025
    From Newsgroup: comp.lang.misc

    Andy Walker <anw@cuboid.co.uk> wrote:
    On 03/10/2025 23:24, Waldek Hebisch wrote:
    [I wrote:]
    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.
    One project that I am working on has more than 8000 exported functions
    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.

    Well, it would be good to have more programmers. Probably 5 full
    time programmers would be plenty (I have day job and spend some time
    on other projects, other currently active developers probably spent
    on the project less time than myself). Concerning problem caused
    by more programmers, certainly educating new programmers is hard
    and "self-starters" that could learn what is needed from available documentation and source code are relatively rare. Some people
    do not want to use old code, instead want to throw it out and
    write new one. Such people would cause trouble. OTOH code
    is split into a bit more than thousend modules. It is written
    is strongly typed language which eliminates several problems.
    There is garbage collector which makes memory management easier.
    Contributors added clusters of modules and integrating such clusters
    into to system was quite easy. Extentions/improvements to existing
    modules were easy too. For many bugs, once there is a reproducible
    test case fixing bug is easy. Of course, there are also harder
    bugs, which require writing substantial amount of new code.
    But I do not see trouble due to scale in this part. In fact,
    other parts which are smaller and contain much smaller number of
    functions seem to be more problemtic (they depend on dynamic typing
    and are not split into well defined modules).

    Maybe I am too optimistic but IMO project could grow 10 or
    maybe 100 times without serious maintanence problem (or course
    would need more developers).

    Let me add that I do not view coding or code structure as main
    problem. As I wrote program is doing complex things and methods
    used are result of substantial research. Good additions preferably
    would add new methods and for this research is the bottleneck.
    I mean either inventing new methods or understanging what is
    described in literature and filling gaps in description. There
    is rather small pool of people having needed knowledge and
    most of them work on different systems.

    (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 (:-)].

    First, I am affraid that your view is shaped but old, suboptimal
    approach to programming. Educated people actively use few thousend
    words (and recognize more). Professional in some domain is likely
    to know few hundreds (or more) domain specific words. Program
    helping such professional may have a function (or a few functions
    in case of overloading) per word in domain vocabulary. Given
    that user already know what the words mean and assuming that
    program usage follows domain practice using hundreds of functions
    is not a trouble. Function names may correspond to multiple
    words, so larger number of names may be quite usable as long
    as names are fit well with domain terminology.

    Of course, things are quite different if one uses outdated approaches.
    Some scientific libraries used random-looking names. Lapack
    is better because there is reasonably natural system of abbreviations,
    but still Lapack function names may be problematic. With bad
    names people managed to use libraries containing hundreds of
    functions, with better names one can deal with larger number of
    functions.

    Concering my project: from user point of view number of function is
    smaller: there is overloading and what user considers as single logical function may have multiple implementations using different data structures
    and possibly quite different algorithms. But there is about 3500
    different names, so still a lot. However:
    - frequently used functions are heavily overloaded, so modest
    number of names covers large number of functions
    - there are tools to help searching for less frequently used
    functions
    - there is IMHO sensible naming convention so names are
    reasonably guessable
    - different users have different needs, I do not know how many
    functions is used by a single user, but when one adds usages
    it is going to be nontivial fraction of all functions
    - some functions do "full job", but some are used in
    implementation of other functions. Users may write their
    own code and may wish to reuse part of implementation
    instead of final result. In current setup this is possible.

    Also, there were people who wanted to do "just one thing". They
    were somewhat disappointed that the thing needed quite complex
    implementation probably using 30-50% of the whole program.

    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.

    Program above depends on garbage collection. There is extensive
    data sharing. Program is highly recursive. So really splitting
    it in Unix way into separate programs is not feasible. But I
    would not call it a monster. It is split into modules with well
    defined interactions. You probably will say that there is way
    too many modules. But relatively small modules simplify reusing
    functionality. AFAICS with larger modules reuse would be harder,
    so there would be more code. And IMO existing module structure
    is quite natural and "expandable", that is it is easy to fit
    new module(s) into existing schema.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Thu Oct 9 01:02:54 2025
    From Newsgroup: comp.lang.misc

    On 08/10/2025 23:53, Janis Papanagnou wrote:
    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

    I was quite reluctant to respond to your post given the insults and condescension I suffered earlier this year in comp.lang.c, which nearly
    gave me a nervous breakdown.

    You (and others) simply HAD to turn every technical point I made into a personal attack: it was always my fault; I was always wrong; I was
    simply stupid.

    Unfortunately little seems to have changed.

    Here, there WAS an issue with getting 'man' to display that info.
    Discovering the right set of incantations to get the info I needed WAS troublesome.

    I simply wanted to know the form of that C that A68G generated, since
    the performance of that C was still quite slow.

    At least there are some magical things I have discovered:

    * There is nothing complicated or confusing about A68G's options; they
    are perfect

    * There is nothing wrong with A68's --stack option despite it not
    working. If a stack-heavy program fails with 'stack overflow', it's
    not because the stack is too small!

    * A68G is considered fast


    You may be lazy (and that's okay), and want others do your homework;

    I didn't start a dozen threads asking stuff about Algol 68 Genie!

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Thu Oct 9 03:15:10 2025
    From Newsgroup: comp.lang.misc

    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    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.)

    Pascal has nested functions which have access to variables
    defined in enclosing function. You can pass such nested function
    as an argument to other functions. Given variable v and function
    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
    you get different function for each value of v. You may object
    that this is really not a new function and such objection is
    quite reasonable in case of global f and v: at any given moment
    there is single value of v so it is more natural to consider this
    as mutation of bahaviour. But in case when f and v are defined
    inside some other function, say g, you may have multiple
    activations of g, each having its own value of v. Consequently
    keeping v unchanged but choosing different instance of f you
    get different behaviour. So it is reasonable to consider
    different instances of f as different functions. At low level
    implementation can get desired effect in various way, one
    approach creates small piece of code (called trampoline) on
    the stack and this code is responsible for calling modified
    version of f, taking extra argument providing value of v.
    With such approach there are actual new machine code function
    created at runtime. There are different approaches which does
    not need any new machine code at runtime, but possiblity of
    implementation via trampolines supports view that by effectively
    cupturing value of v from enclosing functionwe create new
    functions.

    This is exactly the same mechanizm that most laguages claiming to
    support higher order functions use to create function. Difference
    is that in Pascal needed data (and possibly created code) is kept
    on the stack and becomes invalid when exclosing function (called g
    above) returns. Languages supporting higher order functions typically
    allocate needed data (and possibly code) on garbage collected heap,
    so it remains valid as long as there is reference from f. So you
    can return f and use it otside function that created it.

    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.)

    If you use nested functions to create new functions (as I explained
    above) you need a way to reclaim memory when created function is
    no longer needed, which is typically done via garbage collection.

    On more basic level, in functional programming you are expected
    to write things like 'f(g(h(x)))' that is arguments to functions
    are results of previous calls without explicitely storing them.
    This works fine if you operate with values and copy them as needed.
    But in functional programming people want to return potentially
    large and complex date structures. In such case it is natural
    to allocate given data structure once and only pass references
    around. Trouble is, in chain of calls as above reference
    returned by h is passed to g, but not available outside. g may
    embed reference r passed to it in its result or may not do it.
    If g embeds r in its result, then normally it is not valid to
    free memory referenced by r before it is used in f. If g
    does not embed r in its result, then after it returns there is
    way to free memory referenced by r.

    To put it differently: if you return dynamically allocated memory
    in chain of calls as above you need to be very careful which
    function is responsible with freeing allocated memory. Slight
    misunderstanding and you either leak memory of free it too
    early. 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 colegue
    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.

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.misc on Thu Oct 9 08:59:05 2025
    From Newsgroup: comp.lang.misc

    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.

    There might be implementations of extensions of Pascal that provide
    such closures, though.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.misc on Thu Oct 9 10:35:18 2025
    From Newsgroup: comp.lang.misc

    antispam@fricas.org (Waldek Hebisch) wrote or quoted:
    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.

    A destructor has to be /triggered/ somehow.

    When the duration of dynamically allocated (DA) memory is enclosed
    in a scope, RAII can be used to trigger the desctructor.

    When a DA object is being returned, people use smart pointers
    (unique_ptr, shared_ptr, std::make_unique, std::make_shared).
    It is crucial to do all this in an exception-safe manner!
    There are style-guides / idioms for this by Herb Sutter that
    need to be learned.

    They try to always be aware of what instance holds the
    /ownership/ of an object and be aware of transfers of
    ownership, i.e., the responsibility for managing the
    lifetime of a dynamically allocated object.

    Following those rules, copying often can be avoided. Also the
    possibility to /move/ instead of copy values, sometimes can
    help to avoid copying. Moves can transfer ownership without
    copying.

    However, your paragraph made me remember something I read long
    ago, which is partially outdated due to updates in the C++
    specification today, but it is still a cherished memory for me:

    |There were two versions of it, one in Lisp and one in C++.
    |The display subsystem of the Lisp version was faster. There
    |were various reasons, but an important one was GC: the C++
    |code copied a lot of buffers because they got passed around
    |in fairly complex ways, so it could be quite difficult to
    |know when one could be deallocated. To avoid that problem,
    |the C++ programmers just copied. The Lisp was GCed, so the
    |Lisp programmers never had to worry about it; they just
    |passed the buffers around, which reduced both memory use and
    |CPU cycles spent copying.
    <XNOkd.7720$zx1.5584@newssvr13.news.prodigy.com>.

    C++ programmers today rarely have to copy buffers defensively.
    Instead, they can pass around smart pointers or move objects,
    enabling more efficient code like Lisp with automatic GC but
    with the performance control of manual management.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Thu Oct 9 14:31:01 2025
    From Newsgroup: comp.lang.misc

    Stefan Ram <ram@zedat.fu-berlin.de> wrote:
    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.

    Yes, that is frequently used terminalogy. But it means a bit
    different thing than you appear to think.

    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,

    As I wrote, you can.

    because the enclosing function or
    procedure only ever provides one single stack frame

    No. Due to recursion there may be many activations of enclosing
    function, each with its own stack frame. Knuth "man or boy" test
    computes thing which looks like nonsence, but its intent is to
    check that each call to nested function uses correct stack
    frame.

    and the behavior
    of the closure would not be defined after the end of the lifetime of
    its enclosing function or procedure.

    Yes, that is what I wrote.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.misc on Thu Oct 9 14:53:14 2025
    From Newsgroup: comp.lang.misc

    antispam@fricas.org (Waldek Hebisch) wrote or quoted:
    Stefan Ram <ram@zedat.fu-berlin.de> wrote:
    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.
    Yes, that is frequently used terminalogy. But it means a bit
    different thing than you appear to think.

    What is it that I do appear to think?


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Thu Oct 9 21:37:15 2025
    From Newsgroup: comp.lang.misc

    On Thu, 9 Oct 2025 03:15:10 -0000 (UTC), Waldek Hebisch wrote:

    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.

    Reference-counting is a common alternative to garbage collection. It
    works fine, and is quite efficient in its use of memory--until you
    have reference cycles.

    A language like Java goes all-in on garbage collection, with its
    consequent overheads. Other languages, like Perl and Python, try to
    use reference-counting as far as possible, only falling back on
    garbage collection in the aforementioned reference-cycle situation.

    Reference-counting causes issues with trying to take advantage of
    higher CPU performance with multi-threading. PerlrCOs approach to
    threading basically ducks the question; the Python community, on the
    other hand, has been doing a lot of work to handle the situation
    efficiently, without giving up and going to full-time garbage
    collection.

    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.

    The NumPy module for Python works in exactly this way, taking
    advantage of PythonrCOs automatic storage-management infrastructure.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Fri Oct 10 04:18:57 2025
    From Newsgroup: comp.lang.misc

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Fri Oct 10 16:21:51 2025
    From Newsgroup: comp.lang.misc

    On 10/10/2025 05:18, Waldek Hebisch wrote:
    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.

    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.

    My dynamic language, which has to emulate it, runs that x200 test in 0.3 seconds, and the full 10K in 16 seconds (Python would take over 4
    minutes). That version is shown below the C code.


    --------------------------------------
    #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 x=0;

    for(int i=0; i<10000; ++i) {
    x+=a(13, INT(return 1), INT(return -1), INT(return -1),
    INT(return 1), INT(return 0));
    }
    printf(" %d\n",x);
    }

    -----------------------------------------------------
    record bd = var f, k, x1,x2,x3,x4,x5 end

    func B(d) =
    --d.k
    A(d.k, d, d.x1, d.x2, d.x3, d.x4)
    end

    func A(k, x1, x2, x3, x4, x5) =
    if k<=0 then
    callbd(x4)+callbd(x5)
    else
    B(bd(B,k,x1,x2,x3,x4,x5))
    fi
    end

    func callbd(x) =
    f:=x.f
    f(x)
    end

    proc main =
    x:=0
    to 200 do
    x+:=A(13, bd(f:{x:1}), bd(f:{x:-1}), bd(f:{x:-1}), bd(f:{x:1}), bd(f:{x:0}))
    od

    println x
    end

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Fri Oct 10 17:40:56 2025
    From Newsgroup: comp.lang.misc

    On 08/10/2025 22:21, Waldek Hebisch wrote:
    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.

    I am long out of practice with the formal terms here. You have given a
    rather more accurate description than I did.

    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.


    Yes. It is important point that the meanings of the statements are not relevant, just the logical structure - thank you for including that.

    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.

    A lot of Russel's work was "rather special" :-)


    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Fri Oct 10 20:04:14 2025
    From Newsgroup: comp.lang.misc

    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
    can clash and can't be reused, or could accidentally be exported
    from the module when you forget 'static'

    * Where those functions could be called from places where they are
    not intended to be called

    * Where they may not be localised to what would be their enclosing
    function, so could be located anywhere in the module, or even
    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=
    const a = 100
    static int b
    type vector = [4]int

    proc fred(int c) =
    println "Fred", main.a, main.b, c, main.vector.typestr
    end

    b := 200
    fred(300)
    end

    Output is:

    Fred 100 200 300 [4]i64


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Fri Oct 10 21:45:09 2025
    From Newsgroup: comp.lang.misc

    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.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 11 00:29:55 2025
    From Newsgroup: comp.lang.misc

    On 10/10/2025 22:45, Lawrence DrCOOliveiro wrote:
    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.

    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.

    Wrapping functions is not hard and I can't see why higher order
    functions are needed.

    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. No matter if the code becomes more cryptic. Or
    considerbly less efficient with all those layers.

    Sometimes I want to port algorithms, or benchmarks, expressed in such language, but use of exotic features can make that impossible.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Sat Oct 11 00:05:02 2025
    From Newsgroup: comp.lang.misc

    bart <bc@freeuk.com> wrote:
    On 10/10/2025 05:18, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    <snip>

    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.

    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.

    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.

    Similarly, if language have garbage collection, then one can
    create functions in special part of the heap. During function
    creation needed part of heap is marked as writable and non-executable,
    once the function is created corresponding part of the heap is marked
    as non-writeable and executable satisfaing security demands at
    cost of 2 system calls per function created. When no longer
    needed function is garbage collected in usual way. Note that
    stack normally must be writable all the time, so with code on
    the stack there is risk of executing malicious data.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sat Oct 11 00:16:02 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 11 01:59:28 2025
    From Newsgroup: comp.lang.misc

    On 11/10/2025 01:16, Lawrence DrCOOliveiro wrote:
    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.

    Because I implement actual languages and can add any features I like, it
    could be that I don't need it.

    Whatever it is, I can't say it's something I've missed in quite a few
    decades of coding!

    I see that the stuff you posted decorators. That is a feature where,
    everytime I look up what it does, I've forgotten it a few minutes later.

    I need simple, solid, intuitive features that anyone can understand and
    use confidenly. Or if necessary, implement a different way.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.lang.misc on Sat Oct 11 01:42:33 2025
    From Newsgroup: comp.lang.misc

    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?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 11 10:42:49 2025
    From Newsgroup: comp.lang.misc

    On 11/10/2025 02:42, Lawrence DrCOOliveiro wrote:
    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.

    You said you used some complicated method to wrap how you access C-style struct info. I don't know what code would look like before and after you
    did that. Whatever it does, I'm not convinced your approach is the simplest.


    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?

    No. I ran this:

    class fred:
    xyz="hello"

    def def_struct_class(name, ctname, extra = None) :
    ctstruct = getattr(fred, ctname)

    print(ctstruct)

    def_struct_class("a", "xyz")

    It didn't do anything interesting beyond printing 'hello'.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 11 11:25:01 2025
    From Newsgroup: comp.lang.misc

    On 11/10/2025 01:05, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    On 10/10/2025 05:18, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    <snip>

    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.)
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Janis Papanagnou@janis_papanagnou+ng@hotmail.com to comp.lang.misc on Sat Oct 11 13:08:49 2025
    From Newsgroup: comp.lang.misc

    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.

    Janis

    [...]

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Sat Oct 11 12:39:11 2025
    From Newsgroup: comp.lang.misc

    bart <bc@freeuk.com> wrote:
    On 11/10/2025 01:05, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    On 10/10/2025 05:18, Waldek Hebisch wrote:
    bart <bc@freeuk.com> wrote:
    <snip>

    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!)

    Well, this is a compromise. I can not change syntax of C but
    spaces are supposed to make '*' more visible.

    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.

    Good.

    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!

    I think that language implementors want to have single kind of
    function call. Since closures need extra data popular alternatives
    are:
    - use normal pointers (address of code) to represent functions. For
    closures generate extra code that provides the data.
    - use "thick pointers" to represent functions. Thick pointer
    takes 2 machine words and provides both address of code and
    extra data.
    - use descriptors containing both address of code and extra data.
    Represent functions as pointer to the descriptor.

    Second way means that function pointers are bigger than normal
    pointers and do not fit into single machine word. That is
    problematic in languages like C and when interfacing C. Higher
    level languages frequently want to represent complex things via
    single pointer and thick pointers comflict with this. Given
    the above this approach is unattracive for most languages. In
    this version call may need extra instruction compared to first way.

    Third way is essentially "emulated" version, but done in more
    systematic way. Each call must fetch address of code and data from
    the descriptor, adding two memory accesses to each function call.

    Some OS-es went with third convention for C calls. But extra
    memory accesses have its cost, so C implementations typically
    use first way.

    IIUC second way was used in some implementations of languages like
    Pascal or Ada that is relatively low level languages with nested
    functions and restrictive type rules (when they did not care about
    detailed compatibility with C). Actually, in standard Pascal
    and IIRC in Ada too, one can not store functions in data structures
    so only place where representation of function matters is calls
    and both Pascal and Ada need to pass extra data for other
    types, so using thick pointers for functions is reasonably
    natural.

    Third way was used in original IBM PL/I and is frequently used
    by higher level languages which use reference sematintics and
    refer to any composite data via address. To allow smooth
    cooperation of code in various languages third way was used
    as system convention (so also for C) on some OS-ed. Otherwise,
    for interfacing with C they may also implement first way, but
    use it only for functions which may be callable from C.

    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.)
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Sat Oct 11 15:25:47 2025
    From Newsgroup: comp.lang.misc

    On 11/10/2025 12:08, Janis Papanagnou wrote:
    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.

    I think you're also missing mine. Obviously those are toy examples,
    which nevertheless need a LOT of work to implement.

    But I haven't seen many real-life examples where the benefits are clear.
    They just make the code more cryptic and less transparent. And
    near-impossible to port.


    The function twice() that someone
    suggested was a simple example for a _functional composition_, a

    Just having 'functional compositional' as a feature is not enough.

    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.

    The only use I could see myself was for box-ticking, and in the end I
    decided that wasn't enough. My implementation would have been clunky,
    and this stuff needs to be 'native'.

    If my language had this feature, I could also write examples like this
    (not working code):

    fun twice(f) = {x: f(f(x))}
    fun plusthree(x) = x + 3

    g := twice(plusthree)
    println g(10) # displays 16

    My idea was to transform the code so it would be equivalent to emulating closures using existing language features, which would look like this
    (working code):

    fun af$1(x,f,g) = f(f(x))
    fun twice(f) = makecls(af$1, f)
    fun plusthree(x) = x + 3

    g := twice(plusthree)
    println callcls(g, 10)

    This uses a couple of helper functions:

    record cls = var fn, a, b end

    fun makecls(f, ?a, ?b) = cls(f, a, b)
    fun callcls(c, x) = (fn := c.fn; fn(x, c.a, c.b))


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From antispam@antispam@fricas.org (Waldek Hebisch) to comp.lang.misc on Sun Oct 12 03:56:23 2025
    From Newsgroup: comp.lang.misc

    David Brown <david.brown@hesbynett.no> wrote:
    On 08/10/2025 22:21, Waldek Hebisch wrote:
    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:

    <snip>

    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.

    I would say that complex part is creating functions, including
    allocating memory, filling apropriate data structure and
    (if needed) generating machine code.

    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.

    That is usually called "supporting first class functions".

    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?

    I do not know of an example of language that can return created
    functions, but does support closures. However, in Pop11 there are
    two constructs. One, called "frozen values", takes existing function
    and a few values and creates new function, such that call to the
    new function is equivalent to call to the old one with given
    values as first arguments. Second one is closures. Closure are
    more complicated to implements but at the end reduce to frozen
    values. Looking at usage, frozen values are used quite a lot
    and when used code is slightly simpler than alternative code
    using closures. I did not count actual occurences, but my
    impression is that frozen values and closures are used with
    similar frequency or possible frozen values are more frequent.
    Based on this, I think that allowing only frozen values would
    give reasonable support for higher order functions. Adding
    support to C make sense when one wants to compile higher
    level language via C. Of course, this is adding a library
    function, so need not be in C standard.

    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.

    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.

    I explained that in answer to Janis.

    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.

    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.
    --
    Waldek Hebisch
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Mon Oct 13 20:58:15 2025
    From Newsgroup: comp.lang.misc

    On 10/10/2025 21:04, bart wrote:
    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.

    I agree with all of those points. That's why languages like Pascal
    support local functions. Local functions can be useful without
    "advanced" higher order functions (those that return new functions, or
    create functions, rather than simply accepting function pointer
    parameters). I merely said that I think closures and nested functions
    of some sort are so useful for advanced high order functions that a
    language is unlikely to support advanced high order functions without
    also supporting closures and nested functions. But languages can
    certainly support nested functions without being able to write functions
    that return new functions.

    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.


    C does not support local functions. But it could conceivably be added
    to the language (as demonstrated by the gcc extension).

    Closures are only needed for access to stack-frame entities of the
    enclosing functions, including parameters.

    Yes. But those are /really/ useful. Nested functions can have their
    use without closures, but closures make them much more powerful.

    Of course you can emulate closures manually. Basically, you make a
    struct that has all the capture-by-value data and pointers for all the capture-by-reference data, and you add a pointer to that struct to your function parameters. A language that supports closures handles this automatically for you, so that you don't all the boilerplate and manual updates whenever the captured data changes. And the extra struct
    pointer is hidden.

    There was a discussion elsewhere about gcc's handling of nested
    functions sometimes needing an executable stack, or other complications.
    This 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. One way to handle this is with a small function created
    directly on the stack.


    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.


    What is hard or easy to grasp is a very subjective matter.

    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 :

    sort_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.

    You have a library that has a comparison function for names, taking a
    country code as a parameter because different countries use different
    criteria for ordering names (some treat "o" and "||" as different letters
    some treat them as the same letter for ordering, some treat "||" like
    "oe", and so on) :

    compare_names(string name1, string name2, string country)


    The country is chosen by the user. How do you sort your strings using
    these functions? How do you write the function :

    sort_names(string s[], string country)

    ?

    You could do it by :

    define sort_names(string s[], string country) :

    sort_func = lambda (string name1, string name2) :
    return compare_names(name1, name2, country)

    sort_strings(s[], sort_func)

    Perhaps you are using this kind of country-specific sorter function a
    lot, and you want to refactor this into its own function to aid reuse
    and modularisation :

    define make_sort_func(string country) :
    sort_func = lambda(string s1, string s2) :
    return compare_names(s1, s2, country)
    return sort_func

    Now you can do your sorting with :

    define sort_names(string s[], string country) :
    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. 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 don't however, use local functions; I've never gotten into the habit.


    Fair enough. I don't think many people use them a lot - especially with languages that have other ways of handling scoping, such as classes or namespaces.

    I couldn't even tell you if my languages support them and how well.

    That is a rather interesting thing to say! 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 :-) (Yes, I know that
    some of your languages were made a long time ago.)

    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



    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Mon Oct 13 22:38:59 2025
    From Newsgroup: comp.lang.misc

    On 12/10/2025 05:56, Waldek Hebisch wrote:
    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).


    Syntactically, it looks like a function - it is "callable" or
    "invokable" in C++ terms. Yes, it is a somewhat different beast from a
    plain function pointer - every lambda is its own unique type, and can
    wrap a closure as well as a function pointer. If the lambda does not
    need a closure, it can be converted to a function pointer of the
    appropriate type, but it is still not a normal function. C++ supports a number of things that can be used syntactically and semantically like a function in many situations - normal functions, lambdas, function
    templates, methods, functors, std::function objects, etc. They can't
    all be used in the same way in all the same circumstances. In
    particular, if some kind of closure or extra information is needed, you
    can't use a function-like object when a plain function pointer is
    required. But often you use them in connection with templates, allowing
    the syntax and convenience of using them like functions while generating efficient object code.

    If you need a general "pointer to a function or function-like object", however, in C++ you have std::function<> with a good deal of run-time
    overhead to support its generality.

    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.

    It's a question of efficiency. If some of your "function pointers" are
    direct pointers to code, and some are pointers to a descriptor, then
    whenever you are calling through a "function pointer" you need code that checks what style of pointer you have, and acts accordingly. This may
    be significant overhead compared to a straight indirect jump.
    Trampolines on the stack avoid that overhead, but have the cost of
    requiring an executable stack.

    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.


    Yes, I can see that being complicated.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From bart@bc@freeuk.com to comp.lang.misc on Mon Oct 13 23:13:41 2025
    From Newsgroup: comp.lang.misc

    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.
    -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-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.

    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.

    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.misc on Tue Oct 14 00:39:37 2025
    From Newsgroup: comp.lang.misc

    On 14/10/2025 00:13, bart wrote:
    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.

    That is not an unreasonable choice. There are tradeoffs in allowing
    such functions in C (or any other language where you can't pass around
    more information than just the address for a function pointer).

    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.)


    It can always be done with data - but that might be less efficient than
    using executable code. The overhead is probably worth it, however.


    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!


    Yes.

    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).


    Trampolines are fast, but require an executable stack. Function
    descriptor pointers are slower (even when the function pointer is just a normal function - all indirect function calls get an overhead) but
    avoids the executable stack. I don't know what happens for this kind of
    thing in Windows, sorry.

    But as you say, this example isn't that compelling, as there are any
    number of workarounds.


    You can always find workarounds for all kinds of things. A language
    doesn't have to have any kind of for or while loop, if it has "goto". A language doesn't need functions to take more than one parameter, if you
    can pass a pointer to a struct. This is not about "can you make
    flexible sort functions without closures?", it is about showing how real
    code can use closures to give neat and clear code.

    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.


    OK.


    --- Synchronet 3.21a-Linux NewsLink 1.2