Comment by ~akavel on ~icefox/garnet
Not sure where to best write it - sorry if that's not the right thread and maybe some other is better - not a problem for me to delete from here and move elsewhere (assuming sr.ht allows for deleting posts?)
So, just wanted to say, as far as possible inspiration/related lanugages go, I just discovered that the Teal language (formely "typed Lua") had a new major release recently, with some new features that might (or might not) be of some interest to you - in case you also missed it, the link is: https://github.com/teal-language/tl/blob/v0.24.4/CHANGELOG.md#0240---teal-spring-24
Comment by ~akavel on ~icefox/garnet
I distinctly remember recently stumbling upon some page (or reddit comment?) elaborating on why ABI stability in C++ is not a good thing (AFAIR, one of the things listed was that they can't upgrade the data structures used in stdlib containers, e.g. hash map). Possibly in context of some question like "why Rust no haz stable ABI???". But I seem unable to find it in any of my half-broken bookmarking systems, so, sorry :( anyway, just wanted to put it here for completeness, not that I want to sway the decision in any direction. But again, anyway, can't find at the point, so, uh... :( π€·ββοΈ
Comment by ~akavel on ~icefox/garnet
An interesting "high-level" discussion about Rust and C++ with Steve Klabnik and Herb Sutter, including some "what if we had a time machine" pondering as well:
https://softwareengineeringdaily.com/2024/10/23/rust-vs-c-with-steve-klabnik-herb-sutter/ (via)
a transcript (with some small issues): http://softwareengineeringdaily.com/wp-content/uploads/2024/10/SED1756-Rust-vs-Cpp.txt (via)
Comment by ~akavel on ~icefox/garnet
FTR - regarding the paper, one point that is easy to overlook when skimming, is the proposed syntax for languages not using "significant whitespace" for blocks (pg. 6):
if x { > 0 then "positive"; == 0 then "null"; else "?" }
Comment by ~akavel on ~icefox/garnet
I dunno that I actually want to do something like that in Garnet --Rust does a very good job of making questionable things a bit inconvenient to write so that you have some time to think about how much you really need them-- but I appreciate how slick it is.
I think the point and argument of Garnet is in part that Rust makes some things a bit too much inconvenient, no? π I always liked the phrase, sometimes used for various technologies, that: "it feels like there's a smaller, simpler [language] hiding inside [Rust] and wanting to escape" - I'm kind of assuming that's what drives you forward, and it's at least what I'm hoping for Garnet to try to be π
That said, I did also eventually kinda grow some understanding at least of why Rust, as it is currently, does some things the way it does; I put my attempt at explaining my thoughts on that into another diagram π
But with the above apology towards Rust, I still can't stop feeling annoyed by it π so still hoping that indeed things can be simplified in some "second system/generation" language based on lessons learnt by Rust π€© And keeping my fingers crossed for Garnet for that ππ€
But, in the end, whether to support some particular pattern in Garnet or not, is obviously your call and privilege to make π
Comment by ~akavel on ~icefox/garnet
I think one thing to note here could be how to handle fields shared between some variants only. I vaguely recall I felt a need for something like this more than once in my life/career; though also not daily/monthly I think (but maybe that could happen more often if they were available more easily?..).
Now that I think of it, I guess it honestly makes me think of this being a "representation of states/nodes/vertices in a state machine". And data being sometimes shared/retained through a few states of the state machine; notably, also such spans could be overlapping. Say:
enum States { A { foo: Foo }, B { foo: Foo, bar: Bar, zing: Zing }, C { bar: Bar } D { foo: Foo, zing: Zing } }
thus for example representing a state machine like below:
(edit: hm, some random ideas for syntaxes for the above:
enum State { val foo: Foo in A, B, D; val bar: Bar in B, D; val zing: Zing in B, D; A { } B { } C { } D { } } ## ok, I hate the one above... enum States { A { foo: Foo } B { A.foo, bar: Bar, zing: Zing }, C { B.bar }, D { B.foo, B.zing } # could be allowed as well as: D { A.foo, B.zing } ? } ## hm, the one above seems like it could actually make some sense... π€
)
Another thought coming to mind, whether field names could be then still freely shared between variants if they needed to have different types, e.g.:
enum AST { AddInts { left: int, right: int }, AddStrings { left: String, right: String }, } enum States2 { A { foo: Foo }, B { foo: FooSlightlyModified }, } enum HTMLBlock { Div { children: []HTMLBlock } P { children: []HTMLSpan } }
Comment by ~akavel on ~icefox/garnet
Ah, by the way... not strictly "syntax" in the narrow sense, but more like wider "language UX" thing: I'd be extremely grateful if you made sure to allow code written in "top-to-bottom" order. I.e. "main" function first, then helper functions, then their helper functions, etc.
I learned this order of coding in Go, and I find it the most useful/practical for the people reading the code: the "main" function is both the app's entry point, so it is helpful and fitting to have its implementation be the first thing seen when reading a file, but it also becomes the app's "map"/"table of contents". So that, when exploring a new (or old...) codebase, I can start from "the map" of the highest-level function's body, then decide into which area I want to dig down - and hopefully continue along the same path.
Conversely, in languages which make it non-default/hard/impossible (Lua, OCaml, Python, Nix), I often need to waste some time to even find where the entry point of a codebase/file/module is.
The only other thing somewhat orthogonal to it is whether to put definitions of a struct/class fields before, or after its methods. Personally (again), I believe putting data first is the better choice here; this fortunately tends to be rather common in most languages I can recall now.
FWIW/FTR, my code tends to be thus usually ordered more or less like:
- imports from stdlib (least touchable code)
- imports from 3rd-party libs (moderately touchable code)
- imports from company-internal libs (code presumably somewhat more touchable)
- imports from current project (presumed to change often and most easily tweaked)
- main function
- helpers for main function, in "chronological" order (either as mentioned in the main function, or in order of execution)
- their helpers... here order becomes somewhat more messy vs. locality to the caller function's body
- generic helper/utility functions that are used by code above, but look universal enough that they might feel ripe for extraction to a utility library
- class/struct 1 with fields
- constructor of class/struct 1 (if needed) - "by definition" will be chronologically the first thing to be ever called for the class/struct
- other public methods of the class/struct, preferably in a "recommended chronological order of usage of the API" - though e.g. Close method might arguably land near to the constructor for easier cross-checking of fields list, as well as visibility in the API; again a "chronological" order matches the one used for functions called by main above, but also makes it easier to reason how to navigate in the codebase when reading/modifying it (esp. when bugfixing...)
- class/struct 2 with fields...
- etc. methods of class/struct 2...
The order above tends to become less strict and more messy in the middle of the file, because whether I still can maintain chronological order vs. whether locality or the "specific first, universal last" might take priority; but - just kinda speaking out loud here, and sharing some thoughts...
Comment by ~akavel on ~icefox/garnet
And, as to "overloading operations on booleans and integers Feels A Little Wrong", I'd kinda challenge you on that :P Firstly, it don't feel wrong to me honestly ;P secondly, Nim does this and don't seem problem; thirdly, e.g.
1 / 2
vs.1.0 / 2.0
are also overloaded and not exactly the same.On the other hand, I remembered reading something about why C has both && and &, and that BCPL had just &; I tried to find it now, and found this: https://www.bell-labs.com/usr/dmr/www/chist.html, see section "Neonatal C" (via) - apparently one area where this might actually be an important distinction is in precedence rules (ideally
foo && bar == baz
should befoo && (bar == baz)
, whilefoo & bar == baz
should be(foo & bar) == baz
, which is not so in C as explained in the link and a common footgun).
Comment by ~akavel on ~icefox/garnet
To clarify:
- the
foo:bar(arg):baz(arg2)
syntax is not "mine", it's just Lua; but I assume you know this, and maybe are just saying it's mine as a shortcut, then that's ok for me ;) Also, it kinda made me realize it's not really necessarily UFCS I think, it's just a "method invocation syntax"; whether it's used with UFCS or not is a separate topic IIUC.- with
foo |> bar(arg) |> baz(arg2)
, if you add any "piped" syntax, I'd say you need to be ready to accept it in a "nested" form (in args) anyway, it will just happen (whether that's to be deemed "cool" and "good practice" or not is another thing I guess, I don't really know) - e.g. I wrote some in my recent scripts in Nickel as shown below:by-path = fun record-with-paths => record-with-paths |> std.record.to_array # [{field=..., value=...}...] |> std.array.map (fun {field, value} => field |> std.string.split "/" |> put-deep value) |> std.record.merge_all,
oneliners_to_bat = fun record => record |> std.record.to_array |> std.array.map (fun x => { field = "%{x.field}.bat", value = "@%{x.value} %*\n", }) |> std.record.from_array,
As to what is less vs. more noisy: firstly, personally, in your example above, I do actually find the
|>
example to have "more air" than the:
one and as such be easier to read (to me); but that's again just One Internet Person's Opinionβ’. Secondly, this makes me think of Lua's booleans operations vs. C-style ones - in C you havefoo && bar
(or evenfoo&&bar
), in Lua/Python you havefoo and bar
. In each case I think whether one or the other is more readable depends mainly on what the user is more familiar with. I remember there was a time when I found Lua's syntax weird in booleans and "hard to read", but with time they seem both equally fine. And eventually I started liking Lua's consistency, and a kind of a "flow of words" in its syntax - maybe just associating it with my overall liking of Lua and its well balanced minimalism and choice of features.
Comment by ~akavel on ~icefox/garnet
Aaalso, again not exactly a full answer, but Nim has some syntax sugar via a
using
keyword: https://nim-lang.org/docs/manual.html#statements-and-expressions-using-statement