1. 31

A good discussion on /r/programminglanguages

  1. 28

    This is banal for PL nerds, but: sum types and pattern matching. Maybe it’s not niche, but if you look at the top 5 most popular programming languages—perhaps even top 10—then I don’t think any of them support these features properly. (No, template hacks in C++ don’t count.) They are easily one of the most important features that impact how I write code in languages with and without them. I find that with them, there’s a lot less friction between communicating the proper intent I had when writing code than without them.

    Here’s an interesting question: how useful are sum types without generics? Without generics, you couldn’t for example have a generic Maybe/Option type, but you could still use sums for more specific circumstances. Are there any languages with sum types that don’t support generics? (Do tagged unions in C count?)

    1. 3

      I think one way to have useful sum types without generics would be to use dynamic typing. then you could have variants with associated data and even pattern match on them but the type would just be that of the variant. (pyret and pure have sum types in a dynamic language, though I’ve not really used either. it’s a rare combination of features.)

      1. 13

        I think the killer feature of sum types is statically checked exhaustive pattern matching. Without that, …

        1. 2

          Checked exhaustive pattern matching is one of the things I’ve really been enjoying about Swift, warm fuzzies for sure.

        2. 6

          If you’re not doing static typing, then you might as well fake it with tuples and atoms. Like Erlang does it.

          1. 14

            Tuples and atoms are also things more languages should have. :D

          2. 3

            Interesting. I guess I would have thought that sum types wouldn’t really be a thing in a dynamic language, at least not explicitly. Although, I’ve used them with Flow in Javascript, so there’s certainly some kind of middle ground there.

            Do pyret and pure have exhaustiveness checks? I believe Flow does.

            1. 2

              pyret does run-time exhaustiveness checking.

              for pure, i would guess not since (a) pattern matching is pervasive in the language and sum types aren’t really emphasised, and (b) pure seems to think of types in a very different way from most languages (“In Pure the definition of a type takes a somewhat unusual form, since it is not a static declaration of the structure of the type’s members, but rather an arbitrary predicate which determines through a runtime check which terms belong to the type.”)

            2. 3

              Yeah I noticed that Pyret has sum types, but it’s dynamic. (Never heard of Pure, thanks for the reference.)

              I have been using Zephyr ASDL in Oil, which is meant to give you sum types in languages like C++ and Java. You can fake it pretty well with the typing rules for inheritance (that was sort of surprising to me.).

              http://www.oilshell.org/blog/tags.html?tag=ASDL#ASDL

              I’m using it in Python, which means there is no static type checking. But I still found it very useful, so I was thinking of putting that in the Oil language, which is dynamic.

              Although the latest development is that I used MyPy to statically type the OSH front end (9K lines of hand-written code and 7K lines of generated code), and I really liked it. I am now a fan of gradual typing. Between TypeScript, MyPy, and Hack, it seems like gradual typing is “here”.

              There is also a (dormant) shell-like language with tagwords:

              http://jneen.net/posts/2015-03-01-tulip-language/

              That is basically the dynamic version of sum types … although I now wonder if gradual typing makes sense in this context.

              1. 2

                if you are interested in gradual typing, it’s worth taking a look at stanza, which has it designed-in from the beginning.

              2. 2

                There exists a slightly-acceptable embedding of sum types into TypeScript. This is kinda neat for me because TypeScript is interoperable with a widely used dynamically typed language. You put different types into the branches of a union type. You can check switches on it for exhaustiveness by assigning the switched-upon variable to a variable with type never, which is the uninhabitable type.

                I’m 95% sure it’s possible to write this embedding in a way that has nicer ergonomics than what I’ve just written here. This is cribbed from memory of writing Redux reducers in TypeScript.

                Example of an imperfect embedding of Either<A, B> into TS

                enum LR {
                    L = 'L',
                    R = 'R',
                };
                type Left<L> = { direction: LR.L, left: L };
                type Right<R> = { direction: LR.R, right: R }; 
                type Either<L, R> = Left<L> | Right<R>;
                
                // note: `LR.L as LR.L` sucks. I assume a better way exists but IDK.
                // I think you might be able to do it without needing the `direction` enum at all but I'm not sure.
                const aFailure = { direction: LR.L as LR.L, left: -1 };
                const aSuccess = { direction: LR.R as LR.R, right: "Yay it worked" };
                
                const aResult: Either<number, string> = Math.random() >= 0.5 ? aFailure : aSuccess;
                
                function interpretResult(result: Either<number, string>) {
                    switch (result.direction) {
                        case LR.L: {
                            // (This explicit variable isn't actually necessary, I just have it here to demonstrate that the compiler really does know for dead certain that, on this side of this branch, `result` is a `Left<number>`
                            // like how a nice compiler will know what all the types are when it gets to the right hand side of a `match` statement
                            const itIsKnownToBeLeft: Left<number> = result;
                            console.log(`Oh no! Error code ${String(itIsKnownToBeLeft.left)}`);
                            break;
                        }
                        case LR.R: {
                            // (Same here)
                            const itIsKnownToBeRight: Right<string> = result;
                            console.log(itIsKnownToBeRight.right);
                            break;
                        }
                        default: {
                            // Note: if you add something like e.g. `| string` on the end of the definition of Either, you get a type error here because string is not compatible with the `never` type.
                            const shouldNotGetHere: never = result;
                            console.log("Someone else broke type safety. Oh no.", shouldNotGetHere);
                            break;
                        }
                    }
                }
                
            3. 16
              • First-class closure environments from Lua
              • CL’s interactive restarts/condition system
              • Cluster-wide live tracing a la Erlang
              1. 7

                Second the CL restarts/condition system.

                1. 6

                  I’ve been learning Common Lisp this week and the restarts/condition system has been a major surprise. I am in love with it, I have a feeling if I continue this way I may hate programming in other languages soon enough.

                  1. 2

                    Care to summarize for those of us unfamiliar with the language?

                    1. 4

                      Sure, I can try (although I just learned about it so I may not be the best teacher and may even get things wrong).

                      Essentially it’s an extension of the typical exception system. In a typical exception system you have two parts, the part that signals an error (throws an exception) and the part that handles an exception. The Common Lisp condition/restart system divides it into three sections. signaling a condition (aka exception), handling it, and a third one called restarting. The restarts are the different ways that the error can be recovered from.

                      I will take an example from the Practical Common Lisp book: Imagine a library that parses log files with a certain format. When a log entry is malformed the library throws an error. At the application layer this error is catched and then a restart strategy (offered by the library) can be executed. Different restart strategies can be things like:

                      • Skip a malformed entry
                      • Saved malformed entries raw without parsing them
                      • Saved malformed entries to a separate database
                      • Send an email with the malformed entry

                      When an error is thrown, the error goes to the nearest handler without unwinding the stack (this is very important, the error bubbles up, but the stack stays the same, so we haven’t really returned from the function, we simply find the closest error handler and ask him what he wants to do). This error handler can either bubble it up the hierarchy or specify what it wants to do with the error, for example skip a malformed entry, then execution continues in the place it was when the error was thrown with the corresponding restart strategy that was chosen.

                      This is particularly nice when working with the library directly in the REPL, as when the error is signaled you are given all the restart options right there in the debugger, so interactively you can choose the restart strategy.

                      It cleanly separates what causes the error and the specifics of how to recover from it, from the higher layers that only want to know what options it has to recover from what happened.

                      I probably explained myself like shit (it’s late), but if you’re interested I’d recommend reading Practical Common Lisp, and specifically the section about restarts. http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html

                      Hope this was useful, and sorry if it wasn’t very clear as I haven’t internalized it myself yet.

                      1. 3

                        A basic way to explain it: most languages, when you throw an exception, they immediately unwind (destroy the call stack) until the nearest exception handler. Common Lisp doesn’t do that; it finds the nearest handler without unwinding, and asks it to choose from a menu of restarts. It’s like it calls the nearest handler as a function.

                        If you have closures and dynamically scoped variables you can implement restarts quite easily, I think.

                        1. 1

                          Thank you! And … I feel somewhat vindicated by my childhood confusion around C++ exceptions. When I read about exceptions, they seemed analogous to transactions (I didn’t yet know that word for the concept) and thought there should be an explicit initialize state/prepare action/except/apply action state machine. A much weaker version of action/except is what we have in most languages and didn’t seem powerful enough. The CL restarts system sounds excellent.

                          1. 1

                            That was a wonderful explanation, thank you! I think I understand how to have that feature in a Smalltalk too, so I think I understand what you mean, and that does seem like a very sensible knob to expose to the programmer.

                    2. 13

                      erlang style binary pattern matching.

                      1. 11

                        Oooh, here are some of mine that I think most languages would benefit from:

                        • First-class contracts, a la Eiffel or Ada12
                        • The ability to slap metadata on things, like Clojure has
                        • hyphens-in-names
                        • Matrices that are not just arrays-of-arrays, kinda like J has

                        And some that I like a lot but are probably a bad idea in most cases:

                        • Support for quantifiers over finite sets, like \forall and \exists
                        • All operators have the same operator precedence, end of discussion, so 2 + 2 * 2 is 8 instead of 6
                        • Being able to write func-from(x)-to(y) instead of func_from_to(x, y)
                        1. 3

                          Support for quantifiers over finite sets, like \forall and \exists

                          Why limit yourself to “finite sets”? These quantifiers are perfectly valid/computable for (countably-) infinite objects.

                          1. 1

                            Because predicate logic over infinite sets is undecideable.

                            1. 4

                              Undeciability by itself shouldn’t be a problem. I would like to be able to do a variety of searches which might never end, with an extra provision to stop after a certain time period.

                              1. 1

                                That’s basically how the SPARK Ada people do solvers. They set a 2 min limit as the default. It either proves it easily or more work must be done.

                              2. 1

                                Sure, but you can get almost all of the useful functionality by quantifying over (possibly non-finite) types instead.

                                1. 1

                                  Quantifying over infinite types is also undecidable.

                                  1. 2

                                    How does that matter? If we want to know whether an existential type is inhabited, it is up to the programmer to provide the inhabitant (or the proof of noninhabitance).

                                    1. 2

                                      I think we’re looking for different use cases. I want quantifiers to be able to say things like

                                      if ∀x ∈ S: pred(x) {
                                        raise
                                      }
                                      

                                      (not a great example, just showing the kinds of stuff I regularly write for my work). Asking the programmer to provide the proof takes away from the benefits, which is having a computer do predicate logic for you.

                                      1. 1

                                        Ah, that’s completely different that what I was thinking of. I was thinking of quantifiers on types on the type level, not the object level:

                                        (f : (∀x : A).P(x), y : A) ⊢ f y : P(y)

                                        (x, p) : (∃x : A).P(x) ⊢ p : P(x)

                                        In this system, your example would be something like

                                        (∀S : U₀).(∀x : S).P(x) ⇒ ⊥

                                        Where U₀ is the type of type (universe of types). The inhabitant of this type would be a program that takes a type S, an element x of S, and a proof of P(x) and produces a contradiction.

                            2. 1

                              All operators have the same operator precedence, end of discussion, so 2 + 2 * 2 is 8 instead of 6

                              Nah, I think we should [where possible] standardize on the one from math that we were taught in school: Please, Excuse My Dear Aunt Sally. I mean, the operations with those initials. :)

                            3. 7

                              Easy declaration of command line args via a built-in keyword. It’s a small quality-of-life feature that gets little attention in new languages. See an example in the Chapel language.

                              Writing

                              config const numMessages = 100;

                              at the top of a file means you can run your program with the numMessages command line arg:

                              ./hello --numMessages=1000000

                              1. 6

                                I want native GPU and SIMD operations, and support for hardware transactional memory.

                                1. 5

                                  Some small things that came to my mind for now:

                                  • Lua’s coroutines
                                  • Go’s (and Nim’s) “explicit this”, which has regular name like the rest of the arguments
                                  • OCaml’s, Elm’s, F#’s “pipe operator” (also available in Nim in a surprising way)
                                  • Rust’s or Nim’s macros (though see also Rebol/Red’s “dialects”)
                                  1. 5

                                    I am really, REALLY enjoying Lua. It’s like it hits a sweet spot for programming languages. Delightfully small and light. You can pick up the syntax in no time, but there’s some decent power there under the hood.

                                    (Also it powers PICO-8 which I’m infatuated with of late :)

                                    1. 7

                                      Unfortunately Lua’s greatest strength (unrelenting simplicity) cannot be added to other existing languages. It can only serve as an inspiration for new ones.

                                      1. 2

                                        Janet for the win.

                                        1. 1

                                          Sounds like it follows the Wirth tradition then. ;)

                                        2. 3

                                          Wow, TIL about PICO-8. This looks like it might be a great way for me to learn Lua.

                                          Edit: Dang, it uses/requires DRM. Pass.

                                          1. 4

                                            You might like TIC-80, which is similar but open source.

                                            1. 3

                                              It’s proprietary, but the webpage says “DRM-free”, if that’s what you’re worried about.

                                              If “proprietary” is your real concern, you might be interested in Löve 2D, which is an open-source 2D game engine that bundles together a bunch of useful libraries - SDL for graphics, FreeType for text rendering, Box2D for physics, and Lua for scripting to bind it all together. It’s also easy to make a single self-contained executable with the runtime and all the game resources, so you can show off your game to your friends.

                                              1. 2

                                                It says “DRM-free” here but I assumed it was just for a specific set of folks (whatever ‘Lexaloffle’ is..): https://www.lexaloffle.com/games.php?page=updates

                                                But yes, proprietary is also a (major) concern for me. Löve 2D sounds pretty cool, I’ll take a look. Thanks!

                                                1. 4

                                                  Love2D is very, very good, but also very different from Pico-8. It’s much more general purpose and not really centered around creative constraints, plus it doesn’t integrate tools for sprites/maps/music. For that I would strongly recommend TIC-80: https://tic.computer which is free software and has a very welcoming maintainer. (I’ve had a great experience submitting patches.)

                                                  Both of these work great with Lua or Fennel if you prefer lisps: https://fennel-lang.org

                                                  1. 2

                                                    You need to buy it but it has no DRM.

                                                2. 1

                                                  Wait, What? What do you mean in that it requires DRM? It’s a restricted LUA subset? (Genuinely curious here. I bought Pico-8 because it runs great on my Clockwork Pi Gameshell and fell in love with it.

                                                  1. 1

                                                    I could have been mistaken, the wording on their download page implied (to me) that if you weren’t part of some group then you didn’t get the DRM-free version.

                                                    1. 1

                                                      Nope. Not DRM. However it’s also not open source. It’s $15 and worth every cent and more.

                                                      You get an activation key when you pay.

                                                      If you want fully open source, tic-80 might make you happier :)

                                                      (However if you want a taste of the awesome things folks are doing woth PICO-8 take a gander at the PICO-8 hash tag on Twitter.

                                              2. 3

                                                When I realized that in lisp you can make a pipe operator yourself that is natural and easy to use, I realized it is the highest level language we have.

                                                1. 1

                                                  you can do it yourself in ocaml/elm/f# too (in fact the definition in ocaml is simply let ( |> ) x f = f x). it needs first-class functions and user-defined operators, but a lot of languages have that now.

                                                  1. 1

                                                    My experience is that operator precedence makes these sort of extensions feel second class, maybe It’s different in those languages though.

                                                    1. 1

                                                      Can’t speak for elm, but you’re right in the case of ocaml – you can’t set the precedence of user-created infix operators.

                                                      In the closely-related SML, though, it’s straightforward to do:

                                                      fun op|> x f = (f x);
                                                      infix y |>;
                                                      
                                                      (* Where y is an Int, usually from 0 to 7, higher binds more tightly.
                                                         The provided operators all have 0 to 7 precedence. *)
                                                      

                                                      In F#, precedence is set by the first character of the custom operator. That’s not as flexible as SML, but it’s at least well-defined in the spec.

                                                      1. 1

                                                        ocaml does roughly the same thing as F#:

                                                        The precedences and associativities of infix symbols in expressions are determined by their first character(s): symbols beginning with ** have highest precedence (exponentiation), followed by symbols beginning with *, / or % (multiplication), then + and - (addition), then @ and ^ (concatenation), then all others symbols (comparisons).

                                                        1. 1

                                                          Oh, interesting, thanks! I hadn’t found that before. Where is it documented?

                                                          1. 1

                                                            i found that bit in the caml-light manual; you’re right that it doesn’t seem to be explicitly documented anywhere for ocaml so maybe it’s not reliable any more!

                                                            see this blog post though.

                                                2. 2

                                                  The pipe operator is also part of Elixir.

                                                3. 4

                                                  I’d like to see languages consider release/upgrade. Erlang has some good tools for this, but it’s difficult (without serious architecture consideration) to handle by-module upgrade/downgrades and it requires a lot of discipline even if everything else is in-place.

                                                  I’d also like to see languages think about development. Besides a few smalltalk-derivatives, I can’t think of any language that is aware of version control (think: older and newer versions of a module).

                                                  I’d also like to see languages with testing features. “Real” data in my space looks so fundamentally different from any synthetic type of data that I have no choice but to try out ideas in production. That means not only does my application need to know about older and newer versions (and tag any observations they make with the version), but they also need to be able to run modules as experiments. This results in plug-in architectures at weird layers when done right, and more-frequently, just not done at all.

                                                  Another big thing I’d like to see in languages is logging. I know of two languages that do this (in different ways and for different reasons), but when implementations need some capability here, it’s usually instrumentation done with different tools (and it sucks). To take my fully functional program and re-run it on my dataset and only have it execute the changed outputs is extremely powerful!

                                                  Another thing is IPC. Very few languages support this sort of thing since it pulls in all the clustering and system management – and even when they do, everyone focuses on LAN links since the fantasy that a bunch of cheap/little machines is somehow better/faster than one big one is one held commonly as truth.

                                                  Yet another thing is Error handling. I think programming languages have errors fundamentally wrong and consider them domain violations and in fact real errors typically aren’t: Running out of disk-space, or a network connection breaking isn’t a domain error (except over the file descriptor that’s no longer good, but that’s a stupid way to think about things) - as a result, most of the time you just want your program to stop and wait until the error goes away. This is annoyingly hard in many languages, but thinking about things as conditions (like CL) is a good start. Thinking about trying to make programs without errors (like zig) is even better

                                                  1. 2

                                                    I pretty much agree on error handling – there should be a distinction between program errors and errors of the runtime (e. g. resource exhaustion), which should be handled completely separately.

                                                    It should not even be possible for source code to exist that handles both.

                                                    1. 1

                                                      Stop and wait and also notify the user/administrator and let them help solve the problem or choose an alternative, much like CL restarts indeed.

                                                    2. 3

                                                      When I briefly worked with C#, I instantly felt in love with nullable types. It’ve been later adopted by Kotlin. Constructions such as obj?.foo() are also wonderful help.

                                                      But maybe there shouldn’t be null value in the first place. Tony Hoare apologized for inventing it, calling it a billion dollar mistake. Considering languages that utilize static typing, he might be right – you don’t really need such a value. To put it in other words, why having a value that doesn’t have the same properties as other values of the respective type? You can iterate over an empty list, not over null; you can’t assign meaningful values to x and y coordinates of Vect2 signilizing it’s empty, which sometimes makes sense, but you can always implement a method isDefined() or similar.

                                                      A combination of both approaches would be vital, in my eyes. Consider a type T. It’s implicit state is defined as part of the type definition: a collection doesn’t contain any element, a structure (such as Vect2 mentioned above) doesn’t have any implicit state, thus cannot be initialized implicitly. A complementary type T? can implicitly inherit a method such isDefined() mentioned above (unless stated otherwise) and wrap all member variables with modifiers that prevent reading its’ values unless it’s proven that object is in a defined state.

                                                      Pseudo-code:

                                                      List[Int] list; // OK, refers to an empty list
                                                      list.size(); // 0
                                                      
                                                      List?[Int] list; // Compile-time error, any List can be constructed implicitly
                                                      
                                                      Vect2 v; // Compile-time error, no implicit value
                                                      
                                                      Vect2? v; // OK, refers to Vect2, but...
                                                      v.x; // Compile-time error, x not defined yet
                                                      if (v.isDefined()) {
                                                          v.x; // OK
                                                      }
                                                      

                                                      There’re, of course, other related questions. Should objects be mutable? If so, optimizations would be necessary to prevent allocating too much memory for nothing (a literal “nothing”, sometimes), but still allow changing the member variables later. Basically, memory wouldn’t be fully allocated unless actually needed. That raises some questions…

                                                      What do you think about it? Did I overlook anything?

                                                      1. 12

                                                        Sorry if I’m presuming, but OCaml, Haskell and Rust have all solved this problem with Option/Optional types which seem to work something like what you’re suggesting, and just implemented with normal sum types. Or am I missing something about your proposal?

                                                        1. 1

                                                          I’ll have to take a look at their approach. Thanks.

                                                      2. 3
                                                        • implicit parameter passing on some kind of data structure such as a stack, like Forth
                                                        • explicit access to the return stack, like Forth
                                                        • typeless programming (optionally typed?) like Forth
                                                        1. 3
                                                          • Gradual typing
                                                          • Dependent types
                                                          • Row polymorphism

                                                          I’d really like to see all three of these together. Row polymorphism can be implemented with dependent types. But not sure what dependent types look like in a gradual type system.

                                                          I also wish Clojure specifically was moving towards this as a better version of their spec library for writing contracts and generating tests.

                                                          1. 2

                                                            perl6’s phasers look really nice.

                                                            1. 2

                                                              Ruby (Clu?) style blocks and yield, with all the great syntactic benefits those bring to the table.

                                                              1. 2

                                                                Symbols. And they have to be namespaced, too.

                                                                1. 2

                                                                  Built-in SAT/SMT solvers like in Picat but for a language that is closer to what most folks are used to like JavaScript. Also, single binary deployments like Go. I’m tired of dealing with deployment issues.

                                                                  1. 2

                                                                    Or bundled like SPARK Ada with different syntax or Java with Krakatoa?

                                                                  2. 2

                                                                    Even more banal than sum types: Tuples. I mean the elegant Haskell-like tuples, not the C# implementation, which is too verbose, especially of you use multiple tuples.

                                                                    1. 2

                                                                      I was so happy when I discovered that python had a destructuring-bind analog.

                                                                    2. 2

                                                                      I always liked Objective-C’s bottom propagation, and the F# units thing.

                                                                      1. 2
                                                                        • Generics with [] instead of <>. Really, how many decades do people keep trying to make generics with <> work?
                                                                        • Working == (equality) and === (identity) across types.
                                                                        • Clean separation of how data is represented inside e. g. a class or struct vs. how it is exposed to the outside. Most language’s getter/setter/property hacks are pretty gross.

                                                                        Apart from that, a good language that lacks features is what I consider my personal niche feature. Languages are rarely improved by adding features.

                                                                        Most languages suffer from poor overall design quality, because everyone had their distinct niche feature they wanted to add to the language.

                                                                        So I would be delighted about a language who just combined the best parts of the state of the art and made them work, without any special bells and whistles attached.