My first contact with tacit programming came from J and left amazed by the potential of it. Looking at others languages implementing it seems like kind of w/hacky. It goes far beyond using a pipe-ish operators. I remember discovering the concept of hooks & forks and never seen it in other languages. Generalized composition techniques and using being able to create a function by chaining/composing a train of functions are one of the aspect connected to tacit programming that I really like and may (or may not) enhance self-documented code, IMHO.
I wish to see more flawless integration of tacit programming concepts in the future. Haskell’s pointfree is a bit disappointing due to heavy use of the dot . everywhere.
My first contact with tacit programming came from J and left amazed by the potential of it.
I had a similar first contact experience – except that it was Dyalog APL in my case, so the experience came with a lot more non-ASCII characters.
I remember discovering the concept of hooks & forks and never seen it in other languages.
I’m not sure that any non-array language will ever be able to quite match J/APL, etc. But Raku comes far closer than I ever thought I’d see. For example, the docs you link say:
3 hours and 15 minutes is 3.25 hours. A verb hr, such that (3 hr 15) is 3.25, can be written as a hook. We want x hr y to be x + (y%60) and so the hook is:
hr =: + (%&60)
3 hr 15
3.25
You can do essentially the same thing in Raku, with only a few more characters:
my &hr = * + * / 60;
3 [&hr] 15
3.25
(Admittedly, this isn’t idiomatic in Raku. In particular, I’m not sure I’ve ever seen an infix function call in real Raku code. But still!)
I just began to look at Raku and will be learning a bit of it during the Christmas holidays, it really seems a versatile language and a funny one. I realized a long time ago that non-array language can’t provide the same level of expressiveness and compactness of J/APL for tacit programming.
What characterize idiomatic code in Raku when you have so many ways to do it?
And looking for your example, if I want to do something similar but with the average function. In J,
avg =: +/%#
Using the Whatever star seems to imply different positional arguments. Using pointy block,
my &avg = -> @a { @a.sum / @a.elems}
and simply using a block with the topical variable :
my &avg = {$_.sum / $_.elems}
Can we go further than that or take another approach to express it?
I will grant that I don’t know Raku aside from the syntax the article introduced, but the “before” code, to my eye, seems immensely more straightforward and easy to reason about.
I would be pretty scared of breaking the “after” code if I had to add some new bit of logic to it. And I think I know why: if you want to know the shape of the data at any particular step of the pipeline, you kind of have to retrace all the steps from the beginning and run the sequence of transformations in your head. With intermediate variables, you only need to scan back as far as the assignments of the most recent set of variables, whose names should give you some hint about what’s in them. (And in a strongly-typed language, their types will tell you a lot as well.) In essence, with intermediate variables it’s easier to reason about an individual section of the implementation rather than the entire thing.
The idea that you should refrain from naming some objects in your calculation because they distract from its main thrust only makes sense if you know a fair bit about the context the next programmer to read your code will have. It’s better to be explicit about anything another plausible reader of the code might struggle to infer from the implicit version.
Ok, after more diversions and tangents than I can possibly count, and having basically had to hold so much state in my head that I’ve had to page to disk multiple times, I’ve reached the end of the article, and what have I learned?
I am now considerably less confident of the point of pointsfree than I was at the start… which is impressive, because I was pretty confident there was a point.
I will fire anyone that suggests using Raku in production. Which is impressive, because I’m usually an advocate for polyglot.
And I’m really not being flippant or trying to troll here. I truly came in wanting to gain a new perspective on pointsfree and tacit programming… I leave thinking that the line from Spiderman should be “with great power comes the responsibility not to use it”.
I’m sorry that you feel that way; it sounds like neither my coding style nor my writing style are a good fit for your tastes.
One point where I agree with you, however: I don’t think Raku is a great fit for polyglot programming, at least in the way I assume you mean it. Raku has good FFI and can easily call out to anything with a C ABI, so in that sense it’s good at polyglot. But if by “polyglot” you mean “an environment where different parts of the codebase are written in different languages but are understandable to the entire team (including people specializing in other languages)”, well, then Raku isn’t a great fit. As the 101 example shows, you can write Raku code that is easily understood by people without much Raku experience but, imo, that involves giving up most of what makes Raku powerful. At that point, you might as well just use Python/Ruby/JavaScript – and, since everyone already knows those, you can support a polyglot environment.
(As I guess is clear, I believe than the advantages of deep mastery of a set of idioms outweigh the benefits of a polyglot setup, but I’m happy to agree to disagree on that point)
I don’t understand the argument. The line of reasoning is that pointfree programming removes some of the burden of holding state in you head while you read the code, by only naming things that need attention. The things that are not named are defined close to where they are used, so it is not hard to figure out what they are?
But there is as much state as there was before applying the pointfree principles. The same operations are done, you still have to hold the same intermediate states of the data in your head. The choice seems to be: either give the things an easy handle to to grasp them (a name), or don’t and define them close enough that you can look it up quickly.
First, I think those two are not mutually exclusive. You can define things close to where they are used and still give them names. Second, when juggling different pieces of the puzzle in your head it can be very convenient to have that name, even if it is only used in the scope of a few lines of code. Instead of having a vague concept like “those strings that indicate a player, I think” something like “names” is much clearer to me.
As always, it depends on the situation and your mileage may vary, but I am not inclined to use more of these pointfree idioms after reading the article.
I don’t understand the argument. The line of reasoning is that pointfree programming removes some of the burden of holding state in you head while you read the code, by only naming things that need attention. The things that are not named are defined close to where they are used, so it is not hard to figure out what they are?
But there is as much state as there was before applying the pointfree principles.
Here’s the main point that, I believe, you are missing: when something has a name, the meaning of that name can change; when it doesn’t, there is nothing to change (and therefore less state). For a shorter (hopefully clearer?) example, compare the following bits of pseudocode
You are correct that “the same operations are done” in both cases. But my claim is that names and sorted_names represent state that doesn’t exist in the second version: those names could be rebound or (depending on the language) the values they point to could be changed.
Of course, in an example this short, it’s easy to see that nothing changed sorted_names between where it was defined and where it was used. But that brings me to my other response to your comment. You made the point that
You can define things close to where they are used and still give them names.
I agree, you can. But, once you’ve given something a name, you can also refer to it using that name anywhere else in the same scope. You could adopt a rule that you shouldn’t do so, but I’d much rather adopt a style that makes it impossible for me to create that type of issue than to rely on programmer discipline (in the same way I often want the protection of a type system).
Ah, thanks for the explanation. I think I get the point now and I agree that the second version is much better to understand and safer.
However, I still think that there is some mental burden that is being swept under the carpet. Your second version can be understood in a glance by all programmers, except the most junior. That is, depending on the situation, not the case with larger examples:
Remembering what it exactly is that we are doing without names can become very hard very quickly. You step through all the intermediates states in your head and if you flinch once (“wait, what was the first row again?”) then you must start all over from the top, because there are no hooks like a name where you can pick up the trail half way. So I’d say this only works for trivial examples.
There is, of course, another easy solution to this problem that achieves the same goals (isolation, clarity by only emphasizing what matters) for programmers of all skill levels and that is: create a new scope. Put it in a function and you’re done:
print( formatWithWeirdLogic( "ALICE BOB CAROL" ) )
My first contact with tacit programming came from J and left amazed by the potential of it. Looking at others languages implementing it seems like kind of w/hacky. It goes far beyond using a pipe-ish operators. I remember discovering the concept of hooks & forks and never seen it in other languages. Generalized composition techniques and using being able to create a function by chaining/composing a train of functions are one of the aspect connected to tacit programming that I really like and may (or may not) enhance self-documented code, IMHO.
I wish to see more flawless integration of tacit programming concepts in the future. Haskell’s pointfree is a bit disappointing due to heavy use of the dot
.
everywhere.I had a similar first contact experience – except that it was Dyalog APL in my case, so the experience came with a lot more non-ASCII characters.
I’m not sure that any non-array language will ever be able to quite match J/APL, etc. But Raku comes far closer than I ever thought I’d see. For example, the docs you link say:
You can do essentially the same thing in Raku, with only a few more characters:
(Admittedly, this isn’t idiomatic in Raku. In particular, I’m not sure I’ve ever seen an infix function call in real Raku code. But still!)
I just began to look at Raku and will be learning a bit of it during the Christmas holidays, it really seems a versatile language and a funny one. I realized a long time ago that non-array language can’t provide the same level of expressiveness and compactness of J/APL for tacit programming.
What characterize idiomatic code in Raku when you have so many ways to do it?
And looking for your example, if I want to do something similar but with the average function. In J,
avg =: +/%#
Using the Whatever star seems to imply different positional arguments. Using pointy block,
my &avg = -> @a { @a.sum / @a.elems}
and simply using a block with the topical variable :
my &avg = {$_.sum / $_.elems}
Can we go further than that or take another approach to express it?
There’s also the placeholder parameter route via the
^
twigil:I will grant that I don’t know Raku aside from the syntax the article introduced, but the “before” code, to my eye, seems immensely more straightforward and easy to reason about.
I would be pretty scared of breaking the “after” code if I had to add some new bit of logic to it. And I think I know why: if you want to know the shape of the data at any particular step of the pipeline, you kind of have to retrace all the steps from the beginning and run the sequence of transformations in your head. With intermediate variables, you only need to scan back as far as the assignments of the most recent set of variables, whose names should give you some hint about what’s in them. (And in a strongly-typed language, their types will tell you a lot as well.) In essence, with intermediate variables it’s easier to reason about an individual section of the implementation rather than the entire thing.
The idea that you should refrain from naming some objects in your calculation because they distract from its main thrust only makes sense if you know a fair bit about the context the next programmer to read your code will have. It’s better to be explicit about anything another plausible reader of the code might struggle to infer from the implicit version.
Ok, after more diversions and tangents than I can possibly count, and having basically had to hold so much state in my head that I’ve had to page to disk multiple times, I’ve reached the end of the article, and what have I learned?
And I’m really not being flippant or trying to troll here. I truly came in wanting to gain a new perspective on pointsfree and tacit programming… I leave thinking that the line from Spiderman should be “with great power comes the responsibility not to use it”.
I’m sorry that you feel that way; it sounds like neither my coding style nor my writing style are a good fit for your tastes.
One point where I agree with you, however: I don’t think Raku is a great fit for polyglot programming, at least in the way I assume you mean it. Raku has good FFI and can easily call out to anything with a C ABI, so in that sense it’s good at polyglot. But if by “polyglot” you mean “an environment where different parts of the codebase are written in different languages but are understandable to the entire team (including people specializing in other languages)”, well, then Raku isn’t a great fit. As the 101 example shows, you can write Raku code that is easily understood by people without much Raku experience but, imo, that involves giving up most of what makes Raku powerful. At that point, you might as well just use Python/Ruby/JavaScript – and, since everyone already knows those, you can support a polyglot environment.
(As I guess is clear, I believe than the advantages of deep mastery of a set of idioms outweigh the benefits of a polyglot setup, but I’m happy to agree to disagree on that point)
I don’t understand the argument. The line of reasoning is that pointfree programming removes some of the burden of holding state in you head while you read the code, by only naming things that need attention. The things that are not named are defined close to where they are used, so it is not hard to figure out what they are?
But there is as much state as there was before applying the pointfree principles. The same operations are done, you still have to hold the same intermediate states of the data in your head. The choice seems to be: either give the things an easy handle to to grasp them (a name), or don’t and define them close enough that you can look it up quickly.
First, I think those two are not mutually exclusive. You can define things close to where they are used and still give them names. Second, when juggling different pieces of the puzzle in your head it can be very convenient to have that name, even if it is only used in the scope of a few lines of code. Instead of having a vague concept like “those strings that indicate a player, I think” something like “names” is much clearer to me.
As always, it depends on the situation and your mileage may vary, but I am not inclined to use more of these pointfree idioms after reading the article.
Here’s the main point that, I believe, you are missing: when something has a name, the meaning of that name can change; when it doesn’t, there is nothing to change (and therefore less state). For a shorter (hopefully clearer?) example, compare the following bits of pseudocode
versus
You are correct that “the same operations are done” in both cases. But my claim is that
names
andsorted_names
represent state that doesn’t exist in the second version: those names could be rebound or (depending on the language) the values they point to could be changed.Of course, in an example this short, it’s easy to see that nothing changed
sorted_names
between where it was defined and where it was used. But that brings me to my other response to your comment. You made the point thatI agree, you can. But, once you’ve given something a name, you can also refer to it using that name anywhere else in the same scope. You could adopt a rule that you shouldn’t do so, but I’d much rather adopt a style that makes it impossible for me to create that type of issue than to rely on programmer discipline (in the same way I often want the protection of a type system).
Ah, thanks for the explanation. I think I get the point now and I agree that the second version is much better to understand and safer.
However, I still think that there is some mental burden that is being swept under the carpet. Your second version can be understood in a glance by all programmers, except the most junior. That is, depending on the situation, not the case with larger examples:
Remembering what it exactly is that we are doing without names can become very hard very quickly. You step through all the intermediates states in your head and if you flinch once (“wait, what was the first row again?”) then you must start all over from the top, because there are no hooks like a name where you can pick up the trail half way. So I’d say this only works for trivial examples.
There is, of course, another easy solution to this problem that achieves the same goals (isolation, clarity by only emphasizing what matters) for programmers of all skill levels and that is: create a new scope. Put it in a function and you’re done: