Was trying to implement "pretty printing" of APL arrays, mimicking the ]box on style. I have to say, I just kept wishing Python was array oriented because manipulating all those strings was annoying as hell
I got the main things working, at least. May still have some hidden bugs, but some things already print decently:
You mean the lonely 1? Dyalog APL prints this more or less like I have it there.... Except that Dyalog APL aligns decimal points and I think J's for complex numbers as well
Last night I had this idea. In APL, you can modify arrays using the ← operation. Like foo[2;3] ← whatever. Now, when your language has immutable objects, this use of ← is not useful. However, the general idea of deriving an object based on some other with a subset of it modified is still useful. So a general syntax for this may be the following:
Let's choose the symbol ⇺ for immutable modification. Now, the expression foo[2;3] ⇺ 5 will return a new array, similar to foo but with one cell changed. So you could do something like foo ← foo[2;3] ⇺ 5 to create this new array, and reassign it to foo. You could even imagine a new operation, like ⇷ that would do the two things at the same time foo[2;3] ⇷ 5 which then would behave remarkably similar to the APL way of doing it, without having to suffer from modified arrays.
Please critique this idea.
It feels like this is something that someone must have done already. It's not particularly bleeding-edge language research.
This puts me in a dilemma. I was to be compatible with Dyalog where it makes sense, and this is a case where it makes sense. But I use @ for character literals already so I need to find a different character for that :-)
My operation would be as generic as possible. Every operation/function would have a "modification method" that, if it exists, means that the ⇺ operation can be applied to it. So you could, for example, do something like: (↑x)⇺100 to modify the first element of an array.
Well, not modify. Return a new array with the first element replaced.
No, but it should have been imo. We propose ⍢ for it, which is what Extended Dyalog APL and dzaima/APL use, and is probably what Dyalog will use if we ever get around to add it.
I haven't implemented Dyalog style ∘ at this point, and I never considered doing it. Is there some good documentation somwhere that explains precisely how it works?
@Adám Yeah. Now I understand it says it unlinked 0 items because the link was syncing 0 things, and I thought it meant it broke 0 links. Maybe it is not that unclear, I am just a dummy
Hmm, the ∘ operator makes sense. However, from a parsing perspective it collides with its use as the Null function (left argument of .). So I'd need to special-case it when building the AST.
Perhaps I should go back to my original idea of having outer join be a separate symbol, and not a special case of .
@EliasMårtenson for food for thought, dyadic \ or ⍀ could be used for providing the first item to start with. (unfortunately dyadic / & ⌿ are already taken :/)
Well, in this case, disp is simply using a matrix to cause the system to print a specific text.
The Windows IDE instead puts every character into a cell in a grid, so RtL and combining characters are ignored.
It makes reasoning about the data easier, but makes human language unreadable. I think that's fine, though, as you don't need proper rendering until at the GUI frontend, and that'll just work.
(still better than dzaima/BQN :p (and when/if i fix that, i doubt i'll support grapheme clusters, it's probably already impossible enough to get what they are, let alone handle them properly))
@dzaima It is possible. You just let your Unicode library deal with it. That said, that still only covers languages where each grapheme cluster fits in a box. Arabic will of course not work, but even things like Devanagari will be problematic.
@dzaima It is. What you want is BreakIterator.getCharacterInstance(). It returns an iterator that splits a string into a sequence of grapheme clusters. For the purposes of terminal-style rendering, you can consider each substring something that fits in a single box.
@EliasMårtenson But there are additional issues. Some characters have double width. Should boxing take that into consideration? How would you display a 2-by-3 matrix where one row is Chinese and one is Greek?
@Adám I currently don't. This is mainly because the wide characters are considered legacy in Unicode. However, any language with Unicode support has functions that allows you to check if a character is a wide character. It's not really that important though.
@dzaima Good point. What I do is that I only render rank-1 arrays as strings. Multi-dimensional arrays render each character using its @-representation
@dzaima That's not what I was suggesting. In dyalog's case, my opinion is that grapheme clusters should be handled for rank-1 arrays (i.e. "plain" strings). For higher rank arrays, the combining characters should be replaced with replacement characters, so that it's clear where things go.
The issue here is that what Dyalog is trying to write is invalid Unicode. Note how the conbining character ends up on the next line, so the text renderer gets confused since you're not allowed to start a line with a combining character.
But you can't do that, since the å is two characters: The simple character 'a' followed by a combining character. What happens is that the 2 2 reformatting breaks that unit and drops the combining character at the beginning of the next string. This is invalid.
In digital typography, combining characters are characters that are intended to modify other characters. The most common combining characters in the Latin script are the combining diacritical marks (including combining accents).
Unicode also contains many precomposed characters, so that in many cases it is possible to use both combining diacritics and precomposed characters, at the user's or application's choice. This leads to a requirement to perform Unicode normalization before comparing two Unicode strings and to carefully design encoding converters to correctly map all of the valid ways to...
One possibility would be to redefine how character arrays work. Instead of being defined in terms of code points, they could be defined in terms of graphemes. I think Raku does that.
@EliasMårtenson That's is totally possible. Although you'd probably only want to do so for higher-rank arrays, not for vectors, since ⎕UCS 65 66 13 67 68 makes total sense.
@Adám That is exactly what I'm suggesting. With the added suggestion that certain special characers (like the combining characters) are replaced with something more nice than just a U+FFFD. For combining chars, that would be the dotted circle with the combing char added to it.
@Adám Exactly. That's why I said that all of this is only applicable for rank-2 and higher arrays.
@EliasMårtenson Dyalog's fancy output code is written in APL, so I can easily experiment (and make real changes ― I've done so before). Give me the range of characters to replace and I'll give it a try.
For rank-1 character arrays, all that needs to be done is that the number of grapheme clusters is used for measuring the width of the string instead of the char count.
@Adám I have started rewriting my "fancy" (not as fancy as Dyalogs) display code in KAP itself instead of Kotlin. A 100 line function for laying out a rank-2 array become 2 lines of APL.
this all would be much nicer if there were separate output commands for human output and machine output. (i'm planning to do something like that for BQN - •← for pretty human-readable, •Out for consistent, well-defined output (including things like no nested arrays))
Also, currently I don't even have a "plain" output. Everyting is always boxed, so if you want to display a plain string you have to use the print function.
@dzaima That is a very astute observation. In my case, everything is for human reading. You have to use print for machie output. It has formatting arguments that specifies if you want to have PLAIN, FANCY or READABLE output (the last one being output that you can feed back into the parser to get the same data)
@EliasMårtenson but it does store the output as a character matrix
@EliasMårtenson ah, in which case i wholeheartedly agree with whatever pretty-printing you may want to do on ⎕←
@Adám (importantly, •←2‿2⥊"abcd" is free to box things, print in roundtrip, etc whereas •Out 2‿2⥊"abcd" will always literally output the lines of the matrix (even if they include newlines), separated by newlines)
it's annoying that simple scalars are arrays, otherwise i could say that namespaces aren't mutable arrays
regardless, even if you find them pointless, namespaces definitely allow doing things you just could not at all do nearly as nicely otherwise, and pretty much never harm you otherwise
@ngn right, no arbitrary keys is also kind of bad. But with arbitrary keys, dot syntax becomes strange (and it's definitely something i'd want)
@ngn imo they're for giving access to mutable things. A dialog UI is mutable. A drawing canvas is mutable. A file is mutable. So I want a mutable interface to them.
@ngn a special object that somehow connects APL and a window. What specifically that means is the question here - I'm saying the best way is to make the APL object mutable, you want to somehow make it work with it being immutable.
@ngn So the APL-side object can be nothing more than a pointer to the OS-based structure, since any other data it would go out of sync if it's changed in another copy. So what you're proposing is equivalent to a file descriptor, but for windows.
so you're just pushing the mutability problem back to the OS/whatever provides you with the window structure, while at the same time making syntax for using it just awful and defining other pseudo-mutable things (think, a graph element on the window) nigh impossible.
@dzaima here's a proposal: Wrap the window ID in an immutable object that knows the ID is a window. Then make syntax such that thatObject.size calls the OS-specific function to get size of the window from the ID. Is that new completely immutable object still immutable?
i don't understand why we're discussing windows. a window is a resource outside of the control of our language. it needs to have some id acting as pointer. that doesn't mean the rest of the language shouldn't be designed properly and consistently.
there is this double-think some apl users (dyalog in particular) seem to have: when you ask "should arrays be values?", they say yes. then you ask them: "namespaces are arrays, should they be values too?" and they say no. but the conditions are the same. what's so different about namespaces that makes people alter their reasoning?
a window is just one of the simplest examples to think about. You're right that the only places where mutability is just outright necessary is for outside interactions (another example being a canvas - you don't want to keep it in RAM so all interactions to it must be through some (mutable) interface, mutating the GPU contents) but the system of mutability is just so nice for many other things
@ngn Namespaces are "immutable" from the perspective of the array (the reference never changes), but not from the perspective of the user of it (what precisely they reference might change)
let's look at it like this: both arrays and namespaces are like mappings. arrays associate integer indices with the corresponding elements. namespaces associate keys with corresponding elements. so, namespaces are like a generalization of arrays. it's a small generalization - just let the keys be anything (or maybe just identifier strings, like in dyalog's case) that doesn't have anything to do with the mutability of the container.
it's a simple argument. i hope i'm not being too annoying by repeating it so many times.
@ngn Mutable namespaces are more than a just mapping of items though - it's information about a unique thing's mappings. The case when I just want a hashmap is the 1% here (though if they allowed arbitrary keys, the 1% would drastically increase, but still would leave quite a bit of need of some mutable structure too)
@dzaima "unique things" - so, you're attributing identity to namespaces. it's like you assumed they have reference semantics before we even started arguing. that's ok - so, why not give arrays identity too, then?
it's like: sentence A says something, we proved that something, now under the same circumstances statement B says the same thing, so let's consider it false because both truths and falses can be useful sometimes :)
it's good that containers A (arrays) be values. is it good that containers B (namespaces) be values too? no, you say, because "we want both" values and references.
@ngn I think the problem here is that you're starting with the idea that namespaces are intended as a kind of container like an array. Maybe that was the case in Dyalog; I wasn't there. For BQN I was trying to solve some problems of code encapsulation and found that namespaces do that and are also very similar to closures, which already exist. If I wanted to make arrays with non-integer keys then I would have dictionaries and not namespaces.
As the conversation with Elias shows, K-style dictionaries fit poorly with APL arrays because they only have one key while arrays have many. My preferred approach would be to extend arrays so that each axis has a domain which might not be a prefix of the natural numbers. But I think this would be too complicated in BQN, which doesn't have a lot of facilities for working with axes.
not those details, but they are not essential. what matters is what we actually have in the interpreter here and now. i don't care about N years ago - that's gone.
what we have now is a design in which some objects are mutable and some are not.
@ngn But surely there is a reason APL2, Dyalog, and BQN all ended up adding fairly similar mutable name-value associations? I still think Dyalog's version of namespaces is a mess, but I ended up designing something that has the same central ideas.
@ngn if you want to understand why a feature is useful, though, you need to know what it solves (and what it solves includes objects with identity, and also allows users to create their own objects with identity; You may never want identity, but i certainly like it. What it doesn't solve is a need for a dictionary/hashmap structure)
@ngn I come from J and have never considered myself a Dyalog programmer. I'm pretty sure I've never added namespaces to a Dyalog program except to debug them.
(There are related questions about infinite sandpiles, and finding identity elements of sandpiles.)
Given a matrix of non-negative integers, return a matrix of the same dimensions, but toppled:
If the matrix doesn't contain any values larger than 4, return it.
Every "cell" that is larger than ...
@Marshall but are you convinced that programming with value semantics is better than with ref semantics? @dzaima doesn't believe this, and there seems to be no way to convince him otherwise
BQN already had mutable data before namespaces because it has function and modifier closures. Even in Dyalog function closures can be put into an array with ⎕OR, although the interpreter makes scopes vanish when the function finishes so this is hazardous.
e.g. in this code, g is a namespace referencing the window canvas. I can just do g.BG '1' to set its background to #111111. In an immutable world, that'd either look like g ↩ g BG '1', after which I'd need to make some "write to screen" call (never mind needing to refcount the GPU buffer), or there'd be a global list of all canvases and the function mutates that. Both are significantly more annoying than just g.BG '1'
@ngn I don't really think any kind of programming is always better (there are cases where one design is strictly worse than another, but it's very much a partial order).
I think sticking to immutable data is the right thing to do most of the time, but I also think most functions should be pure and yet I've allowed side effects in BQN.
@ngn A pseudorandom number generator is an example that's definitely inside the language. Using a function that takes initial state and creates function closures provides a very clean interface and lets state changes be localized to the parts of code that have access to a particular copy of the generator.
@ngn I do agree that immutable values are always the right choice in cases where you don't use mutable values.
@ngn No, function closures require exactly the same implementation support (they can pretty easily be used to implement namespace-like things with awful syntax).
@dzaima all I'm saying there is I have a type of thing, and I may want to call BG on the thing. Doing that in an immutable world is hard because I necessarily need to write the changes back to wherever I got the object from, and as such you need the structure to be at a consistent location so it's easy to update, heavily decreasing the amount of freedom in structures of things
what functionality do ppl want from namespaces? the only thing that comes to mind for me is code encapsulation but i found lexical scoping plus functions as values was enough for me. granted i havent written a large bqn codebase or interacted w a large codebase so grain of salt etc
imagine a namespace with keys 'id' 'name' and values (3 5 8)('alice' 'bob' 'charlie'). when we transpose it, we get a vector of namespaces. when we index that with, let's say, 1, we get a namespace with id:5, name:'bob'
i think i lack some of the context because i havent used a namespaced apl-family language. the most i interact w namespaces are import/export semantics and i dont see why those have to be mutable at runtime (i dont do gui programming tho)
So you're calling Dyalog namespaces bad because they don't do a thing they aren't meant to do.
@ngn the discussion isn't about the operations on the canvas, it's about the storage of the canvas and how you operate on it (so no, my argument would be no different). In a mutable world I could just have nested containers for a GUI and have the pixel matrix as a leaf element somewhere far off and just call BG on it, whereas I'd need to backtrack-rewrite everything in an immutable situation, or have a drastically different setup for storing them
@cannadayr There's a restricted version of namespaces that have to be immediately destructured. That's the version in the self-hosted BQN (still only mostly implemented). These aren't mutable at runtime like you say. However, it's natural to extend it to general namespaces, which is what dzaima/BQN does.
@ngn "To me, k and C are the same thing. k doesn't have pointers, just some handicapped integer indexes in arrays, so from that I conclude that k is bad"
@ngn You are in favor of both function closures and the eval function, right? Because if you put them together (with local scoping for eval) you literally just get namespaces, except without any control over what variables can be accessed.
Sorry to interrupt but I was wondering if someone could explain what ⍤0 1 does, I used it in a solution and it works somehow but I don't really understand entirely how it works
@ngn also what about "To me (immutable) dictionaries and (mutable) namespaces are the same thing. Dictionaries aren't mutable and so are handicapped, so namespaces are better"?
@dzaima (i'll stop with this spam of comparisons now)
@rak1507 It applies a function over and over again (if necessary) such that the function only ever sees a scalar as left argument and a vector (or scalar) as right argument.
@rak1507 In general ⍤0 1 matches 0-cells of the left argument with 1-cells of the right argument. In this case, the left argument is always a vector (rank 1) while the right argument has rank 2, so the left argument can be seen as a list of scalars and the right argument as a list of rank-1 rows. So ⊃⍤0 1 matches each scalar to the corresponding row.
@dzaima it's funny when you're copying my style of expression :) i guess your last few messages do make sense if we don't consider immutability a virtue, but i strongly believe we should
@ngn immutability is good, but as with most things, it isn't always 100% no-matter-what the absolute best thing. If we assume that immutability is always 100% no-matter-what the absolute best thing, then of course we shouldn't have mutable namespaces
@Marshall closures - not sure. i'm a half-hearted supporter of their inclusion in k, though i never implemented them in my own dialect.. eval - it's a necessary evil. i don't think i've ever actively argued for it. in ngn/apl ⍎ was less powerful than in dyalog (no access to locals). i understand that closures and eval are mutability in disguise.
@rak1507 Well, if your data is orthogonal, you'll get better performance if you can use flat arrays, so with two matrices, you can do (2 2⍴1 1 1 2) (⌷⍤0 1⍤1 2) (2 3⍴⍳6)
@rak1507 Nothing to be scared of. It simply means ⍤1 2 for each vector (row) on the left and the entire matrix on the right, ⍤0 1 for each scalar on the left and each row on the right…
So first, we pair up rows on the left with the entire matrix (each row gets the entire matrix to choose from) and then when we have a vector paired with the matrix, we pair a scalar from the vector with a row from the matrix.
@ngn Well, I guess I'd encourage you to think that closures demonstrate that mutability is a useful tool for code organization rather than that allowing mutability is a downside of closures. I started out wanting everything to be pure: in I, the only parts of the language are functions, and whitespace and parentheses to specify precedence, and the only impure functions are get, set, and print.
I've gradually come to realize that language purity can lead to messy and fragile programs, and a few well-designed impure features can make programs easier to write and reason about overall.
@Marshall "i tried and couldn't do it" is not a good argument - i know that from crypto :) you should look at what the best in the trade have already done.
@ngn (to clarify, slightly off-topic: in cryptography they often get these questions: "i designed a cipher, and no matter how hard i tried, i couldn't break it. could you please review it?" - 100% of the time it's something stupid. everyone can design a cipher they cannot break.)
@Marshall "purity can lead to messy and fragile programs" - i would argue the opposite. it's harder to reason when objects can change without you touching them. you never know if a function you call has a ref to the same ns that you have and changes it when you're not looking.
"a few well-designed impure features" - agree. but let's keep them to a minimum.
I'll agree that 99% of code should work with immutable data, but for that 1% with mutability I'd rather have a well-designed, nice system, rather than something squeezed to a minimum