Hacker Newsnew | past | comments | ask | show | jobs | submit | algernonramone's commentslogin

It's not immediately clear from reading this what this means for ACM books, both older ones and new ones. I'm a fan of a lot of their older books, such as the Turing Award Lecture anthology they published in the early 1990s. I'm also interested in some of the newer books they've published in the last several years (The tributes to Dijkstra and Hoare especially stand out). I really hope these are included as well.


Seems somewhat related to Iverson's 1979 Turing Award lecture, "Notation as a Tool of Thought" (https://www.eecg.utoronto.ca/~jzhu/csc326/readings/iverson.p...)(https://news.ycombinator.com/item?id=25249563)


Reading the YCombinator link there's a mention of APL and a comment by dTal[1] which includes saying:

> "A lot of the mystique of APL is because it's illegible ... nothing more than a DSL for 'numpy-like' code. .. same demo, using Julia and the result is (in my opinion) much more legible: ... let n=sum(map(

sum() in Julia is more clear and more readable at a glance than +/ in APL, but the APL version is a combination of two things. + which is a binary addition function, and / which is reduce, a higher-order operator or meta-function. sum() in Julia doesn't lead you to think about anything else except what other builtins exist. The APL notation leads you to wonder about combining other commands in that pattern, like times-reduce is ×/ and calculates the product of an array of numbers. From the notation you can see that sum and product are structurally related operations, which you can't see from names sum() and product(). Then you change the other part by wondering what plus does if used with other higher functions, like +\ (scan) and it's a running-sum across an array. (i.e. "+\ 1 1 1 1" gives "1 2 3 4", the sum so far at each point).

So the notation isn't just about readability, it's a tool for thinking about the operations. Different notations enable you to think about different things. If we imagine there was no sum() then you might write:

    sum = 0
    foreach (n in numbers) { sum += n }

    product = 0
    foreach (n in numbers) { product *= n }
and whoops that doesn't work; this notation brings to the focus that sum has to start with 0 and product has to start with 1 to get the right answer and you can wonder mathematically why that is; APL notation hides that just like it hides the looping. Different notation is a tool for changing the what people think about - what things we must attend to, cannot attend to, and what new things a notation enables us to see. dTal's next reply:

> "the power of abstraction of APL is available to any other language, with the right functions. ... there's nothing to stop anyone from aliasing array-functions to their APL equivalents in any Unicode-aware language, like Julia (oddly, nobody does)."

Maybe nobody does it because if you can't take the patterns apart and put them back together differently without an APL engine behind it, is there any benefit? Take an example from APLCart[2]:

    {⍵/⍨∨\⍵≠' '} Dv      # Remove leading blanks [from a character vector]
In C# that task is str.TrimStart() and I assume it's a loop from the start of the string counting the spaces then stopping. Calculating length - num_of_spaces, allocating that much memory for the new string, copying the rest of the string into the new memory. I wouldn't think it was do-able using the same higher order function (\ scan) from a running sum. What this is doing to achieve the answer is different:

          {⍵≠' '} '   abc   def'       # make a boolean array mask
    ┌→──────────────────────┐          # 0 for spaces, 1 for nonspaces
    │0 0 0 1 1 1 0 0 0 1 1 1│    
    └~──────────────────────┘
          {∨\⍵≠' '} '   abc   def'    # logical OR scan
    ┌→──────────────────────┐          # once a 1 starts,
    │0 0 0 1 1 1 1 1 1 1 1 1│          # carry it on to end of string
    └~──────────────────────┘
          {⍵/⍨∨\⍵≠' '} '   abc   def'
    ┌→────────┐                        # 'compress' using the boolean
    │abc   def│                        # array as a mask to select what to keep
    └─────────┘  
Now how do I remove the leading 0s from a numeric array? In C# I can't reach for TrimStart() because it's a string only method. I also can't assume that there's a named method for every task I might possibly want to do. So I have to come up with something, and I have no hints how to do that. So I have to memorise the TrimStart() name on top of separately learning how TrimStart() works. That notation gives me a clear readable name that isn't transferable to anything else. In APL it's:

    {⍵/⍨∨\⍵≠0} Dv      # Remove leading zeroes [from a numeric vector]
That's the same pattern. Not clear and readable, but is transferable to other similar problems - and reveals that they can be considered similar problems. In C where strings are arrays of characters, you aren't doing whole array transforms. In C# strings are opaque. In APL strings are character arrays and you can do the same transforms as with numeric arrays.

Which part of that would you alias in Julia? I suspect you just wouldn't write a trimstart in this style in Julia like you wouldn't in C#. You wouldn't think of using an intermediate boolean array.

