13 ms·
A decade of developing a programming language
- otoburb 3y ago>>"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-gradual-typing/ https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
- wk_end 3y agoPursuing 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 3y agoI 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 3y agoI 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 3y ago> 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 3y agoYou can do that with type inference and a strong, static type system.
- munificent 3y agoI 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 3y agoWhat about Julia?
- adgjlsfhk1 3y agoJulia isn't gradually typed. It's strongly but dynamically typed.
- chubot 3y agoOh 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 3y agoRaku (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 3y ago>for like two decades. IOW, Raku is a gradual type of language.
- 3y ago
- layer8 3y agoWhat 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-topics/interop/using-type-dynamic 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 3y agoIs 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 3y agoIt’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 3y agoWell, 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 3y agoThe 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 3y agoI 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 3y agoIIRC 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.
- shadowbanned3 3y ago[flagged]
- ralmidani 3y agoA 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 José 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 José and team are very thoughtful and deliberative. I don’t think they want to introduce gradual typing because it’s trendy.
- deleted 3y ago[deleted]
- lylejantzi3rd 3y ago"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 3y agoWhat part of the statement "is" because of type inference?
- marcosdumay 3y agoYour 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 3y ago> 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 3y agoWell, 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 3y ago> 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.
- sesm 3y agoLooking 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 3y ago> only 3 years, because they were targeting an existing platform. Clojure took only 2 years and was d̶e̶v̶e̶l̶o̶p̶e̶d̶ ̶b̶y̶ targeting a single person.
- canadianfella 3y ago[dead]
- cactusfrog 3y agoWhat do you think about clojure?
- fsckboy 3y agoI 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
- zengid 3y ago> "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 3y agoI 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 3y agoThe 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 3y ago
- coldtea 3y ago>"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 3y ago"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.
- deleted 3y ago[deleted]
- packetlost 3y agoAs 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 3y agoAny? 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 3y ago> 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 3y agoFinding 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 3y ago> 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.
- tabtab 3y agoThe 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 3y ago> 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 3y agoKernel[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 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 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 https://github.com/vito/hummus/tree/master --- [1]:http://web.cs.wpi.edu/%7Ejshutt/kernel.html http://web.cs.wpi.edu/%7Ejshutt/kernel.html [2]:https://fexpr.blogspot.com/2016/08/interpreted-programming-languages.html https://fexpr.blogspot.com/2016/08/interpreted-programming-l...
- khafagy1 3y ago[dead]
- bsder 3y ago> 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 3y agoOh that's a great point! Didn't someone give a talk about that too?
- bsder 3y agoNo video here, but discussion: https://ziglang.org/news/goodbye-cpp/ https://ziglang.org/news/goodbye-cpp/ Welp, video: https://www.youtube.com/watch?v=MCfD7aIl-_E https://www.youtube.com/watch?v=MCfD7aIl-_E
- lerno 3y agoSigh. Please don’t refer to the rather exceptional nature of Zig’s self hosting. There has been an insane amount of work put into the Zig compiler by various contributors. Probably easily eclipsing the effort to write two separate compilers. The whole WASM deal comes AFTER they essentially built the compiler both in C++ and Zig to reduce bootstrapping steps. That just addresses the bootstrapping cost and not the fact that you have to build the compiler at least twice - and if you do it before 1.0 - more than twice. Cargoculting Zig’s development isn’t going to give you Zig’s success. Zig is successful because of Andrew presenting a vision of a language that lots of people was attracted to, and then cultivating the community well. It’s a well known fact in the community that the first C++ compiler had to go because it wasn’t well written, not because self hosting is great.
- bsder 3y agoUm, so? The fact that Zig did a boatload of extra work due to path dependence is true. However, this in no way contradicts that self-hosting via WASM/WASI is likely a far better idea than maintaining two complete compilers in different codebases and languages solely in order to bootstrap. In fact, nowadays, someone building a new language and compiler is probably better off targeting WASM/WASI before any other architecture.
- karmakaze 3y agoRelated post "Inko Programming Language" which might get some interesting comments. https://news.ycombinator.com/item?id=38270265 https://news.ycombinator.com/item?id=38270265 https://inko-lang.org/ https://inko-lang.org/
- Joel_Mckay 3y agoMost 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/ 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 3y agoIt'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 3y agoI 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 3y agoI 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 3y agoI 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 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.
- macintux 3y agoRelated discussion: https://news.ycombinator.com/item?id=38270265 https://news.ycombinator.com/item?id=38270265
- matheusmoreira 3y ago> 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 3y agoI 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 3y agoIt 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 3y agoI 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.
- 3y ago
- danybittel 3y agoYour 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 3y ago> 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 3y agoMaybe people just like to use general purpose languages. It should not be too hard to marry the good parts of major language families
- nyssos 3y ago> 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 3y agoI 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.
- ashton314 3y ago> 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-spectrum-of-type-soundness-and-performance/ https://prl.khoury.northeastern.edu/blog/2018/10/06/a-spectr...
- gilmi 3y agoI 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 https://gilmi.me/blog/post/2023/07/08/js-as-a-target [2]: https://gilmi.me/blog/tags/type%20inference https://gilmi.me/blog/tags/type%20inference
- zubairq 3y agoHaving 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 3y agoI 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 https://github.com/kcl-lang
- peefy 3y agoIt 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 3y agoAs 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-in-memory-safety/ https://gavinhoward.com/2023/02/why-i-use-c-when-i-believe-i... [2]: https://www.youtube.com/watch?v=lw6TaiXzHAE https://www.youtube.com/watch?v=lw6TaiXzHAE
- DonnyV 3y agoSo you wrote a static language using another static language that takes influence from it.......ok