[HN Gopher] A decade of developing a programming language
       ___________________________________________________________________
        
       A decade of developing a programming language
        
       Author : YorickPeterse
       Score  : 273 points
       Date   : 2023-11-14 11:31 UTC (1 days ago)
        
 (HTM) web link (yorickpeterse.com)
 (TXT) w3m dump (yorickpeterse.com)
        
       | otoburb wrote:
       | >> _" In reality, gradual typing ends up giving you the worst of
       | both dynamic and static typing: you get the uncertainty and lack
       | of safety (in dynamically typed contexts) of dynamic typing, and
       | the cost of trying to fit your ideas into a statically typed type
       | system. I also found that the use of gradual typing didn't
       | actually make me more productive compared to using static
       | typing."_
       | 
       | The Elixir team didn't get that memo because they are actively in
       | the process of researching and working on a gradual type
       | implementation.[1]
       | 
       | [1] https://elixir-lang.org/blog/2023/09/20/strong-arrows-
       | gradua...
        
         | wk_end wrote:
         | Pursuing gradual typing makes sense when you've got an
         | established dynamic language and want to incorporate typing
         | into it (forget Elixir, let's just look at the massive success
         | of TypeScript). But as OP's recommendation says - "for new
         | languages", gradual typing has lots of costs and fewer
         | benefits.
         | 
         | To put it in other terms: my informal impression is that the
         | dynamic "features" of TypeScript are used grudgingly; the
         | community strongly pushes towards strictness, eliminating anys,
         | preferring well-typed libraries, and so on. There's little
         | appetite to - in a single project, unless absolutely required -
         | mix-and-match dynamism with staticness, which is the thing that
         | gradual typing gets you. Rather, it feels like we're migrating
         | from dynamic to static and gradual typing is just how we're
         | doing the migration. But in the case of a new language, why not
         | just start at the destination?
        
           | mikepurvis wrote:
           | I wish Python was further down this path-- I experimented
           | with adding annotations and the mypy checker to a bunch of
           | code at my company, and it seemed hopeless. Most libraries
           | didn't have annotations at all, or there were annotations
           | being maintained by a third party in a separate pypi package,
           | but the annotations were out of sync with the main project
           | and thus blew up in weird ways.
           | 
           | I feel for the people trying to develop these gradual systems
           | but it's truly a herculean task, especially in the Python
           | community that is now understandably so _extremely_ shy about
           | major breaking changes.
        
           | galdor wrote:
           | I might be missing something, but the appeal of gradual
           | typing to me is that I can mostly type functions, providing
           | safe input/output boundaries, and avoid having to type every
           | single variable (unless I have to do so for performance
           | reasons, as I do in Common Lisp).
           | 
           | This approach is comfortable to me both in Erlang and in
           | Common Lisp, I see it as a balance between
           | safety/performances and development speed (and I'm saying
           | that as someone using Go for all professional development and
           | being really happy with its full static typing).
        
             | LegibleCrimson wrote:
             | > avoid having to type every single variable
             | 
             | Most static languages don't make you type every single
             | variable anymore. Java, C++, Rust, C#, and many others let
             | you make the compiler infer types where reasonably
             | possible. That's still full static typing.
             | 
             | My Python and Rust have about the same kinds of explicit
             | type annotations in roughly the same places. My C++ has a
             | little bit more, just because `Foo obj{a}` is more
             | idiomatic than `auto foo = Foo{a}`.
        
             | andyferris wrote:
             | You can do that with type inference and a strong, static
             | type system.
        
         | munificent wrote:
         | I think the author's recommendation has some needed context:
         | 
         |  _> Recommendation: either make your language statically typed
         | or dynamically typed (preferably statically typed, but that 's
         | a different topic), as gradual typing just doesn't make sense
         | for new languages._
         | 
         | The "for new languages" part is really important. Gradual
         | typing makes a lot of sense when you are trying to retrofit
         | some amount of static checking onto an existing enormous corpus
         | of dynamically typed code. That's the case for TypeScript with
         | JavaScript and Elixir with Elixir and Erlang.
        
           | nerdponx wrote:
           | What about Julia?
        
             | adgjlsfhk1 wrote:
             | Julia isn't gradually typed. It's strongly but dynamically
             | typed.
        
           | chubot wrote:
           | Oh yeah, as far as I know Elixir has a lot of calling back
           | and forth with Erlang code. So that makes for a super tricky
           | type system problem.
           | 
           | I think the macros make it even harder. Elixir appears to be
           | done almost all with macros -- there are lots of little
           | "compilers" to Erlang/BEAM.
        
           | 7thaccount wrote:
           | Raku (formerly known as Perl 6 and a radically different
           | language than Perl 5) uses gradual typing on purpose. It's a
           | cool language that has a lot of advanced ideas, but has
           | basically been in either the design or beta stage for like
           | two decades.
        
             | fuzztester wrote:
             | >for like two decades.
             | 
             | IOW, Raku is a gradual type of language.
        
               | akoboldfrying wrote:
               | Other puns are not as good as this pun
        
           | YorickPeterse wrote:
           | You're right about this applying to new languages. I've added
           | a note to the article to (hopefully) make this more clear.
        
         | layer8 wrote:
         | What you can do is the opposite, integrate a "dynamic" type
         | into a statically-types language, like C# does:
         | https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...
         | 
         | This lets you use dynamic typing when you want, and enables
         | more seamless interoperability with dynamically-typed
         | languages.
        
           | kaba0 wrote:
           | Is it not literally the same on a type system level? Of
           | course it could be very different from a user-perspective, as
           | the ratio of `dynamic` will be _very_ low in case of C#.
        
             | layer8 wrote:
             | It's not the same, because with "dynamic" the client code
             | decides that it wants to use a specific object dynamically
             | rather than statically, whereas with gradual typing it's
             | the object/class that decides that it now has (or hasn't)
             | type annotations. Aside from that, the default is reversed.
        
         | coldtea wrote:
         | Well, there's no memo (real or as a manner of speaking). It's
         | just this person's preference.
         | 
         | I think gradual typing is a nice concept, and his arguments
         | against it amount to "gradual typing is not stating typing",
         | which is like the whole point. E.g. he goes on how the compiler
         | can't do some optimizations on functions using gradual typing,
         | but, well, it isn't supposed to anyway.
         | 
         | The benefit of gradual typing is that you can make your program
         | fully dynamic (e.g. for quick exploration), and if you want
         | more assurances and optimizations make it fully static, or if
         | you just want that for specific parts, do them static. And you
         | have the option to go for any of those 3 things from the start.
        
           | YorickPeterse wrote:
           | The problem about the whole "it's faster (productivity wise)
           | than static typing" has, as far as I know, never actually
           | been proven (certainly not through repeated studies).
           | 
           | Having worked with both dynamically and statically typed
           | languages extensively, I never felt I was _more_ productive
           | in a dynamically (or gradually) typed language compared to
           | one that was just statically typed. For very basic programs
           | you may spend a bit more time typing in a statically typed
           | language due to having to add type annotations, but typing
           | isn't what I spend most of my time on, so it's not a big
           | deal.
           | 
           | In addition, that work you need to (potentially) pay upfront
           | will help you a lot in the long term, so I suspect that for
           | anything but the most basic programs static typing leads to
           | better productivity over time.
        
             | kybernetikos wrote:
             | I think it's unfair to criticise the lack of studies for
             | that specific question without acknowledging that there are
             | exceedingly few studies that show a benefit for static
             | typing despite the fact that a huge number of people _feel_
             | that there must be an effect.
        
               | mjhay wrote:
               | IIRC there was a study that looked at the proportion of
               | Github issues labeled as bugs, by language. Clojure came
               | in with the lowest proportion. I _overall_ prefer static
               | typing, depending on what I 'm doing, but I think
               | avoiding ubiquitous mutable state is a lot more important
               | than typing.
        
             | coldtea wrote:
             | > _The problem about the whole "it's faster (productivity
             | wise) than static typing" has, as far as I know, never
             | actually been proven (certainly not through repeated
             | studies)._
             | 
             | Neither has the opposite, so there's that.
             | 
             | There's an ACM paper too ("An Experiment About Static and
             | Dynamic Type Systems", 2010) which found higher
             | productivity for the same code quality with dynamic typing.
             | Of course a few papers here and there, pro or against, are
             | as good as none. It's hardly a well studied area.
             | 
             | Besides, one or the other proven better doesn't mean much,
             | just like you liking eggs over easy vs scrambled eggs
             | doesn't depend on some study. If it works for you, and
             | you're more productive with dynamic typing or static
             | typing, use that.
             | 
             | Even if a study "proved" that one kind is "more productive"
             | based on some statistics from measuring some group, or that
             | it has "less bugs" most people when given a choice would
             | still use what they prefer and makes them, as individuals,
             | more productive and happy coding.
             | 
             | > _Having worked with both dynamically and statically typed
             | languages extensively, I never felt I was _more_ productive
             | in a dynamically (or gradually) typed language compared to
             | one that was just statically typed._
             | 
             | Depends on the type of program, the type of programming
             | (e.g. imperative/declarive/functional/logical/OO or some
             | combination and so on), the program's scale, the team size,
             | and other aspects, including individual aptitude and
             | preference, not to mention the language and its semantics
             | beyond dynamic/static (and the ecosystem too). I'd
             | certainly be way more producting using dynamic numpy than
             | some C equivalent, even if as a lib it had feature parity.
             | 
             | > _In addition, that work you need to (potentially) pay
             | upfront will help you a lot in the long term, so I suspect
             | that for anything but the most basic programs static typing
             | leads to better productivity over time._
             | 
             | There are problems where upfront work is not a benefit,
             | e.g. if it means getting behind in building your MVP or
             | getting behind a competitor adding new features faster
             | while you "perfect it", and your early stage startup loses
             | steam. Also for things where the overhead of upfront might
             | put you off from even attempting them. It can also be a
             | problem to have big upfront costs for exploratory
             | programming and searching into the problem space for your
             | design/solution.
        
         | ralmidani wrote:
         | A sibling addresses the nuance expressed in "for new
         | languages". But even if the original author's opinion was that
         | gradual typing is always bad, why does that mean we should
         | dismiss the Elixir team's efforts?
         | 
         | I have opinions about Elixir, mostly around aesthetics (for
         | context, I started programming in Python, not Ruby), but that
         | doesn't mean my opinions are objectively more valid than those
         | of a genius like Jose Valim.
         | 
         | I've actually found Elixir to be very well-designed and
         | internally consistent, and after two years using it, it's
         | obvious to me that Jose and team are very thoughtful and
         | deliberative. I don't think they want to introduce gradual
         | typing because it's trendy.
        
       | lylejantzi3rd wrote:
       | _" either make your language statically typed or dynamically
       | typed (preferably statically typed, but that's a different
       | topic), as gradual typing just doesn't make sense for new
       | languages."_
       | 
       | Is that because of type inference?
        
         | cies wrote:
         | What part of the statement "is" because of type inference?
        
         | marcosdumay wrote:
         | Your question doesn't make a lot of sense. The entirety of
         | gradual typing works by type inference, as does the static
         | typing on any new language.
        
           | Jtsummers wrote:
           | > The entirety of gradual typing works by type inference
           | 
           | Is not a correct statement. You can use gradual typing with
           | explicit type annotations or via inference, it makes no
           | difference to the concept of gradual typing. Gradual typing
           | itself is a way of handling the case of a language being
           | _both_ statically and dynamically typed and handling the
           | interaction between the two portions.
        
             | marcosdumay wrote:
             | Well, technically you can have gradual typing without
             | inference. But you'll need to annotate every single value
             | to get any matching out of it. If you do any language like
             | that, the types will be only useful for documentation
             | purposes.
        
               | Jtsummers wrote:
               | > But you'll need to annotate every single value to get
               | any matching out of it.
               | 
               | Ok, if we're taking "1 is an int" as type inferencing
               | then, yes, every statically typed language, at least
               | every mainstream one, has at least a small amount of type
               | inference since we don't, in C for instance, have to
               | annotate values. But C does not infer the types of its
               | variables or functions, nor do you have to annotate them
               | in every location where they are used. But that's not
               | _inference_ either, that 's using the information
               | determined by annotating the _variables_.
               | 
               | ----------
               | 
               | My main point was that it's weird to say the _entirety_
               | of gradual typing works by type inference. It works by
               | permitting mixing statically typed and dynamically typed
               | code together in one language. Whether the statically
               | typed portion uses type inference or more  "classical"
               | type annotations is orthogonal to the way it works under
               | the hood.
        
               | nicoburns wrote:
               | You can have an explicit "any" or "dynamic" type that can
               | be used in place of a static type where you want
               | dynamism. C# actually has this.
        
       | sesm wrote:
       | Looking at the table in the end of the article: notice how Scala
       | and Elixir both took only 3 years, because they were targeting an
       | existing platform. Clojure took only 2 years and was developed by
       | a single person.
        
         | fsckboy wrote:
         | > only 3 years, because they were targeting an existing
         | platform. Clojure took only 2 years and was developed by
         | _targeting_ a single person.
        
           | cactusfrog wrote:
           | What do you think about clojure?
        
             | fsckboy wrote:
             | I like it, and certainly much more than java which I just
             | can't. I have trouble committing to clojure fully for
             | larger projects because it's still a bit niche in terms of
             | the size of the community but nice that it interfaces with
             | java, and for smaller one-off things I just use scheme.
             | 
             | oh, clojure also is a little bit weird being a sort of
             | grab-bag collection of data structures that it inherits
             | from java and then turns lisp-ish. doesn't make it bad,
             | each thing they add is nice, just feels a little motley
             | 
             | also, i refuse to call it "closure", i pronounce the j
        
               | chii wrote:
               | > a bit niche in terms of the size of the community
               | 
               | i say it's the largest lisp like community out there tbh.
        
               | WJW wrote:
               | I like lisp but that is saying your dog is very big for a
               | chihuahua.
        
               | skrebbel wrote:
               | _also, i refuse to call it "closure", i pronounce the j_
               | 
               | Wow so edgy
        
               | orthoxerox wrote:
               | > also, i refuse to call it "closure", i pronounce the j
               | 
               | Wait, it's _not_ "clodger"?
        
               | jjgreen wrote:
               | Bravo, namers of things don't get to instruct the world
               | on how they are pronounced, we will pronounce it as it is
               | spelled. Likewise lie-nuks, ess-queue-ell, ...
        
               | sesm wrote:
               | Clojure has it's own persistent immutable data
               | structures, that are one of the main components of the
               | language. They do implement Java collection interfaces,
               | but that's mostly for passing them to existing Java
               | libraries without conversion (unlike Scala, where you
               | have to convert).
        
         | bruce343434 wrote:
         | Clojure is a lisp, it basically got to skip the parsing and
         | lexing stages
        
       | zengid wrote:
       | > "Oh, and good luck finding a book that explains how to write a
       | type-checker, let alone one that covers more practical topics
       | such as supporting sub-typing, generics, and so on"
       | 
       | I bought _Practical Foundations for Programming Languages_ by
       | Harper and _Types and Programming Languages_ by Pierce and I just
       | can't get through the first few pages of either of them. I would
       | love to see a book as gentle and fun as _Crafting Interpreters_
       | but about making a static ML like language without starting with
       | hardcore theory. (Bob, if you're listening, please make a
       | sequel!)
        
         | PhilipRoman wrote:
         | I would love to know more about what the author found difficult
         | regarding type checking. As long as you don't screw up your
         | semantics to the point of needing a full blown constraint
         | solver there should be no issues.
         | 
         | Edit: by "screwing up semantics" I mostly mean the combination
         | of overloading and implicit conversions, which is known to
         | cause issues in Java, C++, etc.
        
           | YorickPeterse wrote:
           | The problem for me was that I had a rough idea of how to
           | implement (essentially it's similar to a recursive-descent
           | parser), but I had a difficult time finding appropriate
           | resources to confirm or deny whether my idea was solid, as
           | well as tips and what not.
           | 
           | Basically, the existing material is a bunch of existing
           | incredibly complicated implementations, the odd blog post
           | that just throws a bunch of code your way without really
           | explaining the why/thought process behind it, and books that
           | aren't worth the money.
           | 
           | The result is that you can of course piece things together
           | (as I did), but it leaves you forever wondering whether you
           | did it in a sensible way, or if you constructed some weird
           | monstrosity.
           | 
           | To put it differently: you can probably build a garden by
           | digging some holes and throwing a few plants around, but
           | without the right resources it can be difficult to determine
           | what the impact of your approach may be, and whether there
           | are better ways of going about it. Oh and I'm aware there are
           | resources on gardening, it's just a metaphor :)
        
             | Q6T46nT668w6i3m wrote:
             | As someone who has written many compilers and type
             | checkers, I agree. There's very little information online
             | about the subject. I think part of the issue, for me, was
             | psychological: I felt like I was missing some
             | implementation theory that was presented alongside other
             | compiler subjects (e.g., LALR) when the reality is that
             | you're mostly implementing very simple Boolean operations.
        
             | ww520 wrote:
             | While helping a bit, it's difficult to learn or reverse
             | engineer from existing type checking code because a lot of
             | them are mundane repetitive code implementing some high
             | level theory and algorithms. The actual code is too far
             | remote from the theory. You really want to start from the
             | theory and the overall picture.
             | 
             | The Dragon book has a chapter on type checking. It gives
             | explanations on many topics. It has plenty of examples. It
             | has type treatments on different areas of a language, like
             | expression, array, struct, function, pointer, etc.
             | 
             | Despite being really old, its ideas and explanation on the
             | topic are still relevant.
        
             | mabster wrote:
             | I think the larger writings like books are generally going
             | to be written by academics so they'll be rigorous and hard
             | to read.
             | 
             | So that leaves blog posts for most developers actually
             | implementing this stuff.
             | 
             | But the solution here is that when you finally figure out a
             | good strategy to deal with something muddy like this is to
             | write that better blog post :)
        
               | YorickPeterse wrote:
               | I'm planning on doing something similar to what I did for
               | pattern matching [1]: basically building something
               | entirely standalone that fits in 2k LOC or so, and
               | explains the basics (i.e. nominal typing plus basic sub-
               | typing), hopefully such that people can then take that
               | and extend it.
               | 
               | As for _when_ I'll do that, that depends on when I can
               | convince my inner critic to actually commit to the idea
               | :)
               | 
               | [1]: https://github.com/yorickpeterse/pattern-matching-
               | in-rust
        
           | paulddraper wrote:
           | TypeScript type-checking is quite complicated. I'm sure no
           | human being on earth could pass a test of "does this compile"
           | 
           | But that's an exception, and deliberately decided to be
           | complex.
        
           | alex_lav wrote:
           | I know nothing about how one would make a type checker, but
           | it sort of feels like your comment is "As long as you don't
           | encounter any issues, there won't be any issues", no?
        
           | brundolf wrote:
           | I've been implementing a language with a TypeScript-like type
           | system, and while the core system is pretty intuitive (values
           | combining with other values, determining whether basic value
           | types fit into others), some of the fringes have been
           | challenging:
           | 
           | - Generics, especially inferring generic type params
           | 
           | - Recursive types
           | 
           | - Deeply mutable vs immutable types
           | 
           | - Type refinement based on checks/usage
           | 
           | - Giving good error messages for deep type mismatches (though
           | I'm not sure TypeScript itself has figured this one out yet,
           | lol)
           | 
           | etc. And even the stuff I've figured out, I figured out
           | almost all from scratch (sometimes having to bail and take a
           | totally new approach midway through). I would _love_ to read
           | a book giving me a proper bottom-up walkthrough of the
           | current landscape of type system implementation
        
           | nicoburns wrote:
           | Would type-inference also "screw up the semantics"? My
           | understanding is that this generally operates via some kind
           | of constraints solving.
        
             | orthoxerox wrote:
             | Depends on how much type inference you want.
             | 
             | If you want ML-style total type inference when you expect
             | the compiler to deduce the parameter and the return types
             | of your functions, then yes.
             | 
             | Local variable type inference isn't hard at all, unless you
             | want to do stuff like Rust: declare a variable without an
             | initial value and infer its type from the first assignment
             | downcode.
        
         | chubot wrote:
         | I'm in the same boat as you -- here are the two best resources
         | I found:
         | 
         | https://mukulrathi.com/create-your-own-programming-language/...
         | 
         | https://jaked.org/blog/2021-09-07-Reconstructing-TypeScript-...
         | 
         | I read through the first 10 chapters of TAPL, and skimmed the
         | rest. The first 10 chapters were good to remind myself of the
         | framing. But as far as I can tell, all the stuff I care about
         | is stuffed into one chapter (chapter 11 I think), and the rest
         | isn't that relevant (type inference stuff that is not
         | mainstream AFAIK)
         | 
         | This is also good:
         | 
         | https://github.com/golang/example/blob/master/gotypes/README...
         | 
         | And yeah some of us had the same conversation on Reddit --
         | somebody needs to make a Crafting Interpreters for type
         | checking :) Preferably with OOP and functional and
         | nominal/structural systems.
         | 
         | ---
         | 
         | Also, it dawned on me that what makes TAPL incredibly difficult
         | to read is that it lacks example PROGRAMS.
         | 
         | It has the type checkers for languages, but no programs that
         | pass and fail the type checker. You are left to kind of imagine
         | what the language looks like from the definition of its type
         | checker !! Look at chapter 10 for example.
         | 
         | I mean I get that this is a math book, but there does seem to
         | be a big hole in PL textbooks / literature.
         | 
         | Also I was kinda shocked that the Dragon Book doesn't contain a
         | type checker. For some reason I thought it would -- doesn't
         | everyone say it's the authoritative compiler textbook? And IIRC
         | there are like 10 pages on type checking out of ~500 or more.
        
           | zengid wrote:
           | Thank you so much! This looks like some good morning-with-
           | coffee reading!
        
           | ww520 wrote:
           | Are we looking at the same Dragon book? Chapter 6 the whole
           | chapter is about type checking. 6.2 spells out a type checker
           | for a sample language, admittedly in pseudo code.
           | 
           | It might not have the same lingo as the modern type checking
           | specification but most of the ideas are there. It talks about
           | type systems, type expressions, type rules, constructed types
           | like array/struct, type variables for unknown types, etc. It
           | puts the type rules in the grammar productions. It stores the
           | type info in the AST nodes. It uses the chained symbol tables
           | as the type environments.
           | 
           | It has sections on type conversion, operator/function
           | overloading, polymorphic functions (parameterized types), and
           | type unification.
           | 
           | It has all the pieces and steps to build a type checker.
        
             | chubot wrote:
             | I have the second edition, copyright 2007, and chapter 6 is
             | "intermediate code generation". The whole chapter is
             | definitely not about type checking -- is yours?
             | 
             | https://www.amazon.com/Compilers-Principles-Techniques-
             | Tools...
             | 
             | It has 11 sub-sections, 2 of which are about type checking
             | 
             | 6.3 - Types and Declarations, pages 370-378
             | 
             | 6.5 - Type Checking - pages 386 - 398
             | 
             | So that's about 20 pages out of 993 pages. A fraction of a
             | chapter!
             | 
             | ---
             | 
             | What I was referring to is the appendix "A Complete Front
             | End", which contains a lexer and parser, but not a type
             | checker! Doesn't sound complete to me.
             | 
             | I guess they have a pass to create the three-address code,
             | and type checking is only in service of that. But they
             | don't give you the source code!
             | 
             | ---
             | 
             | Also weirdly, when you look at the intro chapter "The
             | structure of a compiler", it goes
             | 
             | 1.2.2 Syntax Analysis
             | 
             | 1.2.3 Semantic Analysis (3 paragraphs that mentions type
             | checking and coercions)
             | 
             | 1.2.4 Intermediate Code Generation
             | 
             | But when you look at the chapters, there's NO semantic
             | analysis chapter! It goes from (4) Syntax analysis, (5)
             | Syntax-directed translation, to (6) intermediate code
             | generation.
             | 
             | I think there's a missing chapter! (or two)
        
               | ww520 wrote:
               | I have the 1st edition printed 1988. Chapter 6 is Type
               | Checking, chapter 7 is Runtime Environments, and chapter
               | 8 is Intermediate Code Generation.
               | 
               | WTF. The 2nd edition is really butchered.
        
               | chubot wrote:
               | Wow yeah. So in the first edition pages 343 to 388 is a
               | full chapter on type checking. It looks much more
               | coherent.
               | 
               | What the heck happened ...
        
               | adastra22 wrote:
               | Standard thing: older textbooks are generally better.
               | They tend to get dumbed down in new revisions, and
               | modified to fit stupid standards about class syllabus.
        
               | chubot wrote:
               | This reminds me that I actually bought the first edition
               | of the GC handbook on purpose, because it had more C++
               | material, which was relevant to my project.
               | 
               | The second edition cut a bunch of C++ material out.
               | 
               | And yeah I don't think I missed anything -- I think it
               | even had better diagrams.
               | 
               | So yeah I believe that general rule! I didn't realize
               | that at the time I bought the Dragon Book.
               | 
               | Pretty sad how you can lose knowledge over time!
               | 
               | ---
               | 
               | The books also respond to fashion -- e.g. Java was a
               | popular teaching language, so they had to add more Java.
               | I think that is reasonable in some ways but can backfire
               | in the long term.
               | 
               | Although I guess I am not too excited about MIX assembly
               | in TAOCP and so forth, so it's a hard problem
        
               | adastra22 wrote:
               | Obviously sometimes the updates are fixing real critiques
               | of earlier editions. Those are good. But many times it is
               | "fixing" critiques along the line of "cut out advanced
               | material because it is too much to cover in 1 semester."
               | Makes sense in an academic context, but not for
               | professionals like you and me that are looking for that
               | kind of stuff.
               | 
               | Books also tend to become more sparse on the page, and
               | with more pointless "illustrative" stock photos that add
               | nothing and take up page real estate (leading to more
               | advanced topics being dropped to reclaim space), but make
               | the book seem more approachable to undergraduates.
               | 
               | Generally applies more to physics, chemistry, and math
               | books than computer science, but it is a general
               | phenomenon.
        
         | azdavis wrote:
         | Might this help? I wrote it: https://azdavis.net/posts/define-
         | pl-01/
        
         | efnx wrote:
         | I highly recommend https://github.com/sdiehl/write-you-a-
         | haskell as it is very developer friendly. It's not complete,
         | but it really gets the gears turning and will set you up for
         | writing your own Hendley-Milner style type checker.
        
         | andrewcobby wrote:
         | I had a similar experience this year as I'm working on an ML-
         | cousin to Golang. I actually found that ChatGPT (gpt4) was
         | really good and breaking down the step of implementing a
         | Hindley-Milner type system in a language I could understand. It
         | could also provide follow up clarifications to my questions
         | too. I then implemented it a small bit at a time and as the
         | complexity grew I started to better understand the algorithm.
         | 
         | EDIT: ChatGPT could actually demonstrate the whole process for
         | type checking small chunks of code, including: assigning them
         | type variables, collecting constraints and then unification. It
         | would even then point out if there was a type error in the code
         | snippet!
        
           | chewxy wrote:
           | I'm curious how you went. Here's my attempt:
           | https://github.com/chewxy/hm , the core of which (i.e. the
           | interface type) is what powers most of the languages I wrote
           | (different languages I wrote have different unification
           | schemes)
        
             | andrewcobby wrote:
             | Cool to see an implementation in Go, nice! Here's where I
             | got to with my first attempt:
             | https://github.com/cobbweb/flite. There's definitely stuff
             | I'd do a bit different, my second attempt is actually a
             | project to learn Rust with, but I'm already considering
             | switch to Ocaml haha!
        
         | ashton314 wrote:
         | I've written a type checker or two. My experience:
         | https://lambdaland.org/posts/2022-07-27_how_to_write_a_type_...
         | 
         | I left some links/references to books that helped me out.
        
         | gilmi wrote:
         | I've mentioned this in a different comment, but I've written
         | several articles on type inference, trying to make the topic
         | more approachable:
         | 
         | https://gilmi.me/blog/tags/type%20inference
         | 
         | If you find that helpful, I've made more content on compilers
         | (including live coding a compiler, without narration) which
         | should be easily reachable from my website.
        
         | ReleaseCandidat wrote:
         | The bigger challenge is doing the pattern matching and mainly
         | the exhaustiveness checking of patterns which aren't
         | constructors of a sum type, but for example integer intervals.
         | 
         | Simon Peyton Jones together with others wrote a relatively easy
         | to read book about the writing of Miranda (Haskell), which even
         | includes a simple explanation of lambda calculus, so it
         | includes everything you need to know about the theory, more or
         | less: "The Implementation of Functional Programming Languages"
         | 1987, https://www.microsoft.com/en-us/research/wp-
         | content/uploads/...
        
         | trealira wrote:
         | > about making a static ML like language without starting with
         | hardcore theory.
         | 
         | The book _Applicative Order Programming: The Standard ML
         | Perspective_ has you implement an ML-like language at the end
         | (the book is mostly about programming in Standard ML itself).
         | Chapter 10 covers type checking and type inference. Chapter 11
         | covers interpretation via graph reduction. Chapter 12 covers
         | compilation of the ML-like language to an abstract stack
         | machine. The code is all in Standard ML. It 's very direct and
         | practical without getting bogged down in theory, although it
         | does talk about theory, and it's not as gentle and fun as
         | Crafting Interpreters.
         | 
         | It was published in 1991. It's on libgen; I'd recommend
         | downloading a PDF and reading those chapters in order to judge
         | for yourself.
        
         | orthoxerox wrote:
         | > I would love to see a book as gentle and fun as _Crafting
         | Interpreters_ but about making a static ML like language
         | without starting with hardcore theory. (Bob, if you're
         | listening, please make a sequel!)
         | 
         | I don't think an ML-like language is the best option. I would
         | start with type-checking something simple like C or Pascal: no
         | overloading, generics, type inference, anonymous functions.
         | Just plain old subroutines and declared types on everything,
         | arrays and structs as the only composite data structures.
         | 
         | Then you could add implicit conversions, type inference for
         | locals (not that hard), subtyping, target type inference for
         | anonymous functions, generics and maybe overloads if you're
         | brave enough.
        
         | ihnorton wrote:
         | "Modern Compiler Implementation" by Appel is definitely worth a
         | look. I read the ML version, and the code is really
         | clean/readable (not sure about the C and Java versions).
        
       | coldtea wrote:
       | > _" In reality, gradual typing ends up giving you the worst of
       | both dynamic and static typing: you get the uncertainty and lack
       | of safety (in dynamically typed contexts) of dynamic typing, and
       | the cost of trying to fit your ideas into a statically typed type
       | system. I also found that the use of gradual typing didn't
       | actually make me more productive compared to using static
       | typing._
       | 
       | Well, that's like, your opinion, man...
        
       | orthoxerox wrote:
       | "Avoid writing your own code generator, linker, etc"
       | 
       | There's a certain dearth of pluggable code generators and
       | linkers. Well, not on GNU/Linux, where you get both as(1) and
       | ld(1) practically out of the box, but making your compiler emit a
       | PE/COFF on Windows is a pain. You either bring your own homemade
       | codegen and linker or use LLVM, using Microsoft's ml64.exe and
       | link.exe is incredibly impractical.
        
       | packetlost wrote:
       | As someone who has worked with massive typed Python codebases, I
       | 100% agree with the author on Gradual Typing. It's literally the
       | _worst_ of both worlds. It 's actually even worse than that
       | because it gives the _illusion_ of some safety when there isn 't
       | any, especially in Python where most of the tooling "fails open"
       | by default and won't tell you something is wrong despite having
       | annotations.
        
         | Q6T46nT668w6i3m wrote:
         | Any? I regularly find bugs. I don't find every bug, but finding
         | some bugs is better than none. Especially for the minimal cost
         | of writing annotations.
        
           | lmm wrote:
           | > I don't find every bug, but finding some bugs is better
           | than none.
           | 
           | I used to think this, but based on experience I'm now less
           | convinced. Finding most bugs, like real static typing does,
           | is great; you can significantly reduce your test coverage and
           | iterate with more confidence. Finding a few bugs is pretty
           | useless if you're not finding enough to actually change your
           | workflow.
        
             | LegibleCrimson wrote:
             | Finding a few bugs is very useful if they're the kind of
             | bugs that can cause problems in production but usually not
             | in development or testing, like not checking an optional
             | that is very rarely null.
             | 
             | It's not about workflow or finding enough bugs, but finding
             | bugs that you might not have otherwise seen can be
             | monumentally beneficial.
        
               | lmm wrote:
               | > Finding a few bugs is very useful if they're the kind
               | of bugs that can cause problems in production but usually
               | not in development or testing, like not checking an
               | optional that is very rarely null.
               | 
               | There's a kind of excluded middle here though. Either
               | that kind of bug hits production often enough to matter -
               | in which case a checker that catches it sometimes isn't
               | good enough, you need a checker that eliminates it
               | completely. Or it doesn't hit production often enough to
               | matter, in which case a checker is of limited use.
        
         | LegibleCrimson wrote:
         | I disagree, as somebody who has also worked with tons of type-
         | hinted Python code. Gradual typing is obviously worse than real
         | static typing, but it's a step up from full dynamic typing. It
         | has caught many bugs for me before hitting them in runtime, and
         | provides good documentation about what I can expect a function
         | to accept and return without forcing me to read prose.
         | 
         | Type hints don't provide any safety, though. That was never the
         | goal, given that they're strictly optional and don't really
         | exist at runtime anyway (though I have written some
         | experimental monstrosities that used annotations for code
         | generation at runtime). They're documentation in a standard
         | form that static analysis tools can leverage.
         | 
         | I really can't imagine a situation where having type hints in
         | Python is worse than simply not having them. They're not the
         | worst of both worlds, they're a compromise with some of the
         | benefits and drawbacks of each.
        
           | Philpax wrote:
           | > I really can't imagine a situation where having type hints
           | in Python is worse than simply not having them.
           | 
           | Can't they lie / go out of sync with what's actually
           | happening? IMO, an unenforced type hint is very dangerous
           | because of this - it can give you a false sense of confidence
           | in a scenario where you would otherwise write more defensive
           | code.
        
             | actuallyalys wrote:
             | Yes, if you don't run the type-checker regularly and just
             | run the code itself, there's nothing preventing them from
             | going out of sync. Type hints are similar to tests in that
             | way.
        
             | nicoburns wrote:
             | I suspect Python is particularly bad in this regard. For
             | example, PHP's gradual typing is enforced with runtime
             | assertions.
        
             | packetlost wrote:
             | Yes, this is my whole point. Mypy by default will silently
             | _allow_ blatantly incorrect typing which is worse than no
             | types IMO.
        
             | LegibleCrimson wrote:
             | I suppose, if you're relying on type stubs. Ordinary in-
             | line type hints won't lie or go out of sync without
             | complaining somewhere though.
             | 
             | Defensive code is good, but I'm absolutely sick of writing
             | code that has to be defensive about the type of absolutely
             | every variable everywhere. It's ridiculous, verbose, and
             | nearly impossible to do universally. THAT is the worst of
             | both worlds. Having to manually fret about the types that
             | every variable may hold. At the point that you're having to
             | write defensive code due to dynamic types, you've already
             | lost the advantages of dynamic types entirely.
             | 
             | I use type hints to say "use and expect these types or get
             | Undefined Behavior", and that contract is good enough for
             | me.
        
               | packetlost wrote:
               | > Ordinary in-line type hints won't lie or go out of sync
               | without complaining somewhere though
               | 
               | This is demonstrably untrue with the default
               | configuration of Mypy. It will silently ignore explicit
               | hints, inline or not, in certain situations.
        
               | LegibleCrimson wrote:
               | I'm going to need an example of that. I tend to use
               | Pyright, but when I used MyPy, I never had it simply
               | ignore explicit hints.
               | 
               | If it's demonstrably untrue, you should be able to
               | demonstrate it, right?
        
               | packetlost wrote:
               | Here's the quickest, simplest example I could come up
               | with off the top of my head:
               | 
               | https://paste.sr.ht/~chiefnoah/7e07a961cf266fa620d1fd2d31
               | ba2...
               | 
               | This particular issue will get picked up with `--strict`,
               | but it's nearly impossible to do that on a large codebase
               | with typing added post-hoc.
               | 
               | Pyright has saner defaults, it catches this particular
               | issue: https://paste.sr.ht/~chiefnoah/80816fded2a08a03ca8
               | 0804d524ee...
        
               | LegibleCrimson wrote:
               | Thanks for the example. That's happening because the
               | `main` function is an untyped function. You can fix that
               | by changing `def main():` to `def main -> None:` You can
               | also make it pick up with `--check-untyped-defs`. I
               | always used mypy with --strict, so I forgot that it
               | wasn't the default configuration.
               | 
               | I agree, though. Pyright has better defaults. It's not
               | great that mypy just completely skips all functions
               | without typing information given. It makes a lot more
               | sense for it to just check everything and assume Any on
               | all unspecified arguments and returns. It's still
               | valuable to type-check the contents of functions with
               | unspecified types.
        
         | ric2b wrote:
         | How is it worse than no typing? If you're not testing
         | adequately because you think type safety is enough, you done
         | f'd up.
        
         | Jtsummers wrote:
         | Python's type hints are just that, hints. They aren't static
         | type annotations. Cython uses gradual typing, but the
         | mainstream Python implementation does not actually offer
         | gradual typing. It's still dynamically typed, but with an
         | annotation mechanism that lets other tools do some analysis for
         | you.
        
         | JonChesterfield wrote:
         | Gradual typing is great. The parts you want precision on are
         | statically typed, the parts that don't matter dynamic, and they
         | fit together sanely at the language level.
         | 
         | Writing type annotations in comments for an optional
         | preprocessor to pass judgement on before the actual
         | implementation ignores them is a bad thing. But that's on
         | python, not on gradual typing.
         | 
         | (An any type + static is also fine if you have a way to
         | retrieve type information at runtime, and there's a sense in
         | which template instantiations of a generic function and a
         | function taking arguments of type any are the same thing as
         | each other)
        
         | jasonwatkinspdx wrote:
         | I know someone that was on the Dart team and they say the same
         | thing.
        
         | jampekka wrote:
         | It's not the gradual typing. It's that static typing gives you
         | the illusion of some safety where there isn't any.
        
       | tabtab wrote:
       | The only "good" programming language is the language I make!
       | Everybody thinks different and wants to optimize for different
       | things. There is no existing language I've found that I "love";
       | they each have features I like but none have all the features
       | together.
       | 
       | I've drafted up a language called "Moth" that has a lot of "meta
       | power" to program block scope any way you want, as most languages
       | hard-wire scoping rules, which I find limiting. Things like
       | "classes", "functions", while-loops etc. would be defined by
       | libraries, NOT the language. It's like lambda's on steroids and
       | without bloated arrow syntax. But it may run slow as molasses, as
       | scoping meta power adds lots of compiler/interpreter indirection.
       | 
       | However, it may turn out that only a few scoping rules are
       | practical in most cases, and the compiler could then optimize for
       | those. It would then only be slow if you are doing something
       | "weird" with scope. Stick to a fixed known set, and things zip
       | along. But finding that set requires R&D and road testing.
        
         | matheusmoreira wrote:
         | > The only "good" programming language is the language I make!
         | Everybody thinks different
         | 
         | Yeah. There's always one little thing or another that bothers
         | me in every language I've learned. Guess I've just come full
         | circle now that I've finally made my own. No doubt it will
         | bother someone else too. If anyone ever uses it.
        
         | sparkie wrote:
         | Kernel[1] has the ability to make scoping more abstract, but in
         | a clean way which doesn't introduce the "spooky action at
         | distance" of full dynamic scoping. It's an improvement on the
         | older fexprs.
         | 
         | Essentially, you have a lambda-like combiner called an
         | operative which does not reduce its operands, and implicitly
         | receives a reference to the caller's dynamic environment as an
         | additional argument. The operative body can then optionally
         | evaluate anything as if it were the caller, with even the
         | ability to mutate the caller's local scope. The parent scopes
         | of the caller are accessible for evaluation, but not mutation.
         | You can only mutate an environment for which you have a direct
         | reference - and the environments themselves are first-class
         | values which you can pass around and store in other
         | environments. It is only possible to mutate the parent
         | environment of a caller if the caller's caller has passed a
         | reference to its own environment, and the caller forwards that
         | reference explicitly.
         | 
         | You can create empty environments or environments from an
         | initial set of bindings, and build on them from there, and you
         | can also isolate the static environment from a callee, ensuring
         | that only a limited set of bindings are accessible to the
         | callee. In effect, this enables a kind of "sandboxing" approach
         | - where you can easily do things like load an external plugin
         | which can be written using a limited subset of Kernel features,
         | and only access the specific functions of your host program
         | which you allow. It provides a lot of abstractive power, with
         | the ability to have things like types and classes defined as
         | libraries. The operatives can introduce "sub-languages", which
         | can simulate the behavior of other language semantics.
         | 
         | As you suspect, this runs pretty slow because it's almost
         | impossible to optimize ahead-of-time. A given piece of code is
         | just a tree of symbols and self-evaluating atoms, and the
         | symbols in some code are only given any functional meaning by
         | the environment it is evaluated in. Kernel busts the myth that
         | "compilation vs interpretation is just an implementation
         | choice," and explores what can happen when we go all-in on
         | interpretation. The author has written about the nature of
         | interpreted programming languages and how this differs from
         | compiled languages.[2]
         | 
         | There are opportunities to have compilation and performance
         | without _much_ loss of abstractive power, but this is not a
         | focus of Kernel - whose design goals are described at detail in
         | the report. I 've done a lot of R&D on making performance
         | acceptable for a Kernel-like language. The main insight is that
         | you should be able to make _some_ assumptions about the
         | environment passed to an operative without knowing anything
         | else about the environment. I do this by modelling environments
         | as row polymorphic types, where the operative specifies a set
         | of bindings it expects the caller 's dynamic environment to
         | contain, complete with type signatures, and assumes these
         | bindings behave in some specific way. (Eg, that the binding `+`
         | means addition, which is in no way guaranteed by Kernel, and
         | that the type signature of `+` is `Number, Number -> Number`).
         | 
         | ---
         | 
         | A mostly complete implementation of Kernel:
         | https://github.com/dbohdan/klisp (Cloned from Andres Navarro's
         | bitbucket repo which is no longer available).
         | 
         | Some performance improvements of klisp, with a lot of hand-
         | written 32-bit x86 assembly:
         | https://github.com/ghosthamlet/bronze-age-lisp (Cloned from Oto
         | Havle's bitbucket repo, also no longer available).
         | 
         | Another implementation of Kernel, with some examples of
         | defining records, classes, objects, generators etc as
         | libraries: https://github.com/vito/hummus/tree/master
         | 
         | ---
         | 
         | [1]:http://web.cs.wpi.edu/%7Ejshutt/kernel.html
         | 
         | [2]:https://fexpr.blogspot.com/2016/08/interpreted-
         | programming-l...
        
       | bsder wrote:
       | > Avoid self-hosting your compiler
       | 
       | Not sure this is so problematic anymore.
       | 
       | The Zig folks targeted WASM so that they can bootstrap without
       | needing a second compiler implementation. Compilers don't need a
       | lot of POSIX in order to be functional.
        
         | zengid wrote:
         | Oh that's a great point! Didn't someone give a talk about that
         | too?
        
           | bsder wrote:
           | No video here, but discussion:
           | https://ziglang.org/news/goodbye-cpp/
           | 
           | Welp, video: https://www.youtube.com/watch?v=MCfD7aIl-_E
        
       | karmakaze wrote:
       | Related post "Inko Programming Language" which might get some
       | interesting comments.
       | 
       | https://news.ycombinator.com/item?id=38270265
       | 
       | https://inko-lang.org/
        
       | Joel_Mckay wrote:
       | Most fringe languages are vanity projects that address a limited
       | number of use-cases.
       | 
       | There are always language specific features that may reduce a
       | given problems implementation complexity, but it often depends
       | how much time people are willing to commit to "reinventing the
       | wheel". If you find yourself struggling with support scaffolding
       | issues instead of the core challenge, than chances are you are
       | using the wrong tool.
       | 
       | https://m.xkcd.com/927/
       | 
       | I am not suggesting Erlang/Elixir, Lisp and Julia are perfect...
       | but at least one is not trying to build a castle out of grains of
       | sand, The only groups I see freeing themselves of C/C++ library
       | inertia is the Go and Julia communities to a lesser extent.
       | 
       | Have a wonderful day, =)
        
         | matheusmoreira wrote:
         | It's a vanity project until it isn't. I don't know what makes a
         | language succeed but it's clearly possible. I've seen quite a
         | few languages rise from small to widely used. Zig is a
         | prominent example but there are others.
        
           | Joel_Mckay wrote:
           | I often ponder if it is a function of the breadth of language
           | use-cases within a problem domain, and or developer effort
           | minimization.
           | 
           | C/C++ tend to be CPU bound languages that are tightly coupled
           | to the Von Neumann architectures. Anything that is not
           | directly isomorphic tends to not survive very long unless its
           | for a VM, and supports wrapper libraries.
           | 
           | Best of luck, =)
        
           | kaba0 wrote:
           | I wouldn't call Zig widely used just yet. It is definitely
           | not in the same camp as hobby languages, but it is still
           | incredibly small.
           | 
           | Nonetheless, it is a very welcome addition to the low-level
           | landscape with some cool ideas.
        
             | matheusmoreira wrote:
             | I can only hope my own language will become "incredibly
             | small" like that one day. :)
             | 
             | Even if a language doesn't take off, its ideas can make a
             | difference and influence future designs. The creator of Zig
             | certainly made an impression on me with this talk:
             | 
             | https://youtube.com/watch?t=120&v=Gv2I7qTux7g
             | 
             | Even if Zig hadn't been successful, the ideas it represents
             | would've enriched the world. Gives me hope I'll be able to
             | make something out of my own ideas too one day.
        
         | jstimpfle wrote:
         | Good god, all is well but can we please all stop posting this
         | xkcd :p
        
           | Joel_Mckay wrote:
           | So, you are proposing a new standard of satire. =)
        
       | macintux wrote:
       | Related discussion: https://news.ycombinator.com/item?id=38270265
        
       | matheusmoreira wrote:
       | > I used an S-expression syntax, instead of designing my own
       | syntax and writing a parser for it.
       | 
       | > This meant I was able to experiment with the semantics and
       | virtual machine of the language, instead of worrying over what
       | keyword to use for function definitions.
       | 
       | Yeah. I see a lot of people asking why people are so fascinated
       | by lisp and why there are so many lisps out there. I think this
       | is a huge reason. It certainly was for me.
       | 
       | I just wanted to get some ideas working as soon as possible. Lisp
       | is the easiest language to parse that I've ever seen, managed to
       | write a parser by hand. And yet it's a fully featured programming
       | language. It just gives you huge power for very low effort. It
       | took a single bit to add metaprogramming to my lisp:
       | if (function.flags.evaluate_arguments) {         arguments =
       | evaluate_all(interpreter, environment, arguments);       }
       | 
       | I see it as a little frontend for my data structures. I get to
       | work on them endlessly and everything I do improves something.
        
         | kaba0 wrote:
         | I found that a naive frontend for a language is not that big of
         | a deal with something like ANTLR. The benefit is that you have
         | a formal grammar syntax description, and you can iterate on
         | that endlessly without too many code changes down the line.
        
           | ReleaseCandidat wrote:
           | It isn't with a handwritten lexer/parser either, the real
           | work starts after the parsing is complete. Of course, there
           | are two possibilities: often changing them can either result
           | in a cleaner, more readable result or a _real_ mess ;) But
           | you have to rewrite it by hand anyways if you want to get
           | better (or usable) error messages and incremental parsing for
           | a LSP.
        
         | danybittel wrote:
         | I see lisp as sort of "no syntax". It's basically the AST.
         | Which is a good thing, you can concentrate on the compiler and
         | add syntax later. If you are working on a compiler for a non
         | lisp PL, it makes sense to log the AST after parsing, as a
         | lisp, as a readable AST.
        
         | lolinder wrote:
         | > Lisp is the easiest language to parse that I've ever seen,
         | managed to write a parser by hand.
         | 
         | I've written a dozen or so parsers for different languages by
         | hand: the parser is easy _regardless_ of the syntax you choose.
         | The complicated bit is always compilation /interpretation.
         | 
         | The biggest value I see to using S-expressions isn't the time
         | saved in writing a parser, it's reducing the amount of
         | bikeshedding you'll be tempted to do fiddling with syntax.
        
           | matheusmoreira wrote:
           | Aren't languages like C notoriously difficult to parse? I've
           | read that they're actually context sensitive. IIRC much of
           | Go's syntax was designed to avoid parsing complexity.
        
             | shaftoe444 wrote:
             | See the clockwise/spiral rule!
        
               | jstimpfle wrote:
               | Type syntax started pretty easy to parse, basically just
               | parse a C expression. It became more complex and
               | inconsistent later (when for example types for function
               | parameters were added), but IIRC the clockwise/spiral
               | rule is nothing like the idea behind the syntax. I think
               | it is too complicated / not getting the gist of it.
        
             | kazinator wrote:
             | I suspect that the interpretation of C cannot be specified
             | in a brief page of C code. For one thing, the language
             | doesn't supply the data structures for representing any
             | aspec of itself; the code will have to invent those:
             | declarations, definitions, statements, types. Then handle a
             | ton of cases.
             | 
             | It will not be obvious that what the C code is doing is C
             | interpretation, because all those data structures don't
             | resemble the C syntax.
             | 
             | The Lisp meta-circular interpreter side-steps that; the
             | issue is settled elsewhere. It exists against a backdrop
             | where the easy correspondence between the printed syntax
             | and the data structure being handled by the interpreter is
             | taken for granted.
             | 
             | The C would need the same kind of backdrop; and that would
             | be a lot of documentation.
        
             | lifthrasiir wrote:
             | > Aren't languages like C notoriously difficult to parse?
             | I've read that they're actually context sensitive.
             | 
             |  _All_ programming languages with identifiers are context-
             | sensitive if you put well-formedness into the grammar. C is
             | not exactly difficult to parse, rather it just needs some
             | specific approach compared to most other languages. The
             | biggest (and technically the only [1]) ambiguity is a
             | confusion between type-specifier and primary-expression,
             | which is a roundabout way to say that `(A) * B` is either a
             | multiplication or a dereference-then-cast expression
             | depending on what `A` is in the current scope. But it also
             | has a typical solution that works for most parsers: parser
             | keeps a stack of scopes with contained identifiers, and
             | lexer will distinguish type identifiers from other
             | identifiers with that information. This approach is so
             | popular that even has a name  "semantic feedback".
             | 
             | [1] As an example, the notorious pointer and array
             | declaration syntax is actually an unambiguous context-free
             | grammar. It is notorious only because we humans can't
             | easily read one.
             | 
             | > IIRC much of Go's syntax was designed to avoid parsing
             | complexity.
             | 
             | Go's _semantics_ (in particular, name resolution and module
             | system) was designed to avoid complexity. Its syntax is
             | quite normal, modulo personal and subjective bits which all
             | languages have. I can 't see any particular mention of
             | syntax decision to performance in the initial spec document
             | [2].
             | 
             | [2] https://github.com/golang/go/blob/18c5b488a3b2e218c0e0c
             | f2a7d...
        
             | jstimpfle wrote:
             | For the context sensitive part, all you need is a symbol
             | table to check which names are for types. I haven't
             | actually done it but I don't think it is hard.
             | 
             | The real problem is the preprocessor, it's very hard to
             | implement, and implement in a standards-compliant way.
             | (haven't finished one either, but everybody says so). And
             | without preprocessor and proper include processing, you
             | simply can't parse correctly because you lack the context
             | of which types are defined (besides missing preprocessor
             | symbols).
             | 
             | Another problem that comes with that is that you always
             | have to parse everything from beginning to end -- including
             | the include files, which can easily be 10s or 100s of
             | thousands of lines of code. I haven't seen a real
             | performant and always correct parser for C IDEs, not sure
             | if one exists.
        
           | kazinator wrote:
           | That rings untrue in the face of how John MacCarthy specified
           | Lisp interpretation, on paper, in Lisp itself, quite briefly,
           | and didn't even suspect it would be executable. Then Steve
           | Russel comes along, and bootstraps it by hand-translating it
           | to code.
        
       | danybittel wrote:
       | Your mileage may vary. I think It's a good article. But sometimes
       | a PL has to go down an unconventional path to make something new.
       | If you follow a best practice "rulebook", your PL will be like
       | every other PL.
       | 
       | I think the most important part is figuring out _who_ is going to
       | use the PL and _what_ problems they are solving. As precisely as
       | possible, best with a list of concrete Personas and Projects.
       | This creates a  "value system", which makes it easier to answer
       | all questions during implementation.
        
         | grumpyprole wrote:
         | > sometimes a PL has to go down an unconventional path to make
         | something new.
         | 
         | Absolutely, just look at Haskell as an example.
         | 
         | > most important part is figuring out who is going to use the
         | PL and what problems they are solving.
         | 
         | Completely agree. Rust and Go have received a lot of criticism
         | over the years, but I think that is often down to them not
         | being as general purpose as many people try to claim. The
         | creators had particular users and use cases in mind.
        
           | prmph wrote:
           | Maybe people just like to use general purpose languages. It
           | should not be too hard to marry the good parts of major
           | language families
        
             | nyssos wrote:
             | > It should not be too hard to marry the good parts of
             | major language families
             | 
             | It is. Language features generally aren't isolated modules
             | that you can add and remove freely: they interact, and "the
             | good parts" are only good because they play nicely with
             | everything else. You can't have Lisp macros without Lisp
             | (or otherwise homoiconic) syntax, or Haskell's concurrency
             | without its effect tracking, or global type inference with
             | Java-style inheritance.
        
           | rob74 wrote:
           | I think Rust and Go have received that criticism also because
           | they precisely "dared" to take an unconventional approach: Go
           | is very opinionated and uses a radically simplified syntax
           | without inheritance, exceptions and other things people are
           | used to from other languages, which leads to many people
           | trying to bend Go to their style of programming and getting
           | frustrated; Rust has its focus on performance ("zero cost
           | abstractions") and being memory safe without using GC, which
           | leads to its famously steep learning curve. But I agree with
           | OP: if you are going to start a new programming language
           | today, you'd better try to bring some new ideas to the table,
           | otherwise you're just crowding an already crowded space even
           | more.
        
           | randomdata wrote:
           | _> I think that is often down to them not being as general
           | purpose as many people try to claim._
           | 
           | Is that the claim actually that they are general purpose, or
           | do people not understand the claims made? Go has been very
           | explicit that it is designed for systems programming. The
           | Rust camp seems to hold a similar position. "Systems" implies
           | that it is not designed for all tasks. But I'm not sure
           | "systems" is properly understood by a lot of people.
        
           | fho wrote:
           | Fun fact: Haskell was established by a committee based on
           | best practices from a jungle of ML-type languages.
        
       | ashton314 wrote:
       | > In reality, gradual typing ends up giving you the worst of both
       | dynamic and static typing: you get the uncertainty and lack of
       | safety (in dynamically typed contexts) of dynamic typing, and the
       | cost of trying to fit your ideas into a statically typed type
       | system.
       | 
       | Depends on how you implement gradual typing. There's a spectrum
       | [1] of gradual languages, and the differences between, say
       | TypeScript and Reticulated Python matter a great deal.
       | 
       | It is true that you can have some serious performance hits on the
       | migration from dynamically typed -> fully typed code; my advisor
       | Ben Greenman is doing work examining that space of performance en
       | route to fully-typed systems.
       | 
       | In practice, there are several gradually typed languages that
       | give you very good performance when all the code is typed--i.e.
       | you don't have to pay a cost just for having dynamic in your
       | language. You might have to pay a cost when you use it, and the
       | cost can vary dramatically.
       | 
       | Note that's just performance--sometimes performance matters less
       | than being able to prototype something fast in a language, which
       | is a feature I find valuable.
       | 
       | > In fact, the few places where dynamic typing was used in the
       | standard library was due to the type system not being powerful
       | enough to provide a better alternative.
       | 
       | Hey, that sounds like either a win for gradual typing, or a sign
       | that your type system needs some serious work! ;-)
       | 
       | [1]:
       | https://prl.khoury.northeastern.edu/blog/2018/10/06/a-spectr...
        
       | gilmi wrote:
       | I really enjoyed the article.
       | 
       | I think one way to simplify language creation is to use an
       | existing language with somewhat similar operational semantics as
       | a compilation target. This simplifies the backend a lot and
       | leaves more time to explore what the language (frontend) should
       | look like. The backend can always be rewritten at a later time.
       | My personal choice is usually JavaScript[1].
       | 
       | Regarding type checkers/type inference, I've also ran into
       | difficulties with this topic, and I've written several articles
       | trying to make it more approachable[2].
       | 
       | [1]: https://gilmi.me/blog/post/2023/07/08/js-as-a-target
       | 
       | [2]: https://gilmi.me/blog/tags/type%20inference
        
       | zubairq wrote:
       | Having spent a decade writing my own programming language it is
       | funny to read these recommendations. I don't think it is fair to
       | say what someone should not do, as writing a programming language
       | by definition is ridiculously hard!
        
       | peefy wrote:
       | I personally enjoy this article very much, just like what we are
       | doing in KCL language [1]. KCL is a cloud native configuration
       | and policy language, and we have endowed KCL with a semantic
       | level gradual type system. It is a bit like Typescript and you
       | use Javascript to write some project configurations with the
       | Typescript type checker, but we have not completely discarded
       | runtime type checking because as a configuration language, we
       | need to strike a balance between efficiency and stability.
       | 
       | But on the other hand, we are currently facing certain
       | challenges. KCL is somewhat ambiguous in grammar and semantics
       | e.g. the record type, and we are working hard to improve it.
       | 
       | [1] https://github.com/kcl-lang
        
         | peefy wrote:
         | It should be added that we also use Rust to fully build the
         | front-end of the language and compile it into native code using
         | LLVM.
        
       | gavinhoward wrote:
       | As someone who has been designing a language since late 2012 (and
       | it's about to have its first public release early next year O.o),
       | here are some thoughts.
       | 
       |  _Avoid gradual typing_ : Absolutely. I had gradual typing and
       | abandoned it. Gradual typing is _not_ as good as simple Go-like
       | inference, and that simple inference is easy to implement and
       | takes care of 95% of the  "problem."
       | 
       | However, you _also_ need one or more dynamic typing escape
       | hatches. When I implemented a config file format by tweaking
       | JSON, I had to implement dynamic typing in C.
       | 
       |  _Avoid self-hosting your compiler_ : It depends. You probably
       | should avoid it by default, unless your language is just _so_
       | much better than the original. Rust is an example of this
       | (compared to C++). I 'm writing in C, and I want memory safety
       | [1], so bootstrapping makes sense.
       | 
       |  _Avoid writing your own code generator, linker, etc._ : This is
       | _excellent_ advice! Use the standard tools. Of course, me being
       | me, I 'm breaking it. :) I am trying a different distribution
       | method, where you distribute an LLVM-like IR, and the end machine
       | then generates the machine code on first run. In that case,
       | there's no linker necessary, but I do have to write my own code
       | generator. Fun.
       | 
       |  _Avoid bike shedding about syntax_ : Yes, absolutely. I did
       | this, but the syntax still changed _enormously_!
       | 
       |  _Cross-platform support is a challenge_ : Yes, in more ways than
       | one. First, you have to somehow generate code for all of the
       | platforms, then you have to make sure your library works on all
       | of the platforms too.
       | 
       |  _Compiler books aren 't worth the money_: Yes, but please do
       | read _Crafting Interpreters_. Anyway, getting a simple parser and
       | bytecode generator (or LLVM codegen) is the simple part of making
       | a language. Then you need to make it robust, and no one talks
       | about that. Maybe I should write a blogpost about that once my
       | language stabilizes.
       | 
       |  _Growing a language is hard_ : Yes, absolutely. He mentioned two
       | ways it needs to grow: libraries and users.
       | 
       | You can design a language to be easy to grow via libraries. See
       | "Growing a Language" by Guy Steele. [2] I went the extra mile
       | with this, and user code can add its own keywords and lexing
       | code. So growing my language is "easy."
       | 
       | But growing the userbase? That's hard. You need to have a plan,
       | and the best plan is to solve a _massive_ pain point, or
       | multiple. I 'm targeting multiple.
       | 
       | First, I'm targeting shell; my language can shell out as easily
       | as shells, or even more easily, but it's a proper language with
       | strong static type checking. If there are people who want that
       | instead of bash, they'll get it. And there's a _lot_ of bash
       | people might want to replace.
       | 
       | Second, I'm targeting build systems. My language is so easy to
       | grow, I've implemented a build system DSL, and then I put a build
       | system on top.
       | 
       | Shell and build systems are both things people hate but use a
       | lot. These are good targets.
       | 
       |  _The best test suite is a real application_ : Yes, absolutely.
       | Except, the best test suite is actually a _bunch_ of real
       | applications.
       | 
       | My language's own build scripts are the first real program
       | written in it. I'm also going to replace every shell script on my
       | machine with my language, and most of them will make it into the
       | formal test suite.
       | 
       |  _Don 't prioritize performance over functionality_: Yes,
       | absolutely. This is why I would suggest making an interpreter
       | first; you don't depend on LLVM ( _shudder_ ), and you can easily
       | add functionality.
       | 
       |  _Building a language takes time_ : I've taken 11 years, and
       | there's no release yet. Yes, this is true.
       | 
       |  _cries in the corner_
       | 
       | Anyway, I wish the author luck, even though I'll be a competitor.
       | :)
       | 
       | [1]: https://gavinhoward.com/2023/02/why-i-use-c-when-i-
       | believe-i...
       | 
       | [2]: https://www.youtube.com/watch?v=lw6TaiXzHAE
        
       | DonnyV wrote:
       | So you wrote a static language using another static language that
       | takes influence from it.......ok
        
       ___________________________________________________________________
       (page generated 2023-11-15 23:02 UTC)