It's not just about "readability", the APL notation being concise and self-similar reveals some computy/mathematical patterns in data transforms which "giving everything a unique English name" obscure. And APL notation hides other patterns which other notations reveal. i.e. Different notations are being tools for thinking differently about problems, Notation as a Tool for Thought.

[1] https://news.ycombinator.com/item?id=25258819

[2] https://aplcart.info/


this is why i like how operators work in pike:

+ - * / and other operators work not only on numbers but on strings, arrays and other types and all have an intuitive application.

on strings and arrays for example, + is concatenate, / is split, * is join, - is filter (with static values).


I am willing to admit that I find matrix multiplication ugly, as well as non-intuitive. But, I am also willing to admit that my finding it ugly is likely a result of my relative mathematical immaturity (despite my BS in math).


Maybe it helps to think of matrix multiplication as a special case of the composition of linear transformation. In the finite dimensional case they can be expressed as matrix multiplications.


I don't know, but I hate it.


Me too, at first I thought it was a takeoff on "Heathkit". Silly me, I guess.


I always thought that was a cool idea, and I was disappointed when it was essentially abandoned after the PIII. I still think there’s value there for things like customizing processors for certain applications (having a standard base chip and external interface, but being able to customize the rest of the the processor board).


I think ALGOL nowadays would likely work best as sort of a lingua franca psuedocode, I don’t think it would be too difficult to make that work. It kind of was already for a long time, as much of the Collected Algorithms of the ACM were originally written in it IIRC.


I feel that "deterministic" is probably a better word here than "idempotent".


Not speaking to your comment specifically, but adding context to the thread: idempotence would mean having the same result, regardless of whether you run something once, twice, or 10 times, over the same input set. Idempotence requires but goes beyond determinism, as it also accounts for any externalities and side effects.

For example, let’s consider a function that accepts a string as an argument and then writes that string to disk. We can consider the disk state as a side effect of the function.

The function itself is perfectly deterministic (output string is a predictable and consistent function of input string), but depending on the implementation of side effects it may not be idempotent. If, for example, this function room simply added the output to a file “output.txt”, this file would grow with every incantation, which is not idempotent. If instead we overwrote the output file so that it reflects only the singular output of the previous run, then the side effects would also be deterministic, that would be idempotent.

At a pedantic level you could redefine your scope of deterministic to not just include outputs, but also include the external state and side effects, but for practical purposes the above distinction is generally how deterministic and idempotent would be applied in practice in computing. I cannot speak to the math-centric view, if there is a different definition there.


This captures the mathematical definition too which is just that an element x is idempotent if x applied to itself gives you back x. I.e, what you said that the function applied many times produces no change to the system.


I don't think the string to disk example can be idempotent no matter how you implement it as it is dependent on the external state of the disk.


That may be true on a theoretical level, but if you’re talking practically to a data engineer that’s the definition of idempotence you’re going to find they expect.


Practical consideration might be that a disk may experience a fault in one of the executions: works fine a hundred times but fails on 101st (eg hits disk-full error or a bad block).

But that simply means it's harder to implement true idempotency when it comes to disk usage.

This is why the problem is usually simplified to ignore unhappy paths.


I fear y'all (or I) may be dancing a bit too deeply into hypothetical here...

The idempotent version of this function doesn't blindly write. Most of Ansible, for example, is Python code doing 'state machines' or whatever - checking if changes are needed and facilitating if so.

Where y'all assume one function is, actually, an entire script/library. Perhaps I'm fooled by party tricks?

Beyond all of this, the disk full example is nebulous. On Linux (at least)... if you open an existing file for writing, the space it is using is reserved. Writing the same data back out should always be possible... just kind of silly.

It brings about the risk of corruption in transit; connectors faulty or what-have-we. Physical failure like this isn't something we can expect software to handle/resolve IMO. Wargames and gremlins combined!

To truly tie all this together I think we have to consider atomicity


> Writing the same data back out should always be possible...

Depends on the implementation: maybe you open a temp file and then mv it into place after you are done writing (for increased atomicity)?

But as I already said, in practice we ignore these externalities because it makes the problem a lot harder for minor improvements — not because this isn't something software can "handle/resolve".


Maybe even hermetic builds? https://bazel.build/basics/hermeticity


To give a concrete example: some build systems embed a "build number" in the output that increments for each non-clean build (yeah this is stupid but I have seen it).

This is deterministic (doesn't change randomly), but not idempotent.


The main problem with a statement like that is that "interesting" is extremely subjective. Personally, I often find math and CS to be more interesting when it's further from reality. To each his own, I suppose.


This is great, and I will download it, but I might be missing something because I don't see the PDF? I guess I will stick to the epub.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: