I like to rate programming language's features not by how much I use them when I'm in the given language, or how good they make me feel, but by how much I miss them when I'm in a different language, once I'm fluent in that language and writing in the native idiom. (This is important. If you're still trying to write X in Y, yes, you'll miss the features from X, but that's not a useful data point.)
By this metric, rather a lot of features turn out to be less important than they may seem at first. Many things are a zero on this scale that I think might surprise people still on their second or third language. From this perspective you start judging not whether a language has this or that exact feature that is a solution to a problem that you are used to, but whether it has a solution at all, and how good it is on its own terms.
So while sigils have a lot of company in this, they are also a flat zero for me on this scale. Never ever missed them. I did a decade+ of Perl as my main language, so it's not for lack of exposure.
(As an example of something that does pass this test: Closures. Hard to use anything lacking them, though as this seems to be a popular opinion nowadays, almost everything has them. But I'm old enough to remember them being a controversial feature. Also, at this point, static types. Despite my decades of dynamic typed languages, I hate going back to dynamic languages anymore. YMMV.)
> So while sigils have a lot of company in this, they are also a flat zero for me on this scale. Never ever missed them. I did a decade+ of Perl as my main language, so it's not for lack of exposure.
I tend to miss one specific sigil (or pair of sigils): the @ and @@ sigils in Ruby, that mean "instance variable" and "class variable" respectively. Having identifier shadowing between stack-locals, and what Java would call "members" and "statics", be literally impossible, is just so nice. Especially when you get it "for free" in terms of verbosity, rather than needing to type `self.class.` or something.
I also really quite interned-string-literal : sigils in Ruby/Elixir — though I'd be equally fine with the Prolog/Erlang approach of barewords being symbols and identifiers needing to be capitalized. As long as there's some concise syntax for interned strings, especially in the context of dictionary keys. Because otherwise people just won't use them, even when they're there in the language. (See: Java, Python, ECMA6.)
Speaking of Elixir, the "universal sigil" ~ is kind of amazing. Define a macro sigil_h/2, and you can suddenly write ~h/foo/bar (or ~h[foo]bar, or whatever other delimiter works to best avoid the need for escaping), and foo and bar will be passed to sigil_h/2 as un-evaluated AST nodes to do with as you please. The language gives you ~w by default (which works like Ruby %w); but more interestingly, Regex literals in Elixir are just sigil_r.
Thanks for putting into words something I've started to feel over time, but never conceptualized clearly.
I agree that closures pass the test - and I too remember when they weren't popular. I also remember what I did before learning about the very idea of first-class functions and closures: I simulated them with some ad-hoc means (like function pointers in C/C++, or passing strings to be eval()-ed in PHP, etc.).
This, I think, is an useful heuristic: the things likely to pass your test are the ones which people who don't have and don't know about them still end up approximating anyway - meaning those things are a natural solutions to some common problems.
I can think of couple other things that pass your test:
- Functions in general. It's the basic organizational primitive in code; working without them is Not Fun.
- Lisp-style macros. There are many problems that would be best solved with some surgical code generation, and having that option built-in into the language makes all the difference. Most languages don't have this type of macros - but that doesn't mean they aren't needed. Having done enough Lisp macrology, I saw that in those other languages I've always been coping. Missing them without knowing what they are.
Hell, look no further than webdev - these days, major frameworks like React, and every other minor library, and even the language evolution itself, all depend on running an external macro processor / code generation tool as part of your build pipeline.
Thanks for putting into words something that was on the edge of my mind, but never quite graspable.
Two more examples (for me?) of features that I find you really miss in a language even if you’re fluent in the local idioms: First-class functions and pattern matching.
Passing functions as values is so nice and afaik most modern languages have that feature nowadays. But I remember when it used to blow people’s minds.
Pattern matching is something I’ve missed ever since having it in Haskell. Such an elegant solution to a problem that you have just often enough that the typical native approach feels clunky.
Perl's regex handling. That's the one I miss in whatever other language I program in.
To be able to match AND get the matched substrings out in one line, makes this really succinct.
I switched from Perl to Python around 12 years ago. I do think the sigils make code a little faster to comprehend. A bare word in python can be anything, wheres a variable will always start with a '$' or '@'.
It's not a huge win, but I do think it's better than nothing.
As for missing things, I do miss Perl a lot. I missed the curly braces when I first started and whitespace didn't feel right. Than after maybe 6 months I had to go back and do some Perl. Moved some blocks of code around, then got the dreaded missing brace problem. I realized that was something that I never got in Python and am a fan of whitespace since then.
I like this approach - We use python for our backend and the things I miss the most from other languages are the protected/public/private keywords (Java), single-line `if condition: return` statements (js, ruby, etc.), and npm/yarn/package.json (js). I miss types too but it feels unfair to complain about that with Python.
> modern IDEs and editors give us all the type information we could want, and these tools made sigils obsolete.
I like to rate a programming language by how dependent the language is on some bloated IDE ("editor"). If I need an Eclipse or a Pycharm just to edit a file, something has gone wrong syntactically and systemically.
Sigils are semantic information about the code. Sigils do not reduce readability, they increase expressivity and comprehensibility. It isn't the characters themselves that are the problem -- we see the same notations for different purposes entering Python and DSLs such as Pandas.
I agree with this and in most languages I don’t miss sigils but one language I do wish supported them is plpgsql.
The reason is that column names and function arguments overlap a lot, which can cause ambiguities when performing updates or selects. To become productive at plpgsql it’s a problem that you have to solve.
There are several approaches but the one I settled on is just to prefix all formal parameters with underscores.
The wish I have with plpgsql is that I could use $ instead since underscore is already heavily used as a word separator.
> Despite my decades of dynamic typed languages, I hate going back to dynamic languages anymore. YMMV.
Mine does vary - while static typing is helpful, it still (even with more advanced type systems) leads to boilerplate code that I dislike writing. In a compiler written in OCaml that I worked on for a bit, there were hundreds of lines of code dedicated to just stringifying variants. It could have been generated by a syntax transform (the newer tools for this are actually quite good), but that's another dependency and another cognitive overhead. In Kotlin, lack of structural types means that the rabid "clean architecture" fans create 3 classes for each piece of data, with the same 10 fields (names and types), and methods to convert between those classes - it requires 10x as much code for very little gain. Lack of refinement types makes the type systems mostly unable to encode anything relating to the number values, other than min/max values for a given type. There's reflection in Kotlin (not in OCaml though) that you can use, but then we're back to everything being an Object/Any and having runtime downcasts everywhere.
I think gradual type systems are a good compromise, for now at least. I'd prefer Typed Racket approach of clearly delineating typed and untyped code while generating dynamic contracts based on static types when a value crosses the boundary. Unfortunately, that's not going to work for existing languages, so the next best thing is something like TypeScript or mypy.
Of course, convenient, hygienic, Turing-complete not by accident, compile time execution and macros would, to some extent, alleviate the problems a simplistic type systems cause. A good example is Haxe, Nim, Rust, Scala 3, etc. Without such features, though, I'm not willing to part with runtime reflection and metaprogramming facilities provided by dynamic languages - the alternative is a lot more lines of code that need to be written (or generated), and I don't like that.
---
More to the topic: logic variables. The `amb` operator from Scheme, for example, or what Mozart/Oz has, or Logtalk, or Prolog of course. They're powerful, incredibly succinct way of constraints solving without writing a solver (just state the problem declaratively and done - as close to magic as it gets). No popular language offers an internal logic DSL, although there are some external DSLs out there.
Also, coroutines. No more manual trampolining, no need for nested callbacks, the state of execution can be saved and resumed later mostly transparently. Lua has them built-in, Kotlin implements CPS transform in the compiler. Nowadays almost all popular languages provide them, mostly exposed as async/await primitives. Scheme and Smalltalk can implement them natively inside the language and did so for ages; it's nice to see mainstream languages catch up.
REPLs. Not a language feature per se, but an implementation decision that has a lot of impact on productivity. It's relatively commonplace now - even Java has jshell - but most of the REPLs are pretty bad at executing "in context" of a project or module. Racket, Clojure, Common Lisp, Erlang, Elixir are gold standards, still unmatched, but you can get pretty far with Jupyter Notebooks.
Destructuring/pattern matching. It was carefully added in some simplified cases (mostly simply destructuring sequences) in many languages, then the support for wildcard and splicing was added, then support for hashes/dicts was added, and now finally Python has a proper `match` statement. I think more languages will implement it in the near future.
So it's not a type its more of a language implementation detail that doesn't add value to the programmer.
Where as with the Hindley–Milner type system the types exist and add value without requiring all of the extra code. You can infer the type most of the time and you get a nice strong type check at compile time.
Sigils seem like a step in the opposite direction to strong inferred types. You have to add a little bit of boiler plate but it's not that strict so it's meaning can still be confusing.
Perl is the most confusing language I’ve ever used professionally, largely because of how it uses sigils sometimes as operators. The other source of confusion are the million and a half implicit variables that are context-specific. It’s the only language where even after ten years I still regularly have to google to do simple things like iterate over a hash. And half the time it doesn’t work because the hash is actually a ref so now you need an arcane syntax or it doesn’t work. In Perl all of the implementation details of the language become footguns for you to shoot yourself with.
Not to mention that this refactoring introduces a bug:
$length = qw(burgers fries shakes);
Because lists and arrays convert to scalars differently. A list is more of a syntactic construct and an array is a data structure. Confused the heck out of me my first few weeks of Perl.
> So it's not a type its more of a language implementation detail
Well, it’s used to distinguish between values that were passed into a function via „copy by value“ or „copy by reference“. Insofar sigils have nothing to do with types but rather with function passing semantics. Yes, with a good type system you can also communicate whether an array is passed by “copy by reference” in the type signature of a function. Then you wouldn’t need sigils. But it’s a secondary feature of static types.
However, you could also abolish “copy by value” altogether (as JS and Python do) and then wouldn’t need neither sigils nor types.
> Where as with the Hindley–Milner type system the types exist
Perl is a dynamically typed language. If it were using static types, it wouldn’t quite need sigils, yes, but then it also wouldn’t have been the Perl programming language…
Sigils do not really have anything to do with static types and shouldn’t be discussed in this context. Then they are also not as confusing.
BTW: This whole discussion of static vs. dynamic typing has become a bit tiresome over the years. It will never be settled. In the 90s everybody tended to hate static typing and for good reasons so: It makes generic programming quite a bit more complicated than necessary. This was when all these dynamically typed scripting languages were invented to enable programmers write abstract code more easily by lifting the burden of constantly inventing composite types. „If it walks like a duck …“
Of course, everything is a trade off and with that approach you are prone to get more run time errors and, hence, people started test-driven development. Then developers started to hate writing tests (also for good reasons) and re-discovered static typing. Now people seem to be happy hacking ad-hoc types together — until they have to refactor other people’s programs and find it rather tricky because of the contagiousness of type signatures. When they spent enough weeks to rewrite type signatures in half of the program base they will long for the good ol’ scripting languages of the 90s again. And the cycle begins again.
Perl5 got sigils wrong, where more complex usages hinted at incorrect human parsing of expressions. That was designed borrowing from natural language, so wd should probably throw out the early part of the post. Now the post says in Raku
@ says “Use me with an array-like interface”
% says “Use me with a hash-like interface”
& says “Use me with a function-like interface”
$ says “I won’t tell you what interface you can use, but treat me as a single item”
I don't use Raku nor used much of Perl5 (only enough to learn it's good for writing, not for reading). Sigils in Raku may be fine and better than not using them. I'll accept that.
However, I much prefer inferred static typing and referential transparency where everything produces a value and it's not material whether it's a precomputed value or something that will produce the value when 'pulled on'. The last part works well with pure functions and lazy evaluation. Until someone claiming benefits of sigils has used this alternative for large, long-lived code written and maintained by many, I'll leave sigils to Raku alone.
I think there's a piece of insight missing from the author's analysis of non-programmatic sigils. To wit, the sigils are only valuable when both parties deeply understand the information that the sigil is trying to convey. The "$framework at $dayjob" example illustrates this point. Programmers familiar with the use of sigils to indicate variables intrinsically grok this phrase, but it looks like gobbledygook to non-programmers. The email inbox example is similar. (I'd argue the hashtag/@-symbol example is a bit more complicated, because those symbols service important UX functions.)
I think this insight crystalizes the trade-off. I agree with the author that sigils are a powerful way of communicating useful information in a concise fashion. But does their inscrutability to non-expert users justify their existence? I'd argue it usually doesn't. Whenever I've had to pick up a language that uses a lot of sigils (or even just had to read source code in one of those languages if I don't use it daily), I always find the sigils require a bit of extra mental effort to process. It seems like other languages manage to express meaning in a way that is less burdensome to non-experts.
> because those symbols service important UX functions
As I read the post, I was thinking that #tags and @mentions are primarily about input, not reading. It's easier to just whack some #random #tags in your #sentences than to switch to a separate tag list input. Similarly, highlighting some text in order to apply the "mention" brush like we might with bold or italics would be strictly worse.
I might agree with you if I ask for the fact some of the most popular beginner languages use sigils:
- BASIC
- Shell scripting
- PHP
It’s also worth noting that all languages have special tokens to identify properties of the code. Eg why does a string need to be wrapped in quotation marks but integers do not? Why do single and double quotation marks behave differently in some languages? Why do function names behave differently if you pass () vs not including parentheses in some languages?
At the end of the day, if you want to learn to program then you are always going to have some degree of syntax that you just have to learn. Sigils aren’t inherently hard but some languages make I them more abstract than others.
Another thing that’s worth baring in mind is that sigils solve a problem in languages that make heavy use of barewords, such as shells. Eg how do you know if foobar is a variable, function, keyword, parameter, etc if you syntax is
echo foobar
This is why other languages then use quotation marks, parentheses, etc. But while that’s arguably more readable, it’s a pain in the arse for REPL work in a shell (I know because I’ve tried it).
>Programmers familiar with the use of sigils to indicate variables intrinsically grok this phrase, but it looks like gobbledygook to non-programmers.
While I understand where you're coming from, I'd argue that programming-related concepts are all "gobbledygook to non-programmers", that's to be expected. Having something like (this is close to valid Raku but it's not)
Positional[Any] ages = [42, 38, 25];
doesn't make it any easier than
my @ages = [42, 38, 25];
unless you already have prior knowledge of arrays, assignments, types, etc.
> We had that problem at $day_job: our code was a mess, but everyone thought $framework would magically fix it.
Replace with "a" or "that one" and it's the same or even better.
The reality is that $dayjob (or %dayjob% etc) signals "hey I'm a programmer" and is less to type.
> I need to be able to quickly distinguish between the two types of labels so that I can notice emails that don’t have exactly one folder-label. This is a job for sigils.
Prefixing email labels is a workaround in this case because you don't need to notice emails that belong to two folders if your filtering system is good.
> As you’ve probably noticed, I’ve been saying “has an array-like interface” instead of “is an Array ”. That’s because – and this is a crucial distinction – Raku’s sigils do not encode type information. They only tell you what interface you can use.
> ...
> And Buf s aren’t Array s, nor are they any subtype of Array . But they can be used with an array-like interface, so they can be stored in an @ -siglied variable.
Sounds like a missed opportunity to type buffers as byte arrays...
We are past the point where sigils are useful for a modern compiler or machine interpreter. A limited ability to avoid collisions with "reserved words" ... would be more of a mis-feature than a feature.
The value of a sigil is for the other humans reading the code. And, I agree it can be quite valuable there.
> A limited ability to avoid collisions with "reserved words" ... would be more of a mis-feature than a feature.
While I don't necessarily disagree with you, there's something I'd like to mention.
C# allows using @ before a variable name which happens to be the same as a keyword to escape the name.
I don't remember the specifics, but it has saved me once when I wanted to create a class shaped exactly like a JSON object I was loading from an external source and one of the fields was a reserved word. For example, this works fine:
class MyClass
{
public int value;
public string @default;
public string @switch;
}
string json = "{\"value\": 8, \"default\": \"something\", \"switch\": \"on\"}";
MyClass o = JObject.Parse(json).ToObject<MyClass>();
Console.WriteLine("default={0}, switch={1}", o.@default, o.@switch);
I wish that more programming languages would include detailed rationales and justifications of their features like this in their documentation. While this blog post doesn't have the rigor of something like the Ada Rationale[1], it gives a real sense of why something might have been designed the way it was.
Maybe I just enjoy reading this sort of thing. But we are so often told to choose the "best tool", or find ourselves evaluating programming languages for all sorts of reasons. Being able to understand a language in its context, the choices it made in comparison to the alternatives, can only help with that.
When I read the rationale for a new early-stage language Austral[2], I found myself actually able to evaluate what I thought the language might be good for, how it might evolve while under the control of the same creator, whether it fit with my personal strengths, weaknesses, methods and aesthetics, etc.
By contrast, I was perusing the documentation for Odin, another new language, at around the same time. While I don't want to disparage the incredible amount of work it took to a) build a programming language and ecosystem and b) document it, I found myself wishing for a similar "rationale" document so I could actually compare Odin with Austral at a more abstract level than reading syntax.
Odin's "overview" begins with an example of the lexical syntax of comments, rather than what the principle "Striving for orthogonality"[3] in its FAQ actually means and how it is borne out in the language as designed, compared to other approaches that could have been taken.
Austral, by contrast, has a great section called "the cutting room floor" where the creator discusses different approaches to resource safety, the difficult tradeoffs to be made, and why Austral decided on possibly the severest of all the approaches. This isn't just philosophy; it tells me something useful about the tradeoffs involved in using the language.
Anyway, the OP helped me to understand very clearly that Raku's priorities and values are extremely different to my own, and that it would likely be a bad choice for me to invest time in.
Thanks for the Austral spec link! Programming languages come and go but I find it deeply interesting when language designers lay out the rationale behind possibly outlandish decisions in their programming languages. Case in point, Larry Wall and Perl-y languages [0]
>Sigils are why I gave up learning PERL. Everything I learned had a half life of 10 minutes.
Interesting! By "half life of 10 minutes", do you mean the language was changing too quickly under you or that it was difficult to remember the sigils?
I really like sigils for several reasons and I do miss them when in non sigil languages - imo raku made two big improvements over perl that is to avoid changing the sigil on accessing an item and to have an unsigilled option for people who don't like them https://rakujourney.wordpress.com/2022/12/24/on-sigils/
Avoiding sigils is a good future proofing strategy. When you find out you need a new syntax it's nice to have a set of symbols that are guaranteed not to break existing code
The way you worded this reminded me about some language extensions for the Commodore 64 and 128 that hooked into the BASIC language tokenizer. I remember one published in RUN magazine, I think for additional graphics functions, that added new BASIC instructions that were all prefixed with @. Not only did this prefix invoke the new code while interpreting, it also namespaced the addition avoiding conflicts with existing BASIC code.
In Clojure (and I’m pretty sure Scheme), ! and ? are not sigils in the sense that they’re special syntax like, say, @ (which is a reader macro), or @ and @@ in Ruby: they just tend to be used by convention. ! and ? are no different than a or b.
I perceive FORTH in a different way. The only sigil in FORTH is SPACE. Everything else is a letter.
Then you have Bjarne Stroustrup's "Generalized Overloading for C++2000", with which you can overload whitespace, uniquely redefining the meaning of space, newline, tab, and even the absence of space.
Personally, I have the exact opposite thoughs on sigils: they break my fluency in reading code.
Not all sigils are as bad, but to me it's as if there's a word from a foreign language italicized in an english sentance -- that means I'll have a reading pause there.
The dollar sign is among the worst, and curly braces are the lightest, with ”@" bring in the middle in terms of pausing reading.
Well that's a whole lot of talk with 0 concrete code examples...
One interesting thing, the author mentions VS Code as abolishing the need for Hungarian notation, which is funny - the entire VS Code codebase in written in Hungarian.
it’s always been a naughty pleasure that Matrix uses sigils both for ‘mainstream’ user IDs (@user:domain.com) and room aliases (#room:domain.com) as well as more developer focused things - $ for event IDs, ! for room IDs: https://spec.matrix.org/v1.7/appendices/#common-identifier-f...
I have a bad feeling this is due to me doing too much Perl as a child. But folks don’t seem to complain about it, especially since we also sprouted a proper URI scheme too.
* that communicates meta-information about the word.
He gives the example of `echo $USER`, where `$` is a single that communicates that `USER` is a variable, presumably with some contents. Thus, I'd wager `$` is a sigil in `$foo`.
jerf|2 years ago
By this metric, rather a lot of features turn out to be less important than they may seem at first. Many things are a zero on this scale that I think might surprise people still on their second or third language. From this perspective you start judging not whether a language has this or that exact feature that is a solution to a problem that you are used to, but whether it has a solution at all, and how good it is on its own terms.
So while sigils have a lot of company in this, they are also a flat zero for me on this scale. Never ever missed them. I did a decade+ of Perl as my main language, so it's not for lack of exposure.
(As an example of something that does pass this test: Closures. Hard to use anything lacking them, though as this seems to be a popular opinion nowadays, almost everything has them. But I'm old enough to remember them being a controversial feature. Also, at this point, static types. Despite my decades of dynamic typed languages, I hate going back to dynamic languages anymore. YMMV.)
derefr|2 years ago
I tend to miss one specific sigil (or pair of sigils): the @ and @@ sigils in Ruby, that mean "instance variable" and "class variable" respectively. Having identifier shadowing between stack-locals, and what Java would call "members" and "statics", be literally impossible, is just so nice. Especially when you get it "for free" in terms of verbosity, rather than needing to type `self.class.` or something.
I also really quite interned-string-literal : sigils in Ruby/Elixir — though I'd be equally fine with the Prolog/Erlang approach of barewords being symbols and identifiers needing to be capitalized. As long as there's some concise syntax for interned strings, especially in the context of dictionary keys. Because otherwise people just won't use them, even when they're there in the language. (See: Java, Python, ECMA6.)
Speaking of Elixir, the "universal sigil" ~ is kind of amazing. Define a macro sigil_h/2, and you can suddenly write ~h/foo/bar (or ~h[foo]bar, or whatever other delimiter works to best avoid the need for escaping), and foo and bar will be passed to sigil_h/2 as un-evaluated AST nodes to do with as you please. The language gives you ~w by default (which works like Ruby %w); but more interestingly, Regex literals in Elixir are just sigil_r.
TeMPOraL|2 years ago
I agree that closures pass the test - and I too remember when they weren't popular. I also remember what I did before learning about the very idea of first-class functions and closures: I simulated them with some ad-hoc means (like function pointers in C/C++, or passing strings to be eval()-ed in PHP, etc.).
This, I think, is an useful heuristic: the things likely to pass your test are the ones which people who don't have and don't know about them still end up approximating anyway - meaning those things are a natural solutions to some common problems.
I can think of couple other things that pass your test:
- Functions in general. It's the basic organizational primitive in code; working without them is Not Fun.
- Lisp-style macros. There are many problems that would be best solved with some surgical code generation, and having that option built-in into the language makes all the difference. Most languages don't have this type of macros - but that doesn't mean they aren't needed. Having done enough Lisp macrology, I saw that in those other languages I've always been coping. Missing them without knowing what they are.
Hell, look no further than webdev - these days, major frameworks like React, and every other minor library, and even the language evolution itself, all depend on running an external macro processor / code generation tool as part of your build pipeline.
Swizec|2 years ago
Two more examples (for me?) of features that I find you really miss in a language even if you’re fluent in the local idioms: First-class functions and pattern matching.
Passing functions as values is so nice and afaik most modern languages have that feature nowadays. But I remember when it used to blow people’s minds.
Pattern matching is something I’ve missed ever since having it in Haskell. Such an elegant solution to a problem that you have just often enough that the typical native approach feels clunky.
marai2|2 years ago
collyw|2 years ago
It's not a huge win, but I do think it's better than nothing.
As for missing things, I do miss Perl a lot. I missed the curly braces when I first started and whitespace didn't feel right. Than after maybe 6 months I had to go back and do some Perl. Moved some blocks of code around, then got the dreaded missing brace problem. I realized that was something that I never got in Python and am a fan of whitespace since then.
calderwoodra|2 years ago
heresie-dabord|2 years ago
I like to rate a programming language by how dependent the language is on some bloated IDE ("editor"). If I need an Eclipse or a Pycharm just to edit a file, something has gone wrong syntactically and systemically.
Sigils are semantic information about the code. Sigils do not reduce readability, they increase expressivity and comprehensibility. It isn't the characters themselves that are the problem -- we see the same notations for different purposes entering Python and DSLs such as Pandas.
Bash, awk, sed, and Perl are solid tools.
doctor_eval|2 years ago
The reason is that column names and function arguments overlap a lot, which can cause ambiguities when performing updates or selects. To become productive at plpgsql it’s a problem that you have to solve.
There are several approaches but the one I settled on is just to prefix all formal parameters with underscores.
The wish I have with plpgsql is that I could use $ instead since underscore is already heavily used as a word separator.
klibertp|2 years ago
Mine does vary - while static typing is helpful, it still (even with more advanced type systems) leads to boilerplate code that I dislike writing. In a compiler written in OCaml that I worked on for a bit, there were hundreds of lines of code dedicated to just stringifying variants. It could have been generated by a syntax transform (the newer tools for this are actually quite good), but that's another dependency and another cognitive overhead. In Kotlin, lack of structural types means that the rabid "clean architecture" fans create 3 classes for each piece of data, with the same 10 fields (names and types), and methods to convert between those classes - it requires 10x as much code for very little gain. Lack of refinement types makes the type systems mostly unable to encode anything relating to the number values, other than min/max values for a given type. There's reflection in Kotlin (not in OCaml though) that you can use, but then we're back to everything being an Object/Any and having runtime downcasts everywhere.
I think gradual type systems are a good compromise, for now at least. I'd prefer Typed Racket approach of clearly delineating typed and untyped code while generating dynamic contracts based on static types when a value crosses the boundary. Unfortunately, that's not going to work for existing languages, so the next best thing is something like TypeScript or mypy.
Of course, convenient, hygienic, Turing-complete not by accident, compile time execution and macros would, to some extent, alleviate the problems a simplistic type systems cause. A good example is Haxe, Nim, Rust, Scala 3, etc. Without such features, though, I'm not willing to part with runtime reflection and metaprogramming facilities provided by dynamic languages - the alternative is a lot more lines of code that need to be written (or generated), and I don't like that.
---
More to the topic: logic variables. The `amb` operator from Scheme, for example, or what Mozart/Oz has, or Logtalk, or Prolog of course. They're powerful, incredibly succinct way of constraints solving without writing a solver (just state the problem declaratively and done - as close to magic as it gets). No popular language offers an internal logic DSL, although there are some external DSLs out there.
Also, coroutines. No more manual trampolining, no need for nested callbacks, the state of execution can be saved and resumed later mostly transparently. Lua has them built-in, Kotlin implements CPS transform in the compiler. Nowadays almost all popular languages provide them, mostly exposed as async/await primitives. Scheme and Smalltalk can implement them natively inside the language and did so for ages; it's nice to see mainstream languages catch up.
REPLs. Not a language feature per se, but an implementation decision that has a lot of impact on productivity. It's relatively commonplace now - even Java has jshell - but most of the REPLs are pretty bad at executing "in context" of a project or module. Racket, Clojure, Common Lisp, Erlang, Elixir are gold standards, still unmatched, but you can get pretty far with Jupyter Notebooks.
Destructuring/pattern matching. It was carefully added in some simplified cases (mostly simply destructuring sequences) in many languages, then the support for wildcard and splicing was added, then support for hashes/dicts was added, and now finally Python has a proper `match` statement. I think more languages will implement it in the near future.
unknown|2 years ago
[deleted]
waynesonfire|2 years ago
xupybd|2 years ago
Things like implicit scalar conversion
Yeah it's easy to understand once you know what is happening but it's obscure. There are no easy clues to let you know what is happening.Then there are situations where you have a scalar but it's a ref
So it's not a type its more of a language implementation detail that doesn't add value to the programmer.Where as with the Hindley–Milner type system the types exist and add value without requiring all of the extra code. You can infer the type most of the time and you get a nice strong type check at compile time.
Sigils seem like a step in the opposite direction to strong inferred types. You have to add a little bit of boiler plate but it's not that strict so it's meaning can still be confusing.
throwaway173738|2 years ago
kqr|2 years ago
G3rn0ti|2 years ago
Well, it’s used to distinguish between values that were passed into a function via „copy by value“ or „copy by reference“. Insofar sigils have nothing to do with types but rather with function passing semantics. Yes, with a good type system you can also communicate whether an array is passed by “copy by reference” in the type signature of a function. Then you wouldn’t need sigils. But it’s a secondary feature of static types.
However, you could also abolish “copy by value” altogether (as JS and Python do) and then wouldn’t need neither sigils nor types.
> Where as with the Hindley–Milner type system the types exist
Perl is a dynamically typed language. If it were using static types, it wouldn’t quite need sigils, yes, but then it also wouldn’t have been the Perl programming language…
Sigils do not really have anything to do with static types and shouldn’t be discussed in this context. Then they are also not as confusing.
BTW: This whole discussion of static vs. dynamic typing has become a bit tiresome over the years. It will never be settled. In the 90s everybody tended to hate static typing and for good reasons so: It makes generic programming quite a bit more complicated than necessary. This was when all these dynamically typed scripting languages were invented to enable programmers write abstract code more easily by lifting the burden of constantly inventing composite types. „If it walks like a duck …“
Of course, everything is a trade off and with that approach you are prone to get more run time errors and, hence, people started test-driven development. Then developers started to hate writing tests (also for good reasons) and re-discovered static typing. Now people seem to be happy hacking ad-hoc types together — until they have to refactor other people’s programs and find it rather tricky because of the contagiousness of type signatures. When they spent enough weeks to rewrite type signatures in half of the program base they will long for the good ol’ scripting languages of the 90s again. And the cycle begins again.
karmakaze|2 years ago
However, I much prefer inferred static typing and referential transparency where everything produces a value and it's not material whether it's a precomputed value or something that will produce the value when 'pulled on'. The last part works well with pure functions and lazy evaluation. Until someone claiming benefits of sigils has used this alternative for large, long-lived code written and maintained by many, I'll leave sigils to Raku alone.
whakim|2 years ago
I think this insight crystalizes the trade-off. I agree with the author that sigils are a powerful way of communicating useful information in a concise fashion. But does their inscrutability to non-expert users justify their existence? I'd argue it usually doesn't. Whenever I've had to pick up a language that uses a lot of sigils (or even just had to read source code in one of those languages if I don't use it daily), I always find the sigils require a bit of extra mental effort to process. It seems like other languages manage to express meaning in a way that is less burdensome to non-experts.
crabmusket|2 years ago
As I read the post, I was thinking that #tags and @mentions are primarily about input, not reading. It's easier to just whack some #random #tags in your #sentences than to switch to a separate tag list input. Similarly, highlighting some text in order to apply the "mention" brush like we might with bold or italics would be strictly worse.
hnlmorg|2 years ago
- BASIC
- Shell scripting
- PHP
It’s also worth noting that all languages have special tokens to identify properties of the code. Eg why does a string need to be wrapped in quotation marks but integers do not? Why do single and double quotation marks behave differently in some languages? Why do function names behave differently if you pass () vs not including parentheses in some languages?
At the end of the day, if you want to learn to program then you are always going to have some degree of syntax that you just have to learn. Sigils aren’t inherently hard but some languages make I them more abstract than others.
Another thing that’s worth baring in mind is that sigils solve a problem in languages that make heavy use of barewords, such as shells. Eg how do you know if foobar is a variable, function, keyword, parameter, etc if you syntax is
This is why other languages then use quotation marks, parentheses, etc. But while that’s arguably more readable, it’s a pain in the arse for REPL work in a shell (I know because I’ve tried it).So there’s always trade offs.
kqr|2 years ago
luuuzeta|2 years ago
While I understand where you're coming from, I'd argue that programming-related concepts are all "gobbledygook to non-programmers", that's to be expected. Having something like (this is close to valid Raku but it's not)
doesn't make it any easier than unless you already have prior knowledge of arrays, assignments, types, etc.throwaway290|2 years ago
> We had that problem at $day_job: our code was a mess, but everyone thought $framework would magically fix it.
Replace with "a" or "that one" and it's the same or even better.
The reality is that $dayjob (or %dayjob% etc) signals "hey I'm a programmer" and is less to type.
> I need to be able to quickly distinguish between the two types of labels so that I can notice emails that don’t have exactly one folder-label. This is a job for sigils.
Prefixing email labels is a workaround in this case because you don't need to notice emails that belong to two folders if your filtering system is good.
> As you’ve probably noticed, I’ve been saying “has an array-like interface” instead of “is an Array ”. That’s because – and this is a crucial distinction – Raku’s sigils do not encode type information. They only tell you what interface you can use.
> ...
> And Buf s aren’t Array s, nor are they any subtype of Array . But they can be used with an array-like interface, so they can be stored in an @ -siglied variable.
Sounds like a missed opportunity to type buffers as byte arrays...
LeonB|2 years ago
You’ve hit the nail on the head. It’s also about the reader who understands the context and thus feels a sense of belonging to an “in-group”.
It’s petty, but it’s how humans work. We form and reinforce our little cliques in anyway we can.
powera|2 years ago
The value of a sigil is for the other humans reading the code. And, I agree it can be quite valuable there.
Zecc|2 years ago
While I don't necessarily disagree with you, there's something I'd like to mention.
C# allows using @ before a variable name which happens to be the same as a keyword to escape the name.
I don't remember the specifics, but it has saved me once when I wanted to create a class shaped exactly like a JSON object I was loading from an external source and one of the fields was a reserved word. For example, this works fine:
crabmusket|2 years ago
Maybe I just enjoy reading this sort of thing. But we are so often told to choose the "best tool", or find ourselves evaluating programming languages for all sorts of reasons. Being able to understand a language in its context, the choices it made in comparison to the alternatives, can only help with that.
When I read the rationale for a new early-stage language Austral[2], I found myself actually able to evaluate what I thought the language might be good for, how it might evolve while under the control of the same creator, whether it fit with my personal strengths, weaknesses, methods and aesthetics, etc.
By contrast, I was perusing the documentation for Odin, another new language, at around the same time. While I don't want to disparage the incredible amount of work it took to a) build a programming language and ecosystem and b) document it, I found myself wishing for a similar "rationale" document so I could actually compare Odin with Austral at a more abstract level than reading syntax.
Odin's "overview" begins with an example of the lexical syntax of comments, rather than what the principle "Striving for orthogonality"[3] in its FAQ actually means and how it is borne out in the language as designed, compared to other approaches that could have been taken.
Austral, by contrast, has a great section called "the cutting room floor" where the creator discusses different approaches to resource safety, the difficult tradeoffs to be made, and why Austral decided on possibly the severest of all the approaches. This isn't just philosophy; it tells me something useful about the tradeoffs involved in using the language.
Anyway, the OP helped me to understand very clearly that Raku's priorities and values are extremely different to my own, and that it would likely be a bad choice for me to invest time in.
[1] http://ada-auth.org/standards/12rat/html/Rat12-TOC.html
[2] https://austral-lang.org/spec/spec.html
[3] https://odin-lang.org/docs/faq/
luuuzeta|2 years ago
[0] http://www.wall.org/~larry/natural.html
nige123|2 years ago
https://raku-advent.blog/2022/12/02/day-2-less-variable-watt...
smeagull|2 years ago
I haven't programmed C in a decade, I still remember most of it.
luuuzeta|2 years ago
Interesting! By "half life of 10 minutes", do you mean the language was changing too quickly under you or that it was difficult to remember the sigils?
mfranc42|2 years ago
p6steve|2 years ago
luuuzeta|2 years ago
Sigilled "variables" do lose many of the perks that sigils provide though, as codesections explains in the blog.
mkoubaa|2 years ago
thwarted|2 years ago
js8|2 years ago
grzm|2 years ago
You can read more about the Clojure reader here: https://clojure.org/reference/reader
DonHopkins|2 years ago
Then you have Bjarne Stroustrup's "Generalized Overloading for C++2000", with which you can overload whitespace, uniquely redefining the meaning of space, newline, tab, and even the absence of space.
https://www.stroustrup.com/whitespace98.pdf
mihaic|2 years ago
Not all sigils are as bad, but to me it's as if there's a word from a foreign language italicized in an english sentance -- that means I'll have a reading pause there.
The dollar sign is among the worst, and curly braces are the lightest, with ”@" bring in the middle in terms of pausing reading.
stephc_int13|2 years ago
Code clarity should be seen as higher priority than terseness or typing speed.
flumpcakes|2 years ago
The link in the yellow box to the follow up article links to the article itself instead of /2022/12/23/sigils-2/.
Also a missing word "hash" in the link towards the bottom of the article to hash_literals in the Raku documentation.
jakear|2 years ago
One interesting thing, the author mentions VS Code as abolishing the need for Hungarian notation, which is funny - the entire VS Code codebase in written in Hungarian.
Arathorn|2 years ago
I have a bad feeling this is due to me doing too much Perl as a child. But folks don’t seem to complain about it, especially since we also sprouted a proper URI scheme too.
__MatrixMan__|2 years ago
> echo $foo # $foo refers to the contents of the variable foo
luuuzeta|2 years ago
* a non-alphabetic character
* that is at the start of a word
* that communicates meta-information about the word.
He gives the example of `echo $USER`, where `$` is a single that communicates that `USER` is a variable, presumably with some contents. Thus, I'd wager `$` is a sigil in `$foo`.
kqr|2 years ago
AtlasBarfed|2 years ago
And string interpolation (basically the same thing) is on python, groovy, and I think is coming to java.
__MatrixMan__|2 years ago
groovy string interpolation is ${criticism}
/$
emmelaich|2 years ago
ChoHag|2 years ago
[deleted]