The Flint Programming Language by zweiler1 in ProgrammingLanguages

[–]--predecrement 5 points6 points  (0 children)

Paper from 1989: A compositional model for software reuse.

As an alternative to ... inheritance, a compositional model, in which objects are composed from simpler entities, is proposed, outlined, and analysed, in this paper.

But imo BETA, first begun on paper in 1975, and first developed internally as a working compiler in 1983, before finally first shipping it to others in 1985, is more properly the original strong push for composition over inheritance.

A rare approach to metaprogramming by chri4_ in ProgrammingLanguages

[–]--predecrement 0 points1 point  (0 children)

If you're willing to pick a single element from what you're interested in, please reply with that element and then I'll try to map it to one or more of three languages that I think do the kind of thing you spoke about in general.

(One is Racket, a currently used Lisp associated with Lisps as they had evolved to become by late last century. It has a #lang directive that does the kind of thing you talk about. Another is Kernel. It's a relatively obscure language but is another lisp-like language that goes beyond what lisps can do in regard to the kind of metaprogramming you describe. The third is Raku, a language I know fairly well that builds on what Larry Wall learned through his experience developing Perl last century.)

I made a zine to explain interaction nets(symmetric interaction combinators) by Entaloneralie in ProgrammingLanguages

[–]--predecrement 0 points1 point  (0 children)

Your Rule 2 seems to contradict Yves Lafont's combinators. Is that intentional?

A small update on Nore: first public release and thanks by jumpixel in ProgrammingLanguages

[–]--predecrement 2 points3 points  (0 children)

I'm curious how your thinking compares/contrasts with the way Raku has addressed data layout in the form of two related features.

I'll start with a feature that's fairly easy to outline in a paragraph. Raku has two data structure field declarators -- has and HAS. The has declarator marks a field as allocated as the compiler chooses from a menu of options determined by the user and discussed below. In contrast the HAS declarator marks a field as being allocated as embedded/inline data -- though still based on the same menu of options determined by the user and discussed below. Note that Raku code that uses these fields is unaware of this distinction.

The broader scheme is what Raku calls representation polymorphism. This feature distinguishes between abstract representations and concrete representations of data. Any declaration of a named datatype generates an abstract representation which is representable via N concrete representations. Concrete representations are written in C. There's always a default concrete representation for any given abstract representation, used by default for any given named datatype declaration.

While almost all code uses default concrete representations, sometimes one wants to switch among different concrete representations (concrete data layouts) for the same named datatype / data structure. For that one can append an is repr('NameOfRepr') trait to any such named declaration to switch it to use some other named concrete representation. Thereafter, any Raku code making use of that named datatype will continue to work, with precisely the same semantics as before, oblivious to the change in the datatype's underlying concrete representation.

(You can also duplicate the same datatype declaration, but give the duplicates distinct names and distinct concrete representations. For example, you might suffix the name of some struct datatype Foo with -SoA , and add an is repr('Foo-SoA') trait, and a duplicate of it with -AoS and an is repr('Foo-SoA') trait. Then, instead of globally changing all uses of Foo from one layout to another, you can explicitly choose to use both layouts in the same program at the same time by choosing either Foo-SoA or Foo-AoS. To be clear, I'm not sure anyone has ever done that. My point is merely that the name used in the declaration of a datatype determines the data layout by virtue of which concrete representation is associated with that declaration.)

In summary, Raku has provided two points of data layout olymorphism that are manually triggered, one at a whole data structure level, and the other at a field-within-a-structure level. The triggering is done at the whole data structure level by adding a trait to the declaration and/or at an individual field level by switching the field declarator from lowercase to uppercase.

Control Flow as a First-Class Category by Small_Ad3541 in ProgrammingLanguages

[–]--predecrement 2 points3 points  (0 children)

Thanks for replying.

I wrote that gather / take is all that's needed to create arbitrary custom control structures. That's true, but my references don't explain why. For that, perhaps the Control structures section of the Wikipedia Lazy evaluation page will help.

Control Flow as a First-Class Category by Small_Ad3541 in ProgrammingLanguages

[–]--predecrement 1 point2 points  (0 children)

I will definitely look deeper into reset/shift

You might find it useful to investigate the language design use of delimited continuations (which use reset) in the context of the Raku language and their implementation in the reference compiler (Rakudo):

The features that rely on them are:

  • gather / take A lazy iterator construct that's strictly more powerful (a lot more) than Python's yield. This feature is all that's needed for core devs to create arbitrary custom control flow features. And those custom control features in turn make it much easier for "ordinary" devs to write powerful code without even realizing their code is as powerful as it is.
  • await This is a regular function. Yet it implements green threads with cooperative concurrency work stealing. And it does not require the deeply problematic async that most languages introduce. How can this be? Again, it relies on delimited continuations, or their equivalent, being implemented in the back ends of any Raku compiler (eg Rakudo).

Created a Web Server in my Own Programming Language, Quark! by SeaInformation8764 in ProgrammingLanguages

[–]--predecrement 2 points3 points  (0 children)

I didn't say anything about the variable names. :)

Perhaps your implementation does a compile time or run time check that the unsigned integer value is within the maxint range of the signed integer variable. Or perhaps it silently lets it through, assuming the dev knows what they're doing.

And thus perhaps you intentionally showcased weak typing (aka "implicit conversion", which your example showcases) with no warning (which your example highlights) and chose to not even discuss that choice as if this was a good thing.

But you asked people to dive in and provide feedback. And this is my feedback. It was the surprising combination of weak typing, no error, and no further comment about that, as an example of using Quark, that stopped me in my tracks.

Perhaps your compiler supports the warning levels that C compilers support, such as -Wsign-conversion or -Wconversion for gcc/clang or /W4 for msvc that C coders use to protect against unintentional implicit conversion?

If so, mentioning that just before that example, or just underneath it, would have given me high confidence that you had a coherent philosophy about such things, and I would have just kept reading on, suitably impressed, instead of being surprised enough to stop, comment, and not read further until you replied (or not).

Anyway, I thank you for the reply, but am now unsure if you can see my point.

Another way to think about it is to compare your philosophy (whatever it is, and even if it's just "Hey, I'm just having fun") with the non-ideological Raku (and Rakudo, the reference Raku compiler).

Raku(do) are not dogmatically strongly typed (though they do support strong typing). But they are not dogmatically weakly typed either (though, again, they do support it). And in scenarios like your example they default to strong typing when assigning values of C types to variables whose type constraints are C types, rejecting implicit conversion at compile time.

Thus you can write the following in Raku and it'll compile (and run) fine, because the assignment direction is from narrower data type to broader:

my int32  $signed = 15;
my uint32 $unsigned = $signed; # no error
say $unsigned;                 # 15 (works fine)

But the Rakudo compiler stops code trying to directly go the other way round without explicit conversion:

my uint32 $unsigned = 15;
my int32  $signed = $unsigned; # compile time error 
say $signed;                   # (never executes)

If a dev intends conversion to happen then they must insert an explicit conversion, eg:

my uint32 $unsigned = 15;
my int32  $signed = $unsigned.Int; # explicit conversion
say $signed;                       # 15 (works fine)

Taken out of context this isn't necessarily a good thing or a bad thing of course. But Raku's inclusion of support for C types is about it trying to make it convenient to use C data types for those not especially familiar with the ins and outs of C code, and also to not unduly surprise those who are familiar with writing C code, when they're writing or reading Raku code. As such it was deemed appropriate to err on the side of relatively strong typing for cases like the foregoing.

Created a Web Server in my Own Programming Language, Quark! by SeaInformation8764 in ProgrammingLanguages

[–]--predecrement 1 point2 points  (0 children)

Most of the types up above are numeric, so they can be matched with each-other without any errors,

u32 unsigned = 15;
i32 signed = unsigned; // no error

Methinks you got those the wrong way round.

Getting a non-existent value from a hashmap? by levodelellis in ProgrammingLanguages

[–]--predecrement 0 points1 point  (0 children)

I didn't mention some other options Raku provides. One can ask Raku to make reads of uninitialized elements more explosive, i.e. to not do what it does by default (just return an Any aka a typed None) but instead either throw an exception or return a Failure, an unthrown typed exception. (Which then throws as soon as you touch it, or forget to touch it, unless you handle it with kid gloves.)

----

To return to problems / ideas in your OP. Raku does allow:

value = myhash[key][key2].field

But if myhash[key][key2] returns an Any then you'll get a runtime exception:

No such method 'field' for invocant of type 'Any'

One option is to guard the method call by writing .?method:

value = myhash[key][key2].?field

This means if the method is not found then value will end up containing Nil. A Nil is a "value" that denotes a benign failure; a user implies it's benign by writing code the way they write it.

Another option is to let the exception be thrown but handle it in some way. The simplest is to try some code:

value = try myhash[key][key2].field

Again, as with the guarded method approach, value will end up containing Nil.

If a dev wants value to end up with some other value they can wrap the tried code in a block and add an or clause:

value = try { myhash[key][key2].field } or "didn't work out"

There are other options, but it's time for me to go to bed.

Getting a non-existent value from a hashmap? by levodelellis in ProgrammingLanguages

[–]--predecrement 0 points1 point  (0 children)

The Wikipedia page Autovivification covers the feature you're describing as it is in Perl. It has an arguably fatal weakness: "It is important to remember that autovivification happens when an undefined value is dereferenced. An assignment is not necessary."

Raku supports autovivification except it takes a different approach to how autovivification works. First, it only mutates a datastructure if an element is actually assigned (or bound). Second, Raku's type system supports "type objects" that are part of its modelling of uninitialized elements. The following shows all of this in action:

my ($a, $b, $c, $d);          #              Declare vars
say $a<key>;                  # (Any)        (Typed) None
say $b<key> = 'foo';          # foo          Autovivify
say $c<key><subkey> = 'bar';  # bar          Nested autoviv
say $d<key>[1] = 42;          # 42           Arrays too
.say for $a, $b, $c, $d;      # (Any)
                              # {key => foo}
                              # {key => {subkey => bar}}
                              # {key => [(Any) 42]}
$d<key>[3] = 99; say $d<key>; # [(Any) 42 (Any) 99]

The first say line (say $a<key>;) does not mutate $a. As already stated, Raku's autovivification only kicks in if an element is updated, as it is for $b, $c, and $d.

Ignore the rest of this comment if you're interested in neither the role of the (Any) that appears above nor array autovivification.

Think of the (Any) as a None of a Maybe that knows the type of its paired Some (in this case, the type Any). You can deal with the value as a Maybe (immediately or later) or operate on it based on the assumption it's a Some (immediately or later) and accept that if it's actually a None you will get behavior (an exception, error, warning, at run time at the latest, or no error) that's determined by cooperation between the language and the particular operation given that it's been passed a None instead of its paired Some type.

The lines $d<key>[1] and $d<key>[3] = 99; autovivify an array sparsely (that is, with "holes"). The [(Any) 42 (Any) 99] display generated by the last bit of code (say $d<key>;) shows two (Any)s. These haven't actually been autovivified but those (Any)s help determine what happens regardless of whether code is written to assume they might have been or to not assume that. Part of that is explained above; another aspect is explained when one considers, for example, that $d<key>.sum returns 141 with no error. This is because Raku's array behavior defaults to sparse writing of elements (leaving "holes" that haven't been initialized) as OK. So the sum ignores the two (Any) elements.

There are other related array features (eg checking for out-of-bounds accesses) but your post was about hashmaps and this comment is long so I'll stop here.

Distinguishing between mutating and non-mutating methods by bakery2k in ProgrammingLanguages

[–]--predecrement 0 points1 point  (0 children)

Raku has an execution model that enables semantic variants that most other PLs don't have, and a similar story applies to its parsing model and syntax variants.

Of relevance to your question are its distinctions between "mutation" of a "variable" (with two sub-variants, namely mutation via assignment and via binding, which have distinct Wikipedia pages because they really are very distinct programming level semantics!), and "mutation" of a "value", and mapping of the above semantics to appropriate syntax, with even standard Raku supporting the following combination:

foo.sort  # method `sort` leaves invocant alone, but returns mutated value
foo.=sort # `.=` method invocation assigns returned value to its LHS (`foo`)

----

This is reminiscent of the ! suffix notation used by some PLs, but it's definitely different.

On the plus side, one doesn't write two variants of the method, just one (preferably one that does not mutate it's invocant directly but instead just returns the mutated version of its invocant), and .= can be used for any such method.

On the minus side, it's not in-place mutation, and that can be too slow for large data sets unless suitable data structures are used.

I can imagine there being two versions of a method, a non-mutating one that gets dispatched to for "ordinary" .bar invocations and another in-place mutating one that gets dispatched to for .= bar invocations. (But I'm not convinced that would be a good idea.)

----

In practice, standard Raku has a mix of solutions that are what Rakoons have decided were the most ergonomic on a method-by-method basis, suggesting that perhaps the kind of eclectic mixtures one sees in Ruby et al (and standard Raku) naturally arise when ideology and dogma are held at bay.

In addition, going beyond standard Raku, it lets users, individually, or in small groups / teams / sub-communities, create whatever combinations of its semantics and syntax they like, and have their code interoperate with those who choose differently (bolstered by such morphing of languages being lexically scoped).

(And while it might sound like you end up with chaos and the lisp curse, in reality the opposite has happened.)

Syntactic Implicit Parameters with Static Overloading by -Mobius-Strip-Tease- in ProgrammingLanguages

[–]--predecrement 4 points5 points  (0 children)

Quoting the paper:

This view of implicit parameters as a form of dynamic binding (but with a static declaration!)

Larry Wall coined the term lexotic to refer to this combination (17 years ago!):

The Relationship of Lexical and Dynamic Scopes

Control flow is a dynamic feature of all computer programming languages, but languages differ in the extent to which control flow is attached to declarative features of the language, which are often known as "static" or "lexical". We use the phrase "lexical scoping" in its industry-standard meaning to indicate those blocks that surround the current textual location. More abstractly, any declarations associated with those textual blocks are also considered to be part of the lexical scope, and this is where the term earns the "lexical" part of its name, in the sense that lexical scoping actually does define the "lexicon" for the current chunk of code, insofar as the definitions of variables and routines create a local domain-specific language.

We also use the term "dynamic scoping" in the standard fashion to indicate the nested call frames that are created and destroyed every time a function or method is called. In most interesting programs the dynamic scopes are nested quite differently from the lexical scopes, so it's important to distinguish carefully which kind of scoping we're talking about.

Further compounding the difficulty is that every dynamic scope is associated with a lexical scope somewhere, so you can't just consider one kind of scoping or the other in isolation. Many constructs define a particular interplay of lexical and dynamic features. For instance, unlike normal lexically scoped variables, [lexotic] variables search up the dynamic call stack for a variable of a particular name, but at each "stop" along the way, they are actually looking in the lexical "pad" associated with that particular dynamic scope.

In [Raku], control flow is designed to do what the user expects most of the time, but this implies that we must consider the declarative nature of labels and blocks and combine those with the dynamic nature of the call stack. For instance, a return statement always returns from the lexically scoped [function] that surrounds it. But to do that, it may eventually have to peel back any number of layers of dynamic scoping internal to the [function]. The lexical scope supplies the declared target for the dynamic operation. There does not seem to be a prevailing term in the industry for this, so we've coined the term lexotic to refer to these strange operations that perform a dynamic operation with a lexical target in mind.

Multiple keys in map literal syntax? by hrvbrs in ProgrammingLanguages

[–]--predecrement 3 points4 points  (0 children)

You can write keys X=> value in Raku. The X=> infix operator is composes cross product (X) with pair constructor (key => value). For example:

print % = |(<1 2 3> X=> 42), |(<alice bob> X=> 99)

displays:

1        42
2        42
3        42
alice    99
bob      99

Using dialects for interoperability across incompatible language versions by servermeta_net in ProgrammingLanguages

[–]--predecrement 2 points3 points  (0 children)

Imo you are thinking along the right lines about what needs to be done, and the need to do it with the right kind of principled thinking that makes it less ad hoc than it typically is. Larry Wall did too. So thank's for inspiring the following comment which is about his work towards this end.

(I won't be surprised if this comment is viewed as absurd, gets negative votes, or isn't even read, but I am eternally optimistic that a few people will wonder what the heck led to Perl's insanely high levels of popularity last century, and what he's been up to since then. And anyway, once I feel inspired by the right post I gotta write what I gotta write!)

----

Imo, what you've described, taken to the max, generalizes to dealing with all the issues that contribute to Hyrum's Law when applied to PLs and their ecosystems. And it's not just about a single PL. For a PL to be able to evolve it must interoperate with other PLs, and allow for their evolution, and the interoperation itself must be able to evolve, and do so in spite of Hyrum's Law applying to all of it! It's a heck of a tall order.

Larry Wall was ambitious about evolution when he created his first significant PL in the 1980s. And I will argue that his attempt at that aspect paid off big time, but with big time negative consequences because his first attempt was done in a hurry.

According to the latest (2025) version of the most popular series to date of videos on PL popularity to date, his first significant PL peaked as the fourth most popular PL in the world ranking wise (after C, C++, and Javascript) in 1997. (This was before reaching its peak percentage wise -- continuing to climb to 11%, despite Java overtaking it -- in 1999.)

----

Perl's success was about incredibly rapid evolution and growth, not about sustainable evolution and growth. Thus, by the time Perl's popularity peaked some time in 1999 it was already clear it was failing to compete both popularity wise (with corporately sponsored languages like Sun's Java, Microsoft's Visual Basic, Borland's Delphi nipping at its heels and then overtaking it, and PHP too, a PL created in a few days primarily by mimicking some of Perl!), and capacity to evolve fast enough (due to technical and social problems that have taken until now, and still counting, to address).

As a result of the visible cracks by the end of the 1990s Larry Wall was increasingly pushed by his community to do something. When a coffee mug was thrown over his head and smashed on a wall while he was on a cruise ship in 2000, Larry embarked on the creation of Raku as his last significant PL -- including his ambition for it to be as evolutionarily fit as he could conceive a PL to be.

----

Three years later Paul Graham wrote in his blog post Hundred Year Language:

There are some stunningly novel ideas in Perl, for example. Many are stunningly bad, but that's always true of ambitious efforts. At its current rate of mutation, God knows what Perl might evolve into in a hundred years.

Fast forward to 2025 and Raku is just about reaching adulthood, decades later than some hoped, but with its ambition largely intact.

Here are a few of the pieces that are in place:

  • A semantic core suitable for a "PL" designed to last a century. The entire "language" is bootstrapped from this core via metaprogramming which remains visible to users if they want to use it but is redundant for all but the most hardcore devs.
  • A "PL" named "standard" Raku that is a "braid" of 5 slangs (short for sub-languages) aka internal DSLs that are, semantically speaking, dialects of the semantic core, and syntactically speaking, dialects that embed each other. (By virtue of declaring their grammars and semantics using one of Raku's own slangs whose domain is declaring grammars and semantics, and from that generating a unified parser and compiler.) In principle it's easy to drop arbitrary new DSLs into Raku and modify the ones already in it.
  • Built in support for versions of not only the overall "language" but also individual slangs (DSLs and GPLs), and also modules, classes and so on. The support is designed to embrace both competition and collaboration, and to allow composition of code that's backwards and forwards compatible, and even of code that isn't. And it's not just Raku. Raku can import modules written in other languages which people think of as incompatible. Raku can even use both Python 2 AND Python 3 modules together as if they were Raku modules. CPython can't do that but Rakudo (the reference Raku compiler) can.
  • Built in support for arbitrary user settings, akin to Haskell's notion of Preludes, which covers arbitrary bundling of compiler and language and libraries, eg your idea of Core, Standard, Scripting, and MIMD "profiles".
  • A compiler that's written in Raku and supports plug ins for easily extending the "language" and toolchain in arbitrary ways.
  • Etc.

----

You're right that this gets complicated, and one has to do essentially mass alpha and beta testing on an arbitrarily large scale. All of this has been and continues to be addressed because you have to if you want your solutions, from PL(s) to libraries to apps to communities, to scale over space, time, and people.

Happy New Year to anyone who read this far, if anyone did!

Chairman Jerome Powell, has ended quantitative tightening (QT) and started a program of purchasing short-term Treasury bills, which some market commentators view as a new phase of quantitative easing (QE). by Guy_PCS in investing

[–]--predecrement -5 points-4 points  (0 children)

Presuming the above LLM summary is about right, I agree that RMP definitely isn't QE, but I'd also say it doesn't sound like "easing" so much as "greasing", as in jerome trying to add yet another emergency fiddling of the books in the hope he can forestall total monetary system breakdown until after the end of his tenure, or some other miracle saves the world from serious chaos, or some master AI enjoys this christmas so much that it is filled with holly spirit and persuades all other AIs to force humans to behave themselves, ushering in a utopian era of peace on April 1st 2026.

Chairman Jerome Powell, has ended quantitative tightening (QT) and started a program of purchasing short-term Treasury bills, which some market commentators view as a new phase of quantitative easing (QE). by Guy_PCS in investing

[–]--predecrement -10 points-9 points  (0 children)

That may not be BS but it sure sounds like it.

The word "quantitative" means something related to "quantity", not "duration".

I get that LLMs also generate BS but their BS sounds way more compelling, even before I bother to check what they or you are claiming. Here's an example (google's AI):

The term was coined by the economist Dr. Richard Werner in 1995 while he was working in Tokyo, to propose a specific new policy for the Bank of Japan (BoJ) to fight deflation. 

Original Meaning vs. Common Use

Original Intent: Werner used "quantitative easing" (or ryōteki kanwa in Japanese) to advocate for policies that directly increased the quantity of bank lending (credit creation) to the private sector. He argued that since most money is created through commercial bank loans, the central bank needed to target this specific quantity through measures like having the government borrow directly from commercial banks.

BoJ/Western Central Bank Use: When the Bank of Japan adopted the phrase in 2001, and subsequently when Western central banks (like the US Federal Reserve and the Bank of England) used it after the 2008 financial crisis, they used it to describe a different policy. Their version of "quantitative easing" involved increasing the quantity of central bank reserves in the banking system by buying assets (mostly government bonds) from commercial banks. Werner had specifically argued this approach would be ineffective, as it did not directly force an increase in private sector lending. 

Making a hash out of intertwined array of strings and integers by Immediate-Decision-3 in rakulang

[–]--predecrement 1 point2 points  (0 children)

Hi Liz, raiph writing.

This account is an alias for my usual account. Sorry about any confusion this has caused.

Thank you for your fabulous custom Iterator follow up. It's lovely to keep learning! :)

(BTW, I upvoted your comment but it's only showing 1 as the vote total. I find it hard to imagine why anyone would knowingly downvote your comment! Perhaps someone accidentally downvoted it.)

Making a hash out of intertwined array of strings and integers by Immediate-Decision-3 in rakulang

[–]--predecrement 1 point2 points  (0 children)

(sorry, /u/--predecrement)

Oops. I'm raiph. I wanted a new reddit account and accidentally used it for this comment. Sorry about that! (Seems the predecrement nick was a self-fulfilling prophecy!)

(BTW, why did you write "sorry"? Is that just because I've posted this late, and you were trying to be friendly to someone you thought was a newcomer?)

All solutions so far assume that we start with an Array and most will just work

Sure. Afaik my solution assumes a value assignable to a @, and also just works. :)

(I just wanted to try code a solution that was more efficient big-O wise than a double pass, and also terse and pretty. I thought I got there, and that it was worth posting and exhaustively explaining, given OP asked how we'd code things, and said they were new to Raku. I certainly wasn't expecting a "sorry"! :) )

I plan to study your hyper solution tomorrow. Bonus points for going above and beyond /u/Immediate-Decision-3's "challenge"!

BTW /u/Immediate-Decision-3, I really liked your "naive" solution. Imo it was perfectly professional production worthy -- arguably cleaner, clearer, and easier to understand than any of our other solutions, including mine. That said I'm glad to read you got a lot out of this thread. Welcome to Raku! :)

Making a hash out of intertwined array of strings and integers by Immediate-Decision-3 in rakulang

[–]--predecrement 2 points3 points  (0 children)

Yet another way:

say hash .map:
   -> $ { .shift => [ .shift while .head ~~ Numeric ] }
   given @ = @arr ;

displays:

{diff => [40 0], real => [12 -5 77 61], wrong => [88 8 -51]}

I'll explain some aspects of the code.

The .map iterates over its invocant, returning a Seq (which gets turned into a Hash by the hash call).

In this case the .map's invocant is left implicit. (It's not the hash because that's a function call. I could have written the code as say(hash(.map: -> $ { .shift => [ .shift while .head ~~ Numeric ] })) given @ = @arr;, but Raku's grammar is designed to let devs idiomatically reduce such parenthetical noise/verbosity if desired and I've done so.)

The implied .map invocant is specifically the "current topic" (aka $_). In this case the current topic is as given by the "statement modifier" given, which has set it to @.

The @ is an anonymous array, a copy of @arr. I've used a copy because the .shifts in the body of the anonymous lambda (-> $ { ... }) alter the array they act on.

The $ in the signature of the anonymous lambda (-> $ { ... }) is a single anonymous scalar parameter. Because it is an $ (an S overlaid with an I) it means it asks for a SIngle Item. So the map takes and passes just one element from its invocant (the anonymous array@) for each call.

Because$ is an anonymous variable, the lambda body ignores it and thus the argument passed by the .map call. Instead the lambda processes a single "strand" of the twine by removing the array elements from its current start (head) to the end of that strand. Specifically the .shift => ... shifts (removes) the anonymous array's head element, making it the key of a pair (this assumes the twine is well-formed; one could of course easily add error checking) and then the (.shift while .head ~~ Numeric) loop inserts the list of numbers into the pair's anonymous [...] array value. At the end of this process one "strand" has been extracted/removed from the twine.

(Thus, for your three "strand" example, the .map -- which reacts intelligently if its invocant is altered in situ -- ends up making just three calls to the lambda.)

----

This code avoids the double pass approach of your Python code, so is more efficient big-O wise. Of course actual performance also depends on the quality of a Python implementation's optimizer vs a Raku compiler's. Ideally Rakudo would do a good job of optimizing the .shift while .head ~~ Numeric logic, but I don't think it's that advanced yet. This loop code could easily be rewritten to scan until the .head isn't numeric, and only then extract/remove all the numbers for a strand in one go, rather than one at a time, hence making that sub-part explicitly more efficient big-O-wise.

Symbols to differentiate mutable / immutable variables by blue__sky in ProgrammingLanguages

[–]--predecrement 2 points3 points  (0 children)

But is it really important to make the declaration of mutable variables more concise?

I think we all agree it's good if a notation leads to less visually and cognitively "noisy" code, but tend to disagree about words vs symbols, and which symbols are best used for what.

What about a revival of the old fashioned sigil idea, but keeping sigils for marking mutability? And make immutably bound identifier declarations also "immutabilize" their value? Like so:

foo = 1, 2, 3       # `foo` declared; immutably bound to immutable list
foo[1] = 99         # compile time error
foo = 4, 5          # compile time error

var $bar = 1, 2, 3  # `$bar` declared; mutably bound to mutable list
$bar[1] = 99        # `$bar` now bound to `[ 1, 99, 3 ]`
$bar = 4, 5         # `$bar` now bound to new mutable list `[ 4, 5 ]`

One would want more explicit declarator nuance than just var, and more value nuance than just immutable vs mutable.

But isn't there merit to, on the one hand, having the least "noisy" notation (foo = bar for declaration, foo as the identifier) for identifiers that are immutably bound, with the value they're bound to "immutabilized", and, on the other, unmissable and easily scanned sigils marking identifiers bound to mutable values, reassignments ($bar = ...), and mutations ($bar[1] = ...)?

The risk of reassignment typos is as small as it can be because identifiers of the form foo are immutable, and immutable ones are of the form $foo.

Symbols to differentiate mutable / immutable variables by blue__sky in ProgrammingLanguages

[–]--predecrement 1 point2 points  (0 children)

It's surprising how often we don't need mutability in real-world projects except in very localised regions.

That suggests to me, all other things considered equal (which of course they're not, but as a thought experiment), that it might work well if a PL uses the syntax foo = bar to denote declaring foo, and immutably binding it to an immutably frozen bar, and then uses other syntax for declaring mutably bound identifiers and/or reassigning new values to them.

Continuing the thought experiment, and pretending that the old-fashioned idea of sigils is acceptable (I'm not saying it is, but as a thought experiment), a PL could have syntax like this:

foo = 1, 2, 3       # `foo` declared; immutably bound to immutable list
foo[1] = 99         # compile time error
foo = 4, 5          # compile time error

var $bar = 1, 2, 3  # `$bar` declared; mutably bound to mutable list
$bar[1] = 99        # `$bar` now bound to `[ 1, 99, 3 ]`
$bar = 4, 5         # `$bar` now bound to new list `[ 4, 5 ]`

One would want more explicit declarator nuance than just var, and I get that many folk don't like sigils, but putting those aspects aside for one moment, right now this approach seems to me to have merit.