Not the person you replied to, but I thought the same thing. Perl was my first as well, and it certainly shaped the way I think about coding. It made Python feel too rigid and Ruby feel familiar. There's something to be said for the restrictions of an environment when you're learning how to operate in a domain that seems to shape future thinking.
I'm sure there are people who started in a language and later found something that made more sense. I'm just reflecting on what I've found in my experience.
> It made Python feel too rigid and Ruby feel familiar.
That's so funny to me; I like Python, and dislike Perl & Ruby. Something about Ruby rubs me the wrong way - I could name a few things that I think are _objectively_ bad decisions in the language's design, but it's mostly about an aesthetic preference that's just a matter of taste.
> There's something to be said for the restrictions of an environment when you're learning how to operate in a domain that seems to shape future thinking.
When at University the academic running the programming language course was adamant the Sapir–Whorf hypothesis applied to programming language. ie language influences the way you think.
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.
I'm sure there are people who started in a language and later found something that made more sense. I'm just reflecting on what I've found in my experience.