13 ms·
26 programming languages in 25 days, Part 2: Reflections on language design
- dang 4y agoRecent and related: 26 Programming Languages in 25 Days - https://news.ycombinator.com/item?id=34161410 https://news.ycombinator.com/item?id=34161410 - Dec 2022 (74 comments)
- _ZeD_ 4y ago>>> Somewhat surprisngly, indentation-sensitivity in languages like Python and Haskell had me writing cleaner-looking and more readable code by default. Who would have though! It's like this would have been the whole goal of this trick
- thargor90 4y agoWhat I hate about python/yaml/etc is that copy/paste + reformat does not work. Yes it forces clean indentation, but it gives the writer the burden to take care of it. That sucks. Let the computer do the work.
- TuringTest 4y agoThat's only because editors are not programmed to do the work. An IDE with proper language support for a whitespace-aware language should be able to parse the AST figuring out the code structure from the indentation level; then it could reformat it at the proper indent level when you copy and paste it elsewhere, just like it does with a delimiter-based language.
- deleted 4y ago[deleted]
- dmitriid 4y agoNo, they couldn't. There are too many places where the identation can go either way. Just because you paste a bunch of code after `then` in Python doesn't mean all of it goes into `then`.
- TuringTest 4y agoThe origin text that you're cutting will have a precise syntax determining where the block starts and ends. Just copy it to the new position, and change the indentation level to that of the target position, maintaining the same block definition. You know, the same thing that would be done if there were start and end delimiters; because in Python, indentation changes ARE block delimiters, with a well-defined syntax. It's not rocket science, just following the language indentation rules for defining blocks. If the language parser can do it, why not the text editor?
- dmitriid 4y ago> The origin text that you're cutting will have a precise syntax determining where the block starts and ends. Will it? Are you sure? > Just copy it to the new position, and change the indentation level to that of the target position, maintaining the same block definition. "Just" copy. "Just" manually re-indent it. Remember, we're talking about an IDE automatically figuring out proper indentation for something. > If the language parser can do it, why not the text editor? Because constructs that are valid from the compiler's point of view may be ambiguous fro the program's point of view: https://news.ycombinator.com/item?id=34235121 https://news.ycombinator.com/item?id=34235121
- TuringTest 4y ago>> The origin text that you're cutting will have a precise syntax determining where the block starts and ends. > Will it? Are you sure? Yes, it's defined as part of Python specification. https://docs.python.org/3/reference/lexical_analysis.html#indentation https://docs.python.org/3/reference/lexical_analysis.html#in... Indentation increasing or decreasing generates INDENT and DEDENT tokens, which are used as block delimiters. > Remember, we're talking about an IDE automatically figuring out proper indentation for something. The IDE doesn't need to figure out whether the program is correct. Only has to treat code blocks as defined by the language syntax. In your linked example: if x > 0: function1() function2() function3() if x > 0: function1() function2() function3() The IDE should treat the blocks as if defined with delimiters this way: if x > 0: { function1() function2() function3() } if x > 0: { function1() function2() } function3() Because that's how Python will interpret them. So, the IDE would do exactly the same behavior with curly bracket delimiters and with changes in indentation, because in both cases there is a precise rule to define where blocks start and end.
- lelanthran 4y ago> That's only because editors are not programmed to do the work. No; it is not decidable where a block ends and the next line of the out block starts when there is no marker for block ends ('}', or 'END', etc).
- TuringTest 4y agoOf course it is decidable, the programming language has very strict rules to determine it. That's why you need language support in the editor, not just finding matching open/close delimiters.
- lelanthran 4y ago> Of course it is decidable, the programming language has very strict rules to determine it. No, it's not. The programming language grammar can reject things, but it cannot determine certain class of mistakes which explicit terminators ("}" and so on) can, which is why programs like `indent` existed for decades for other languages, but have yet to exist for Python which is already 30 years old. If no one has cracked that particular nut for 30 years, you can't with a straight face claim that it is obviously possible. It looks more impossible with each passing year.
- TuringTest 4y agoIf a program cannot determine when a block code starts and ends from indentation alone, then it is not correct Python; the language needs a deterministic process to determine what sequence of commands belong to the same block, otherwise it couldn't compile. If you're talking about failing to determine blocks which are NOT correct Python, then I don't have a problem with those failing to be correctly indented under copy/paste. The vast majority of use cases would still be well served.
- kaba0 4y agoWe are talking about pasting code into another, where both can fail to be correct at the time and you may or may not want to output to be correct. Nonetheless, your intent can’t be determined due to multiple ambiguous “good” outputs, e.g. should insertion start at the current level and keep at it, did the copy happen from a “dumb” medium without leading whitespace so that it should be tried to be indented first, etc. Sure, you can have a look at the resulting code and fix, but we all know that humans err a lot, this is something that works braindeadly easily with non-whitespace ”aware” languages.
- peoplefromibiza 4y agoOTOH hand, COBOL had this and then switched to format free code, because it's something 1960s when computers were slow and helping the compiler made it fast enough to be used.
- IshKebab 4y ago> I’ve bounced back and forth between statically typed and dynamically typed languages my whole career. > > I’ve never settled dogmatically into any camp, and this experience didn’t change that. I don't think any proponents of static typing would expect small problems like these to really show the benefits of static typing though. If you were trying to decide whether comments were worth it you would conclude from AoC that they are not, which is clearly the wrong conclusion in general. The author does say they have extensive experience of Haskell and Standard ML so presumably they have experience with the enormous benefits of static typing at "real program" scale so it's a strange conclusion IMO.
- 082349872349872 4y ago> In particular, days where dense matrices were the primary data structure seemed to favor dynamically typed languages. IIRC, if one pedantically types a typical NxM transformation matrix, one may need NM different explicit types. (or maybe this was just a problem when derivatives were involved?)
- Smaug123 4y agoIf we're being pedantic, this is only true if your type system is weak enough to lack type-level natural numbers.
- Ambolia 4y agoWhich programming languages have a strong enough type system to do this? And does it have any draw backs to implement type-level natural numbers, or is it just complicated and a lot of work?
- tomsmeding 4y agoHaskell does, and you get decent type-level arithmetic with a compiler plugin.
- pharmakom 4y ago
- breck 4y agoIn general, really fascinating post. I might repeat the experiment myself. Thank you. > Based on this experience alone, if I were to design a non-S-Expression-based language, it would push me toward indentation-sensitivity. > That’s a notable shift from my prior perspective, where I felt that whitespace-insensitivity was preferrable to indentation-sensitivity. Welcome to the club! I just wrote some new thoughts on this today: https://breckyunits.com/useTheSpine.html https://breckyunits.com/useTheSpine.html
- tikkabhuna 4y agoA language that does not derive information from whitespace, doesn't mean the language is written without whitespace. Java, Go, Javascript, Rust, etc are all regularly written with whitespace, and have tools to enforce such formatting, but they don't derive information from it. Also, Python has existed for decades and still there is little further adoption of indentation-sensitivity. It doesn't seem like a wave of indentation-sensitive languages will be coming any time soon.
- breck 4y ago> Java, Go, Javascript, Rust, etc are all regularly written with whitespace, and have tools to enforce such formatting, but they don't derive information from it. Ah you reminded me. A curious phenomenon I've observed with Prettier in JS and fmt in Go is languages are moving to standardized whitespace, but as you said, not yet deriving information from it. I don't know enough about Java or Rust but I suspect they probably both have adopted a Prettier/fmt like convention where all code is formatted on save. So it seems like we are moving to a world where it will be a simple flip of a switch to then start having popular languages extract meaning from the whitespace. > Also, Python has existed for decades and still there is little further adoption of indentation-sensitivity. It doesn't seem like a wave of indentation-sensitive languages will be coming any time soon. I think it's coming big time this year. I think our Scroll (https://scroll.pub/ https://scroll.pub/) will catch fire and be the go to language instead of Markdown by the end of the year. Then with the increasing success of TreeBase (powering PLDB and others) we will start to see JSON fall for config formats and document storage databases. A lot more will happen to, data vis will be a big one, but those 2 I'm reasonably certain of happening in 2023.
- conaclos 4y ago> even in languages where a [...] } was needed, the indentation always predicted where it would have gone anyway. > if I were to design a [...] language, it would push me toward indentation-sensitivity. > That’s a notable shift from my prior perspective, where > I felt that whitespace-insensitivity was preferrable to indentation-sensitivity. I'm starting to have the same change of perspective for a different reason: widespread use of formatters. One of the drawbacks of white-space sensitivity is the accidental mixing of distinct white-spaces (e.g., tabs and spaces). Using a formatter eliminates this problem because all white-spaces are normalized.
- quickthrower2 4y agoAbsolutely. After all in language whitespace doesn’t matter so much!
- pharmakom 4y agoSimple solution is to ban tabs from source-code using the compiler
- vitro 4y agoYou can set tab display width for whatever width you prefer, you cannot do the same for space. From accessibility point of view, tabs are more usable for people with sight problems.
- zokier 4y agoIf you have standardized indentation width, then the only difference is how the indentation is stored in the file. You can present either option in arbitrary way to the user. Doing n spaces to m character cells is as simple transformation as 1 tab to m character cells.
- mananaysiempre 4y agoNo, it isn’t[1], not in most formatting styles, if we’re speaking about the usage where the user can adjust the tab width and have the formatting not fall apart. (Note this excludes e.g. Vim or Visual Studioconfigured to use tabs, so that may be a part of the confusion.) The reason is that alignment still needs to be done with spaces: TAB if (foo) { TAB TAB bar(spam, TAB TAB ....eggs); TAB } [1] https://www.emacswiki.org/emacs/SmartTabs https://www.emacswiki.org/emacs/SmartTabs
- ummonk 4y ago> However, for puzzles where the data structures were straightforward, dynamically typed languages had me moving faster out of the gate." I don't understand this. I don't really encounter situations where the data structures are "straightforward" that type inference isn't able to handle on its own (or at most with explicit type specification for just a few variables).
- wink 4y agoNot 100% sure what's meant in this specific example but as someone who did advent of code a lot, there are truly benefits of an untyped python dictionary over anything in Rust or Java or C++. You can just put None next to your integers without having to wrap in Optional or even worse, a struct.
- still_grokking 4y agoYou can't compare Python to something like Rust, Java, or C++. Compare to for example Scala, that would make more sense.
- ummonk 4y agoJava and C++ don't have type inference. It would surprise me if Rust isn't smart enough to infer that the type of the dictionary is "Optional<Integer>" or "Integer | None". I haven't used Rust myself, but every language that I've used with type inference and algebraic data types will make this inference on its own.
- rowls66 4y agoRecent versions of Java have type inference.
- atdrummond 4y agoWhat’s really crazy is Matt does this while also running the Hugh Kaul Center for Precision Medicine at UAB. Some of their recent work: https://www.uab.edu/news/health/item/13224-clinical-trial-for-rare-genetic-disease-gets-assistance-from-artificial-intelligence https://www.uab.edu/news/health/item/13224-clinical-trial-fo...
- smalltalkcoder 4y agoIndentation-sensitivity is so popular that only one language in the Top 20 programming languages has it. That's nearly 40 years after I saw my first indentation-sensitive language: occam. Indentation-sensitivity seemed like a great idea in 1983. I'm not convinced today.
- 082349872349872 4y agoThe original formulation of the offside rule made provision for handwritten entry. If/when computers get small enough to lose the keyboards and fast enough to reliably OCR, maybe that provision will be rejuvenated?
- dropofwill 4y agoNot sure what list you’re referring to, but I would expect to find Python and Scala in it, maybe OCaml or Haskell depending on the criteria. Personally I think it causes more problems in interpreted languages, a lot of those disappear with a compiler with thoughtful error messages like Scala.
- lelanthran 4y ago> Not sure what list you’re referring to, but I would expect to find Python and Scala in it, maybe OCaml or Haskell depending on the criteria. Surely neither OCaml nor Haskell are in the top 20 of any "Most Used Programming Languages" list.
- dropofwill 4y agoMaybe of undergrads at a French university :)
- andsoitis 4y agoA common list people have in mind is TIOBE: https://www.tiobe.com/tiobe-index/ https://www.tiobe.com/tiobe-index/ I will give you that for highly specialized domains, those lists would look different (eg in some niche OCaml would be high while Rust would be low, or R high where C++ is lower down the list) , but it is telling that Scala comes in at #32 when compared to other languages that, I think, occupy a similar problem space. Of course, Python is #1 on the TIOBE list (and have had a strong showing for years), which I ascribe to the overall productivity and wide application domain fit. So, whether or not white space is objectively bad (for any language or only some languages), it hasn’t detracted so much from Python’s popularity.
- smalltalkcoder 4y agoIn my experience, type-related programming bugs are very rare (less than 2%). That's why I'm not in the least bit concerned with whether or not a language is statically-typed or dynamically-typed. I use both as needed. My favourite languages are Go and Smalltalk. Smalltalk allows me to do live programming with great ease. This is a huge productivity amplifier. Smalltalk's productivity is much higher than that of any major language in use today.
- _glass 4y agoDepends on the language, but when you have to use some metaprogramming gymnastics, then types are really hindering to develop something generically, if the language doesn't provide a fully developed type systems, which few systems have. For example higher-kinded types are quite essential, but not always given. Then I prefer dynamic typing and get this done fast without safety. But then I remember getting a lot of bugs in certain types of applications, maybe one fifth, or something.
- thesz 4y agoGo is not statically typed, really. Consider using Haskell, if not Agda. About "type-related programming bugs." I once took endeavour to write cycle-accurate MIPS CPU simulation in Haskell. Most of the errors I uncovered were easily preventable with the types - mark integers with their size in bits and you will never add instruction pointer to an unaligned offset. Haskell did not had proper types at the time but later I used that trick quite successfully. With the types you can eliminate whole classes of logic errors. They just will not appear before end user.
- friedman23 4y agoDo type related programming bugs include null pointer errors? I also think this 2% stat is just complete bs. Anyway, this debate is over. Absolutely every top language designer has seen the merit of statically typed languages, if not for avoiding type errors then for making code refactorable.
- arcturus17 4y agoFor me it’s never been about type bugs but rather about being able to work comfortably with the code. Some of us don’t have the greatest working memories; having the IDE offering support with types is a godsend. Python’s my prototyping language and I start adding types very early on, if not right from the start.
- samsquire 4y agoThank you for writing this article. Very interesting. The dualities static vs dynamic, whitespace insensitive vs sensitive, brackets vs nobrackets, pascal/fortran/style vs algol, interpreted vs compiled, dynamic scope vs lexical scope, aot compiled vs jit compiled, functional vs oop are all dualities that a language must bear with the ramifications of that decision. I don't think it's obvious that one category of the duality is ALWAYS superior to the other in every scenario. Obviously we would prefer a language that is expressive, easy to read and compiled for performance and avoids errors at compile time. Does this language exist? And that is as easy to write as a scripting language such as Python or Ruby. Python is extremely fast to write scripts for. I'm working on my own programming language and it resembles Javascript, so it is C style brackets and semicolons. In the interests of getting running fast, I compile to my own imaginary assembly language which is a switch based interpreter. This means I can work on the frontend and backend of the language in parallel. My language is a multithreaded language and can communicate integers between threads. I am still designing compound struct sharing between threads. Since this enters the move vs copy or by reference or by value semantics problem. If I send an object/struct/hash/list by copy then the code is slow. If I do it by reference, I have memory management to worry about if I plan to rewrite my language in a compiled language and not rely on Java's garbage collection. On eatonphil's discord we talk programming language design.
- wink 4y agoNice, I only managed to do 20 (of the same goal, 25 languages) in 2019: https://f5n.org/blog/2019/advent-of-code-2019/ https://f5n.org/blog/2019/advent-of-code-2019/ > Tactic: Do translation more than optimization This rings especially true - "you can write Java in any language", or whatever sort of language shaped your thinking. Even after years of using different languages my prototyping code is often inherently imperative and I have to actively rewrite it to be functional.
- dogfishbar 4y agoThanks for letting us tag along. What I'd like to hear are your thoughts on the Groucho Marx cigar question: laziness is useful every now and then, but in lazy languages like Haskell you pay for it everywhere. The same is true for dynamic dispatch, it's needed when working with values of sum type. But like laziness, it isn't needed wall-to-wall but in dynamically-typed languages, you pay for it everywhere. Give me a call-by-value language with arrow and sum types and I can pay as I go. [edit: added "arrow and"]
- TylerE 4y agoOcaml then? It has optional options-in lazyness for the few times you really need it. Or just rolling it yourself with e.g. a python generator
- dogfishbar 4y agoSure, OCaml is great as are the other dialects of ML. I'm hoping that someone will weigh in with a compelling case for laziness everywhere or dynamic dispatch everywhere, other than pre-existing infrastructure, libraries, etc.
- still_grokking 4y ago> hoping that someone will weigh in with a compelling case for laziness everywhere How should this happen? You can hear even form the horses mouth that laziness by default was a big mistake. > dynamic dispatch everywhere Big mistake of most "OO language" indeed. Rust got this right. Static dispatch is the default (simple and fast!) but you can have dynamic dispatch where it makes sense. I hope this design will be copied in the future everywhere. Maybe even some language manage to get rid of their mistake and switch to the sane design (looking at you Scala, just make type-classes first-class, but keep OOP for the cases where it makes sense).
- imaltont 4y agoYou should check out Scheme with SRFI-119 or similar. It is a really elegant and noise-free way of writing S-expressions, unsure if it works with Racket or not though. I also did a similar challenge this year, though I had to drop some of the more exotic languages that you kept in. Nice work.
- rwmj 4y agoeval is bad, but there are better alternatives which don't involve unsafe string concatenation. OCaml had (for a while) a nice system for constructing ASTs, eg: let ast = <:expr< 1 + 2 >> would be the way to write the AST: + / \ 1 2 and you could compose those in various ways, eg: let ast2 = <:expr< $ast$ + 3 >> Of course it was all safe at compile time. The main issue with it was it was quite complicated and terribly badly documented.
- AstralStorm 4y agoThe whole idea behind eval is to provide textual input. Requiring an AST as input breaks the whole idea - you're just writing code directly in an awkward format.
- rwmj 4y agoYou're avoiding a whole can of exploits: eval 'print ("hello, $some_user_input")'
- dannymi 4y agoeval comes from Lisp, and there, it's not textual input. You give it a quoted form (i.e. an AST) and an environment and it evaluates the form in that environment. > (eval '(display (+ 5 6)) (interaction-environment)) 11
- attractivechaos 4y ago> compilation versus interpretation never made a sigificant difference in performance for my solutions (which tended to be dominated by algorithmic complexity concerns) ... Were I to implement a language, I think I’d favor having an interpreter first. Constant matters. Ahead-of-time compilation (AOT) and just-in-time compilation (JIT) are a lot faster than interpretation [1,2], sometimes by a factor of >100 in the case of C vs pure python [3]. This gap is noticeable for a program taking more than a few seconds to run. Except for performance-critical tasks, my preference is JIT-based languages without a manual compilation step, such as LuaJIT, Javascript-V8/Node.js and Dart. Nonetheless, these language implementations are much harder to develop than interpreters. [1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/python3-go.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... [2] https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/python.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... [3] https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/python3-gcc.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- kaba0 4y agoInterpreters don’t mean the internal evaluation method here, but only the mode of operation. E.g. ruby implementations that use JIT, java’s jshell, python also has a JIT interpreter.
- sideeffffect 4y ago> Constant matters Not in these exercises. They're focused on algorithms and their asymptotic complexity.
- attractivechaos 4y agoI am not familiar with Advent of Code. If all their problems take ~0.1s to solve in C, you are probably right. However, it is a stretch to make a high-level conclusion just based on small exercises, like this: > There was never a time where rewriting in another language felt like the right way to get better performance. Similarly, for small exercise problems, dynamic typing rarely hurts, but for large collaborative projects, static typing starts to show its advantages.
- soegaard 4y agoThe github repo: https://github.com/mattmight/advent-of-code-2022 https://github.com/mattmight/advent-of-code-2022
- threadl0cal 4y ago> What I’d really like to see is more support for embedded domain-specific languages within general-purpose languages. I see that you tried Julia. You can kind of write DSLs in it using macros. E.g. Turing.jl ( https://turing.ml/dev/ https://turing.ml/dev/ ) off the top of my head. There was a little blog post about it as well ( https://julialang.org/blog/2017/08/dsl/ https://julialang.org/blog/2017/08/dsl/ )
- still_grokking 4y ago> What I’d really like to see is more support for embedded domain-specific languages within general-purpose languages. Seems to be a heavily debated topic. Scala has very strong support for eDSL (and I personally think this is good) but many people who used or use Scala are constantly arguing against it. Most likely the "With great power …" thingy applies here. You can indeed create a big mess with overused DSLs.
- rcgorton 4y ago[dead]
- dunham 4y ago> Somewhat surprisngly, indentation-sensitivity in languages like Python and Haskell had me writing cleaner-looking and more readable code by default. Yeah, I've gone from hating this, to accepting it (after using Python), to wanting it if I were to design a language (after using Haskell/Purescript/Idris). > Based on this experience alone, if I were to design a non-S-Expression-based language, it would push me toward indentation-sensitivity. The catch is that it's a pain to parse. Haskell and Python do token injection. Idris has some manual checks (with lots of holes/exceptions) and explicitly passes around an indentation (in some places). A few others conflate parsing and lexing. I've settled on having a `(layoutRow, layoutCol)` in the state and requiring `col > layoutCol || row == layoutRow && col == layoutCol`. This lets you setup the off-sides layout and still accept the first token at that column.
- dorfsmay 4y agoI use to not only agree with this sentiment but was an evengelist about it. Then fixing a lot of bugs due to people (including myself) getting indentation wrong when refactoring python got me wondering. Then languages servers convinced me indentation is best left to editors and that the cost of braces, semi-colons, etc... is tiny compared to the huge benefit of knowing with 100% certainty and at zero cognitive cost where a block starts and ends.
- brundolf 4y ago+1, since I discovered and embraced "format on save" in my editor I've been able to stop thinking about indentation almost completely
- dunham 4y agoAs I implied, I find the Haskell-like languages attractive. To the point that I am taking that approach in a toy language. But you are right, the cost of it is pretty high - not just copy/paste, but it's tricky to parse, auto-formatting is harder, LSP support (changing code) is harder, renaming variables and functions is harder, etc. (Haskell-like languages are worse than Python because they don't always start a new indentation level on a new line, so renaming can change the indentation level.) So it's probably not worth it. And I will add, like the sibling commenter, I like the auto-format on save of Go. When writing Go, I'll type whatever, often on one line, and let the formatter take care of making it pretty.
- sevensor 4y agoI was struck by this note at the end. > Of existing languages, Racket (paired with Typed Racket) probably comes closest to what I feel is the sweet spot for language design, and it may explain why I seem to pick it more than most other languages when given a choice. Racket is great, and I'm coming to appreciate it more as I spend my spare time with it. I didn't really start enjoying it until I stopped trying to pretend it was Python with delimiters. Expressions are fundamental to Racket in a way that goes way beyond syntax.
- DonaldPShimoda 4y ago> Expressions are fundamental to Racket in a way that goes way beyond syntax. This is typical of all (truly) functional languages, in my experience. They are expression-oriented rather than statement-oriented. You'll find similar behavior in, e.g., OCaml and Haskell. And since it was inspired by OCaml, you'll also find that Rust has an interesting expression-oriented approach to imperative programming, where they have essentially adopted the idea that "statements" are just unit-producing expressions that can be sequenced.