1. 25

    I’d like to provide a more sympathetic outside perspective.

    There are a few common complaints about Elm and Elm community:

    • Blocking discussion (locking threads etc.)
    • Insufficient communication about Elm development
    • Things getting removed from the language.

    With regards to blocking discussion, I think the logic is something like this:

    • The core developers have a roadmap for Elm development and they want to stick to it
    • They tried including more developers but haven’t found an effective way to deal with more contributors
    • Therefore, they have limited time
    • They can spend this time rehashing the same arguments and debating half-baked ideas, or they can spend this time following their roadmap, but not both.

    I would prefer that the discussions weren’t removed or locked, but on the other hand, it’s got to be grating to deal with the same entitled, uninformed or complaining comments all the time. I’ve read most of these discussions, and other than people venting, nothing is ever achieved in them. My reflexive reaction is to be uncomfortable (like a lot of other people) but then, there is also a certain clarity when people just say that they will not engage in a discussion.

    With regards to insufficient communication, I think the main things to understand is that Elm is an experiment in doing things differently, and it’s causing a clash with conventional understanding. Elm is about getting off the upgrade treadmill. So, for example, when a new release like Elm 0.19 comes out, it happens without a public alpha and beta phases, and it’s not actually the point where you go and immediately migrate your production code to it! It’s only the point to start experimenting with it, it’s the point where library and tool authors can upgrade and so on. (There was quite a bit of activity prior to release anyway, it just wasn’t advertised publicly.)

    Finally, the most contentious example of a “feature” getting removed is the so called native modules (which basically means the ability to have impure functions written in JS in your Elm code base). As far as I can tell (having followed Elm since 0.16), native modules were always an internal implementation detail and their use was never encouraged. Nevertheless, some people started using them as a shortcut anyway. However, they were a barrier to enabling function-level dead code elimination which is the main feature of the 0.19 release, so the loophole was finally closed. Sure, it’s inconvenient for people who used them, but does anyone complain when, say, Apple removes an internal API?

    Ultimately, Elm is just an open source project and the core maintainers don’t really owe anybody anything - no contracts are entered into and no funds are exchanged. They can do whatever they want.

    Of course, there is a question of the long term effects this approach is going to have on the community. Will it alienate too many people and cause Elm to wither? Will Elm remain a niche language for a narrow class of applications? That remains to be seen.

    1. 19

      but on the other hand, it’s got to be grating to deal with the same entitled, uninformed or complaining comments all the time.

      Over the years, I have come to believe this is a vital part of building a community. Using draconian tactics to stomp out annoying comments is using power unwisely and worse yet – cripples your community in multiple ways.

      The first thing to remember is that when a comment (entitled, uninformed or otherwise) comes up repeatedly – that is a failure of the community to provide a resource to answer/counter/assist with that comment. That resource can be a meme, a link, an image, a FAQ, a full on detailed spec document, whatever. This type of thing is part of how a community gets a personality. I think a lot of the reason there are a bunch of dead discourse servers for projects is too stringent policing. You should have a place for people to goof off and you have to let the community self police and become a real community. Not entirely, obviously, but on relevant topics.

      This constant repetition of questions/comments is healthy, normal, it is the entrance of new people to the community. More importantly, if gives people who are just slightly deeper in the community someone to help, someone to police, someone to create resources for, even to a degree someone to mock (reminding them they aren’t THAT green anymore) – a way to be useful! This is a way to encourage growth, each “generation” of people helps the one that comes after them – and it is VITAL for building up a healthy community. In a healthy community the elders will only wade in occasionally and sporadically to set the tone and will focus on the more high minded, reusable solutions that move the project forward. Leave the minor stuff be done by the minor players, let them shine!

      Beyond being vital to build the community – it is a signal of where newcomers are hurting. Now if documentation fixes the problem, or a meme… terrific! But if it doesn’t, and if it persists … that is a pain point to look at – that is a metric – that is worth knowing.

      1.  

        Yeah, each one of these people gives you a chance to improve how well you communicate, and to strengthen your message. But shutting down those voices then run the risk of surrounding yourself with ‘yes people’ who don’t challenge your preconceptions. Now, it’s entirely up to the Elm people to do this, but I think they are going to find it harder to be mainstream with this style of community.

        Note that I’m perfectly fine with blocking and sidelining people who violate a CoC, or are posting hurtful, nonconstructive comments. You do have to tread a fine line in your moderation though. Being overly zealous in ‘controlling the message’ can backfire in unpredictable ways.

        Anyway, I continue to follow Elm because I think the designer has some excellent ideas and approaches, even if I do disagree with some of the ways the way the community is managed.

        1.  

          even if I do disagree with some of the ways the way the community is managed.

          I don’t think the two jobs (managing the community and managing the project) should necessarily be done by the same person. I actually think it probably shouldn’t. Each job is phenomenally challenging on its own – trying to do both is too much.

          1.  

            Yeah, completely agree! I think it would take a huge weight of that person’s shoulders too! :)

            1.  

              I don’t think Evan personally moderates the forums. Other people do it these days.

              1.  

                But, they do it on his behalf? This policy of locking and shutting down discussions comes from somewhere. That person directly or indirectly is the person who “manages” the community, the person who sets the policies/tone around such things.

                I personally have no idea, I am not active in the Elm community.

                1.  

                  I’m not sure who sets the policy and how.

          2.  

            That’s a very interesting perspective, thanks.

          3. 10

            I’ll add the perspective of someone who loved Elm and will never touch it again. We’re rewriting in PureScript right now :) I’m happy I learned Elm, it was a nice way of doing things while it lasted.

            In Elm you may eventually hit a case where you can’t easily wrap your functionality in ports, the alternative to native modules. We did, many times. The response on the forum and other places is often to shut down your message, to give you a partial version of that functionality that isn’t quite what you need, to tell you to wait until that functionality is ready in Elm (a schedule that might be years!), or until recently to point you at native modules. This isn’t very nice. It’s actually very curious how nice the Elm community is unless you’re talking about this feature, in which case it feels pretty hostile. But that’s how open source rolls.

            Look at the response to message linked in the story: “We recently used a custom element to replace a native modules dealing with inputs in production at NoRedInk. I can’t link to it because it’s in a proprietary code base but I’ll be writing an speaking about it over the next couple months.”

            This is great! But I can’t wait months in the hope that someone will talk about a solution to a problem I have today. Never mind releasing one.

            Many people did not see native modules as a shortcut or a secret internal API. They were an escape valve. You would hit something that was impossible without large efforts that would make you give up on Elm as not being viable. Then you would overcome the issues using native modules which many people in the community made clear was the only alternative. Now, after you invest effort you’re told that there’s actually no way to work around any of these issues without “doing them the right way” which turns out to be so complicated that companies keep them proprietary. :(

            I feel like many people are negative about this change because it was part of how Elm was sold to people. “We’re not there yet, but here, if we’re falling short in any way you can rely on this thing. So keep using Elm.”

            That being said, it feels like people are treating this like an apocalypse, probably because they got emotionally invested in something they like and they feel like it’s being changed in a way that excludes them.

            You’re right though. Maybe in the long term this will help the language. Maybe it will not. Some people will enjoy the change because it does lead to a cleaner ecosystem and it will push people to develop libraries to round out missing functionality. In the short term, I have to get things done. The two perspectives often aren’t compatible.

            I’m personally more worried about what will happen with the next major change where Elm decides to jettison part of its community. I don’t want to be around for that.

            1.  

              If people encouraged you to use native modules, then that was unfortunate.

              I’m not sure I understand the issue with custom elements. Sure, they’re a bit complicated and half baked but it certainly doesn’t require a research lab to use them (in fact, I’ve just implemented one now).

              I would agree, however, that the Elm developers have a bit of a hardline approach to backward compatibility. Perhaps there is a misunderstanding around the state of Elm - ie whether it’s still an experiment that can break compatibility or a stable system that shouldn’t.

              I’m not sure how I feel about backward compatibility. As a user, it’s very convenient. As a developer, it’s so easy to drown in the resulting complexity.

            2. 9

              I would prefer that the discussions weren’t removed or locked, but on the other hand, it’s got to be grating to deal with the same entitled, uninformed or complaining comments all the time. I’ve read most of these discussions, and other than people venting, nothing is ever achieved in them. My reflexive reaction is to be uncomfortable (like a lot of other people) but then, there is also a certain clarity when people just say that they will not engage in a discussion.

              I’ll go one further and say I’m quite glad those discussions get locked. Once the core team has made a decision, there’s no point in having angry developers fill the communication channels the community uses with unproductive venting. I like the decisions the core team is making, and if those threads didn’t get locked, I’d feel semi-obligated to respond and say that I’m in favor of the decision, or I’d feel guilty not supporting the core devs because I have other obligations. I’m glad I don’t have to wade through that stuff. FWIW, it seems like the community is really good at saying “We’re not going to re-hash this decision a million times, but if you create a thread about a specific problem you’re trying to solve, we’ll help you find an approach that works” and they follow through on that.

              I don’t have a lot of sympathy for folks who are unhappy with the removal of the ability to compile packages that include direct JS bindings to the Elm runtime. For as long as I’ve been using Elm the messaging around that has consistently been that it’s not supported, it’s just an accidental side effect of how things are built, and you shouldn’t do it or you’re going to have a bad time. Now it’s broken and they’re having a bad time. This should not be a surprise. I also think it’s good decision to actively prohibit it. If people started using that approach widely, it would cause a lot of headaches for both the community and hamstring the core team’s ability to evolve the language.

              1. 6

                I’m quite glad those discussions get locked

                and

                I like the decisions the core team is making

                Do you believe your perspective would change if you didn’t agree with the developers decisions? Obviously I have a different perspective but I am curious if think you would still have this perspective if you were on the other side?

                Additionally, just because the core team has “made a decision” doesn’t mean it wasn’t a mistake, nor that it is permanent. Software projects make mistakes all the time, and sometimes the only way to really realize the mistake is the hear the howls of your users.

                1.  

                  I’m pretty confident I wouldn’t change my position on this if I wasn’t in agreement with the core team’s choices. I might switch to PureScript or ReasonML, if I think the trade-offs are worth it, but I can’t see myself continuing to complain/vent after the decision has been made. I think appropriate user input is “I have this specific case, here’s what the code look like, here’s the specific challenge with any suggested alternative” If the core team decides to go another way after seeing their use cases, it’s clear we don’t have the same perspective on the trade-offs for those decisions. I can live with that. I don’t expect everybody to share my opinion on every single technical decision.

                  As an example, I use Clojure extensively at work, and I very much disagree with Rich Hickey’s opinions about type systems, but it’s pretty clear he’s thought through his position and random folks on the internet screaming differently isn’t going to change it, it’ll just make his job more difficult. I can’t imagine ever wanting to do that to someone.

                  sometimes the only way to really realize the mistake is the hear the howls of your users

                  It’s been my experience that the folks who can provide helpful feedback about mistaken technical decisions rarely howl. They can usually speak pretty clearly about how decisions impact their work and are able to move on when it’s clear there’s a fundamental difference in goals.

                  1.  

                    It’s been my experience that the folks who can provide helpful feedback about mistaken technical decisions rarely howl.

                    We fundamentally disagree on this point (and the value of the noisy new users), and I don’t think either of us is going to convince the other. So, I think this is a classic case of agree to disagree.

              2. 10

                I think what bothers me the most about the core team’s approach to features is not that they keep removing them, but that for some they do not provide a valid alternative.

                They’ll take away the current implementation of native modules, but coming up with a replacement is too hard, so even though the core libraries can use native code, us peasants will have to do without.

                They won’t add a mechanism for higher rank polymorphism because coming up with a good way to do it is hard, so even though the base library has a few magic typeclasses for its convenience, us peasants will have to make do with mountains of almost duplicated code and maybe some code generation tool.

                So where does that leave Elm right now? Should it be considered a production-ready tool just by virtue of not having very frequent releases? Or should it be regarded as an incomplete toy language, because of all the breaking changes between releases, all the things that haven’t been figured out yet, and how the response to requests for ways to do things that are necessary in real code is either “you don’t need that”, which I can live with most of the time, or “deal with it for the moment”, which is unacceptable.

                I think Elm should make it more clear that it’s ostensibly an unfinished project.

                1.  

                  They’ll take away the current implementation of native modules, but coming up with a replacement is too hard

                  They won’t add a mechanism for higher rank polymorphism because coming up with a good way to do it is hard

                  I don’t think this is a fair characterization of the core team’s reasons for not supporting those features. I’ve read/watched/listened to a lot of the posts/videos/podcasts where Evan and other folks discuss these issues, and I don’t think I’ve ever heard anyone say “We can’t do it because it’s too difficult.” There’s almost always a pretty clear position about the trade-offs and motivations behind those decisions. You might not agree with those motivations, or weigh the trade-offs the same way, but it’s disingenuous to characterize them as “it’s too hard”

                  1.  

                    I exaggerate in my comment, but what I understood from the discussions around rank n polymorphism I’ve followed is basically that Evan doesn’t think any of the existing solutions fit Elm.

                    I understand that language design, especially involving more complex features like this, is a hard issue, and I’m sure Evan and the core team have thought long and hard about this and have good reasons for not having a good solution yet, but the problem remains that hard things are hard and in the meantime the compiler can take an escape hatch and the users cannot.

                  2.  

                    Should it be considered a production-ready tool just by virtue of not having very frequent releases? Or should it be regarded as an incomplete toy language

                    I always struggle with this line of questioning because “incomplete and broken” describes pretty much all of the web platform in the sense that whenever you do non-trivial things, you’re going to run into framework limitations, bugs, browser incompatibilities and so on.

                    All you can do is evaluate particular technologies in the context of your specific projects. For certain classes of problems, Elm works well and is indeed better than other options. For others, you’ll have to implement workarounds with various degrees of effort. But again, I can say the same thing for any language and framework.

                    Is it good that it’s so easy to bump up against bugs and limitations? No. But at least Elm is no worse than anything else.

                    Taking a tangent, the main problem is that Elm is being built on top of the horrifically complex and broken foundation that is the web platform. It’s mostly amazing to me that anything works at all.

                    1. 7

                      To me the problem is that Elm is not conceptually complete. I listed those issues specifically because they’re both things that the compiler and the core libraries can do internally, but the users of the language cannot.

                      But at least Elm is no worse than anything else.

                      No, Elm is a language, and not being able to do things in a language with so few metaprogramming capabilities is a pretty big deal compared to a missing feature in a library or a framework, which can easily be added in your own code or worked around.

                      1.  

                        But how is this different from any other ecosystem? The compiler always has more freedom internally. There are always internal functions that platform APIs can use but your library cannot. Following your logic, we should condemn the Apple core APIs and Windows APIs too.

                        1.  

                          No, what I meant is that the core libraries use their “blessed” status to solve those problems only for themselves, thus recognizing that those problems effectively exist, but the users aren’t given any way to deal with them.

                          1.  

                            But there are actually solutions on offer: ports and custom elements. What’s wrong with using them?

                            1.  

                              Ports are very limiting and require much more work to set up than a normal library, and I haven’t used custom elements so I can’t speak for those.

                              There’s also no workaround for the lack of ad-hoc polymorphism. One of the complaints I hear the most about Elm is that writing json encoders and decoders is tedious and that they quickly become monstrously big and hard to maintain; often the json deserialization modules end up being the biggest modules in an Elm project.

                              This is clearly a feature the language needs (and already uses with some compiler magic, in the form of comparable, appendable, and so on).

                      2. 7

                        Is it good that it’s so easy to bump up against bugs and limitations? No. But at least Elm is no worse than anything else.

                        Having worked with ClojureScript on the front-end for the past 3 years, I strongly disagree with this statement. My team has built a number of large applications using Reagent and whenever new versions of ClojureScript or Reagent come out all we’ve had to do was bump up the versions. We haven’t had to rewrite any code to accommodate the language or Reagent updates. My experience is that it’s perfectly possible to build robust and stable tools on top of the web platform despite its shortcomings.

                        1.  

                          I think the ease of upgrades is a different discussion. There is a tool called elm-upgrade which provides automated code modifications where possible. That’s pretty nice, I haven’t seen a lot of languages with similar assistance.

                          My point was, you cannot escape the problems of the web platform when building web applications. Does ClojureScript fully insulate you from the web platform while providing all of its functionality? Do you never run into cross-browser issues? Do you never have to interoperate with JavaScript libraries? Genuinely asking - I don’t know anything about ClojureScript.

                          1.  

                            My experience is that vast majority of issues I had with the web platform went away when my team started using ClojureScript. We run into cross-browser issues now and then, but it’s not all that common since React and Google Closure do a good job handling cross-browser compatibility. Typically, most of the issues that we run into are CSS related.

                            We interoperate with Js libraries where it makes sense, however the interop is generally kept at the edges and wrapped into libraries providing idiomatic data driven APIs. For example, we have a widgets library that provides all kinds of controls like data pickers, charts, etc. The API for the library looks similar to this to our internal widgets API.

                            1.  

                              Sounds like a great development experience!

                              Let me clarify my thinking a bit. For a certain class of problems, Elm is like that as well. But it certainly has limitations - not a huge number of libraries etc.

                              However, I think that pretty much everything web related is like that - limitations are everywhere, and they’re much tighter than I’d like. For example, every time I needed to add a date picker, it was complicated, no matter the language/framework. But perhaps your widgets library has finally solved it - that would be cool!

                              So I researched Elm and got a feel for it’s limitations, and then I could apply it (or not) appropriately.

                              I would agree, however, that the Elm developers have a bit of a hardline approach to backward compatibility. Perhaps there is a misunderstanding around the state of Elm - ie whether it’s still an experiment that can break compatibility or a stable system that shouldn’t.

                              I’m not sure how I feel about backward compatibility. As a user, it’s very convenient. As a developer, it’s so easy to drown in the resulting complexity.

                              1.  

                                Yeah, I agree that the main question is around the state of Elm. If the message is that Elm isn’t finished, and don’t invest into it unless you’re prepared to invest time into keeping up, that’s perfectly fine. However, if people are being sold on a production ready language that just works there appears to be a bit of a disconnect there.

                                It’s obviously important to get things right up front, and if something turns out not to work well it’s better to change it before people get attached to it. On the other hand, if you’re a user of a platform then stability is really important. You’re trying to deliver a solution to your customers, and any breaking changes can become a serious cost to your business.

                                I also think it is important to be pragmatic when it comes to API design. The language should guide you to do things the intended way, but it also needs to accommodate you when you have to do something different. Interop is incredibly important for a young language that’s leveraging a large existing ecosystem, and removing the ability for people to use native modules in their own projects without an alternative is a bit bewildering to me.

                          2.  

                            I have the opposite experience. Team at day job has some large CLJS projects (also 2-3 years old) on Reagent and Re-Frame. We’re stuck on older versions because we can’t update without breaking things, and by nature of the language it’s hard to change things with much confidence that we aren’t also inadvertently breaking things.

                            These projects are also far more needlessly complex than their Elm equivalents, and also take far longer to compile so development is a real chore.

                            1.  

                              Could you explain what specifically breaks things in your project, or what makes it more complex than the Elm equivalent. Reagent API had no regressions in it that I’m aware of, and re-frame had a single breaking change where the original reg-sub was renamed to reg-sub-raw in v 0.7 as I recall. I’m also baffled by your point regarding compiling. The way you develop ClojureScript is by having Figwheel or shadow-cljs running in the background and hotloading code as you change it. The changes are reflected instantly as you make them. Pretty much the only time you need to recompile the whole project is when you change dependencies. The projects we have at work are around 50K lines of ClojureScript on average, and we’ve not experienced the problems you’re describing.

                      3.  

                        I was hoping to read other points of view on that matter, thanks for taking the time writing down yours!

                      1. 5

                        I think it’s worth linking to the whole course too.

                        It’s a shame the slide show doesn’t respond to mouse clicks below the main text. The arrow keys work, but I spent a minute or so clicking like an idiot before the next slide showed up.

                        1. 3

                          Yeah, the lecture notes on the GHC implementation seem a bit easier to digest than the slides.

                        1. 2

                          I’ve found bidirectional type checking is indeed a very handy technique for making expressive type systems quickly. I haven’t yet mastered how to marry it with constraint based inference (for implicit arguments) but it proved to be very useful when starting out on building Pikelet.

                          David Christiansen has a somewhat more acessible intro here. It’s also used in the LambdaPi paper which Pikelet was originally based off.

                          Let Arguments Go First is another interesting paper that adds an ‘application mode’ that allows you to pull more type information from argument applications. I’m hoping to try to implement that too at some stage!

                            1. 22

                              After writing Go for 5 years, I’d recommend Rust for C developers. It’s more complicated than Go for sure, but also has more to offer. The lack of garbage collection and support of generics are definitely a plus compared to Go.

                              Go is a better language for junior devs, but I wouldn’t call C programmers junior. They should be able to digest Rust’s complexity.

                              1. 9

                                They should be able to digest Rust’s complexity.

                                Non trivial amount of C programmers are still doing C to avoid additional complexity. Not everyone wants a kitchen & sink programming language.

                                1. 6

                                  Rust can definitely get overly complex if the developers show no constraint (i.e. type golf), but the control afforded by manual memory management makes up for it, IMHO. Unless it’s a one-run project, performance will eventually matter, and fixing bad allocation practices after the fact is a lot harder than doing it right from the beginning.

                                  1. 1

                                    Couldn’t they just start with a C-like subset of Rust adding from there to their arsenal what extra features they like? It’s what I was going to recommend to those trying it for safety-critical use since they likely know C.

                                    1. 9

                                      I think it’s rather difficult to write rust in a C like manner. This contrasts with go, where you can basically write C code and move the type declarations around and end up with somewhat unidiomatic but working go.

                                      1. 3

                                        I think C++ as a better C works because you still have libc besides the STL, etc. The Rust standard library uses generics, traits, etc. quite heavily and type parameters and lifetime parameters tend to percolate to downstream users.

                                        Though I think a lot of value in Rust is in concepts that may initially add some complexity, such the borrow checker rules.

                                        1. 3

                                          The problem with C++ is its complexity at the language level. I have little hope of teams of people porting various tools for static analysis, verification, and refactoring to it that C and Java already have. Certifying compilers either. C itself is a rough language but smaller. The massive bandwagon behind it caused lots of tooling to be built, esp FOSS. So, I now push for low-level stuff either safer C or something that ties into C’s ecosystem.

                                        2. 4

                                          You could argue the same for C++ (start with C and add extra features). Complexity comes with the whole ecosystem from platform support (OS, arch), compiler complexity (and hence subtle difference in feature implementations) to the language itself (C++ templates, rust macros). It’s challenging to limit oneself to a very specific subset on a single person project, it’s exponentially harder for larger teams to agree on a subset and adhere to it. I guess I just want a safer C not a new C++ replacement which seems to be the target for newer languages (like D & Rust).

                                          1. 4

                                            It’s challenging to limit oneself to a very specific subset on a single person project, it’s exponentially harder for larger teams to agree on a subset and adhere to it.

                                            I see your overall point. It could be tricky. It would probably stay niche. I will note that, in the C and Java worlds, there’s tools that check source code for compliance with coding standards. That could work for a Rust subset as well.

                                            “I guess I just want a safer C not a new C++ replacement which seems to be the target for newer languages (like D & Rust).”

                                            I can’t remember if I asked you what you thought about Cyclone. So, I’m curious about that plus what you or other C programmers would change about such a proposal.

                                            I was thinking something like it with Rust’s affine types and/or reference counting when borrow-checking sucks too much with performance acceptable. Also, unsafe stuff if necessary with the module prefixed with that like Wirth would do. Some kind of module system or linking types to avoid linker errors, too. Seemless use of existing C libraries. Then, an interpreter or REPL for the productivity boost. Extracts to C to use its optimizing and certifying compilers. I’m unsure of what I’d default with on error handling and concurrency. First round at error handling might be error codes since I saw a design for statically checking their correct usage.

                                            1. 3

                                              I can’t remember if I asked you what you thought about Cyclone. So, I’m curious about that plus what you or other C programmers would change about such a proposal.

                                              I looked at it in the past and it felt like a language built on top of C similar to what a checker tool with annotations would do. It felt geared too much towards research versus use and the site itself states:

                                              Cyclone is no longer supported; the core research project has finished and the developers have moved on to other things. (Several of Cyclone’s ideas have made their way into Rust.) Cyclone’s code can be made to work with some effort, but it will not build out of the box on modern (64 bit) platforms).

                                              However if I had to change Cyclone I would at least drop exceptions from it.

                                              I am keeping an eye on zig and that’s closest to how I imagine a potentially successful C replacement - assuming it takes up enough community drive and gets some people developing interesting software with it.

                                              That’s something Go had nailed down really well. The whole standard library (especially their crypto and http libs) being implemented from scratch in Go instead of being bindings were a strong value signal.

                                              1. 2

                                                re dropping exceptions. Dropping exceptions makes sense. Is there another way of error handling that’s safer or better than C’s that you think might be adoptable in a new, C-like language?

                                                re Zig. It’s an interesting language. I’m watching it at a distance for ideas.

                                                re standard library of X in X. Yeah, I agree. I’ve been noticing that pattern with Myrddin, too. They’ve been doing a lot within the language despite how new it is.

                                                1. 4

                                                  Dropping exceptions makes sense. Is there another way of error handling that’s safer or better than C’s that you think might be adoptable in a new, C-like language?

                                                  Yes, I think Zig actually does that pretty well: https://andrewkelley.me/post/intro-to-zig.html#error-type

                                                  edit: snippet from the zig homepage:

                                                  A fresh take on error handling that resembles what well-written C error handling looks like, minus the boilerplate and verbosity.

                                                  1. 2

                                                    Thanks for the link and tips!

                                      2. 7

                                        Short build/edit/run cycles are appreciated by junior and senior developers alike. Go currently has superior compilation times.

                                        1. 10

                                          Junior and senior developers also enjoy language features such as map, reduce, filter, and generics. Not to mention deterministic memory allocation, soft realtime, forced error checking, zero-cost abstractions, and (of course) memory safety.

                                          1. 3

                                            Junior and senior developers also enjoy language features such as map, reduce, filter, and generics.

                                            Those are great!

                                            deterministic memory allocation, soft realtime, forced error checking, zero-cost abstractions, and (of course) memory safety.

                                            Where are you finding juniors who care about this stuff? (no, really - I would like to know what kind of education got them there).

                                            1. 8

                                              I cared about those things, as a junior. I am not sure why juniors wouldn’t care, although I suppose it depends on what kind of software they’re interested in writing. It’s hard to get away with not caring, for a lot of things. Regarding education, I am self-taught, FWIW.

                                            2. 1

                                              Map, reduce and filter are easily implemented in Go. Managing memory manually, while keeping the GC running, is fully possible. Turning off the GC is also possible. Soft realtime is achievable, depending on your definition of soft realtime.

                                              1. 1

                                                Map, reduce and filter are easily implemented in Go

                                                How? Type safe versions of these, that is, without interface{} and hacky codegen solutions?

                                                1. 1

                                                  Here are typesafe examples for Map, Filter etc: https://gobyexample.com/collection-functions

                                                  Implementing one Map function per type is often good enough. There is some duplication of code, but the required functionality is present. There are many theoretical needs that don’t always show up in practice.

                                                  Also, using go generate (which comes with the compiler), generic versions are achievable too. For example like this: https://github.com/kulshekhar/fungen

                                                  1. 9

                                                    When people say “type safe map/filter/reduce/fold” or “map, reduce, filter, and generics” they are generally referring to the ability to define those functions in a way that is polymorphic, type safe, transparently handled by the compiler and doesn’t sacrifice runtime overhead compared to their monomorphic analogs.

                                                    Whether you believe such facilities are useful or not is a completely different and orthogonal question. But no, they are certainly not achievable in Go and this is not a controversial claim. It is by design.

                                                    1. 1

                                                      Yes, I agree, Go does not have the combination of type safety and generics, unless you consider code generation.

                                                      The implementation of generics in C++ also works by generating the code per required type.

                                                      1. 5

                                                        The implementation of generics in C++ also works by generating the code per required type.

                                                        But they are not really comparable. In C++, when a library defines a generic type or function, it will work with any conforming data type. Since the Go compiler does not know about generics, with go generate one can only generate ‘monomorphized’ types for a set of predefined data types that are defined an upstream package. If you want different monomorphized types, you have to import the generic definitions and run go generate for your specific types.

                                                        unless you consider code generation

                                                        By that definition, any language is a generic language, there’s always Bourne shell/make/sed for code generation ;).

                                                        1. 1

                                                          That is true, and I agree that go does not have support for proper generics and that this can be a problem when creating libraries.

                                                        2. 3

                                                          That’s why I said “transparently handled by the compiler.” ;-)

                                                          1. 0

                                                            I see your point, but “go generate” is provided by the go compiler, by default. I guess it doesn’t qualify as transparent since you have to type “go generate” or place that command in a build file of some sort?

                                                            1. 1

                                                              Yes. And for the reasons mentioned by @iswrong.

                                                              My larger point here really isn’t a technicality. My point is that communication is hard and not everyone spells out every point is precise detail, but it’s usually possible to infer the meaning based on context.

                                                              1. -1

                                                                I think the even larger point is that for a wide range of applications, “proper” and “transparent” generics might not even be needed in the first place. It would help, yes, but the Go community currently thrives without it, with no lack of results to show for.

                                                                1. 1

                                                                  I mean, I’ve written Go code nearly daily since before it was 1.0. I don’t need to argue with you about whether generics are “needed,” which is a pretty slimy way to phrase this.

                                                                  Seems to me like you’re trying to pick a fight. I already said upthread that the description of generics is different from the desire for them.

                                                                  1. -2

                                                                    You were the first to change the subject to you and me instead of sticking to the topic at hand. Downvoting as troll.

                                              2. 1

                                                By superior, I guess you meant shorter?

                                                1. 2

                                                  Compiling a very large go project with a cold cache might take a minute (sub-second once the cache is warm).

                                                  Compiling a fairly small rust app with a warm cache has taken me over a minute (I think it’s a little better than that now).

                                                  1. 1

                                                    Yes, and superior to Rust in that regard. Also the strict requirement to not have unused dependencies contributes to counteract dependency rot, for larger projects.

                                              1. 3

                                                Wow - great description of Yoneda in the context of Haskell at the end!

                                                1. 4

                                                  This is a good start with many missing pieces. Haskell section should discuss Stack, for example.

                                                  1. 1

                                                    Also Bundler for Ruby, the lessons from which heavily influenced the design of Cargo (it was made by the same creators). Could also add NPM?

                                                  1. 6

                                                    Yep, this is how I figured out monads too, but when using Rust! There is more to them though - the laws are important, but it’s sometimes easier to learn them by examples first!

                                                    1. 3

                                                      Can you show an example where a monad is useful in a Rust program?

                                                      (I’m not a functional programmer, and have never knowingly used a monad)

                                                      1. 10

                                                        I learned about monads via Maybe in Haskell; the equivalent in Rust is called Option.

                                                        Option<T> is a type that can hold something or nothing:

                                                        enum Option<T> {
                                                            None,
                                                            Some(T),
                                                        }
                                                        

                                                        Rust doesn’t have null; you use option instead.

                                                        Options are a particular instance of the more general Monad concept. Monads have two important operations; Haskell calls them “return” and “bind”. Rust isn’t able to express Monads as a general abstraction, and so doesn’t have a particular name. For Option<T>, return is the Some constructor, that is,

                                                        let x = Option::Some("hello");
                                                        

                                                        return takes some type T, in this case, a string slice, and creates an Option<T>. So here, x has the type Option<&str>.

                                                        bind takes two arguments: something of the monad type, and a function. This function takes something of a type, and returns an instance of the monad type. That’s… not well worded. Let’s look at the code. For Option<T>, bind is called and_then. Here’s how you use it:

                                                        let x = Option::Some("Hello");
                                                        let y = x.and_then(|arg| Some(format!("{}!!!", arg)));
                                                        
                                                        println!("{:?}", y);
                                                        

                                                        this will print Some("Hello!!!"). The trick is this: the function it takes as an argument only gets called if the Option is Some; if it’s None, nothing happens. This lets you compose things together, and reduces boilerplate when doing so. Let’s look at how and_then is defined:

                                                        fn and_then<U, F>(self, f: F) -> Option<U> 
                                                        where F: FnOnce(T) -> Option<U>
                                                        {
                                                            match self {
                                                                Some(x) => f(x),
                                                                None => None,
                                                            }
                                                        }
                                                        

                                                        So, and_then takes an instance of Option and a function, f. It then matches on the instance, and if it’s Some, calls f passing in the information inside the option. If it’s None, then it’s just propagated.

                                                        How is this actually useful? Well, these little patterns form building blocks you can use to easily compose code. With just one and_then call, it’s not that much shorter than the match, but with multiple, it’s much more clear what’s going on. But beyond that, other types are also monads, and therefore have bind and return! Rust’s Result<T, E> type, similar to Haskell’s Either, also has and_then and Ok. So once you learn the and_then pattern, you can apply it across a wide array of types.

                                                        Make sense?

                                                        1. 3

                                                          Make sense?

                                                          It absolutely does! I’ve used and_then extensively in my own Rust code, but never known that I was using a monad. Thanks for the explanation Steve.

                                                          But there’s one gap in my understanding now. Languages like Haskell need monads to express things with side-effects like IO (right?). What’s unique about a monad that allows the expression of side effects in these languages?

                                                          1. 7

                                                            No problem!

                                                            This is also why Rust “can’t express monads”, we can have instances of individual monads, but can’t express the higher concept of monads themselves. For that, we’d need a way to talk about “the type of a type”, which is another phrasing for “higher minded types”.

                                                            So, originally, Haskell didn’t have monads, and IO was done another way. So it’s not required. But, I am about to board a flight, so my answer will have to wait a bit. Maybe someone else will chime in too.

                                                            1. 2

                                                              higher minded types

                                                              (Just so others don’t get confused, I think you meant “kinded” here, right?)

                                                              1. 1

                                                                Heh, yes. Thanks.

                                                            2. 3

                                                              A monad has the ability to express sequence, which is useful for imperative programming. It’s not unique, e.g. you can write many imperative programs using just monoid, functor, applicative or many other tools.

                                                              The useful function you get out of realising that IO forms a Monad is:

                                                              (>>=) :: IO a -> (a -> IO b) -> IO b
                                                              

                                                              An example of using this function:

                                                              getLine >>= putStrLn
                                                              
                                                              1. 4

                                                                I should say Monad is unique in being able to express that line of code, but there’s many imperative programs which don’t need Monad. For example, just Semigroup can be used for things like this:

                                                                putStrLn "Hello" <> putStrLn "World"
                                                                

                                                                Or we could read some stuff in with Applicative:

                                                                data Person = Person { firstName :: String, lastName :: String }
                                                                liftA2 Person getLine getLine
                                                                

                                                                So Monad isn’t about side-effects or imperative programming, it’s just that imperative programming has a useful Monad, among other things.

                                                                1. 2

                                                                  You are way ahead of me here and I’m probably starting to look silly, but isn’t expressing sequence in imperative languages trivial?

                                                                  For example (Python):

                                                                  x = f.readline()
                                                                  print(x)
                                                                  

                                                                  x must be evaluated first because it is an argument of the second line. So sequence falls out of the hat.

                                                                  Perhaps in a language like Haskell where you have laziness, you can never be sure if you have guarantees of sequence, and that’s why a monad is more useful in that context? Even then, surely data dependencies somewhat impose an ordering to evaluation?

                                                                  For me, the utility of Steve’s and_then example wasn’t only about sequence, it was also about being able to (concisely) stop early if a None arose in the chain. That’s certainly useful.

                                                                  1. 2

                                                                    but isn’t expressing sequence in imperative languages trivial?

                                                                    Yes.

                                                                    In Haskell it is too:

                                                                    (>>=) :: IO a -> (a -> IO b) -> IO b
                                                                    

                                                                    But we generalise that function signature to Monad:

                                                                    (>>=) :: Monad m => m a -> (a -> m b) -> m b
                                                                    

                                                                    We don’t have a built in idea of sequence. We just have functions like these. A generalisation which comes out is Monad. It just gives code reuse.

                                                                    1. 1

                                                                      Maybe is an instance of a monad, and there are many different kinds of monads. If you think of Maybe as “a monad that uses and_then for sequencing”, then “vanilla” sequencing can be seen as “a monad that uses id for sequencing” (and Promises in JavaScript can be seen as “a monad that uses Promise#flatMap for sequencing”).

                                                                      Yes, expressing sequence in eager imperative languages is trivial because you can write statements one after the other. Now imagine a language where you have no statements, and instead everything is expressions. In this expression-only language, you can still express sequence by using data dependencies (you hit this nail right on the head). What would that look like? Probably something like this (in pseudo-JavaScript):

                                                                      function (next2) {
                                                                        (function (next) {
                                                                          next(f.readline())
                                                                        })(function (readline_result) {
                                                                          next2(print(readline_result))
                                                                        })
                                                                      }
                                                                      

                                                                      with additional plumbing so that each following step has access to the variables bound in all steps before it (e.g. by passing a dictionary of in-scope variables). A monad captures the spirit of this, so instead of doing all the plumbing yourself, you choose a specific implementation of >>= that does your plumbing for you. The “vanilla” monad’s (this is not a real thing, I’m just making up this name to mean “plain old imperative sequences”) implementation of >>= just does argument plumbing for you, whereas the Maybe monad’s implementation of >>= also checks whether things are None, and the Promise monad’s implementation of >>= also calls Promise#then and flattens any nested promises for you.

                                                                      What’s useful here is the idea that there is this set of data structures (i.e. monads) that capture different meanings of “sequencing”, and that they all have a similar interface (e.g. they have all an implementation of >>= and return with the same signature) so you can write functions that are generic over all of them.

                                                                      Does that make sense?

                                                                  2. 2

                                                                    There is a comment below saying it pretty succintly:

                                                                    A monad is basically defined around the idea that we can’t always undo whatever we just did (…)

                                                                    To make that concrete, readStuffFromDisk |> IO.andThen (\stuff -> printStuff stuff) - in the function after andThen, the “stuff” is made available to you - the function runs after the side effect happened. You can say it needed specific API and the concept of monads satisfies that API.

                                                                    Modelling IO with monads allows you to run functions a -> IO b (take a pure value and do an effectful function on it). Compare that to functions like a -> b (Functor). These wouldn’t cut it - let’s say you’d read a String from the disk - you could then only convert it to another string but not do an additional effect.

                                                                    EDIT: I might not have got the wording entirely right. I ommited a part of the type annotation that says the a comes an effect already. With Functor you could not reuse values that came from an effect; with Monad you can.

                                                            1. 4

                                                              Went to the National Gallery of Victoria (Melbourne, Australia), and saw some works of art that are on loan from MoMA in New York. Was very inspiring!

                                                              Gonna do some more messing around on Pikelet. Trying to look at implicit arguments, and rework how I do variable binding. As you may notice I created an organisation for it last week!

                                                              1. 4

                                                                Moved my language project (dependent typed systems programming language) to a new Github organisation: https://github.com/pikelet-lang - been working up to a 0.1 release, which will sadly just be an interpreter. I am definitely aiming for unboxed data types and closures, linear types, compile time evaluation, and low level interop in order to win the right for the ‘systems language’ moniker though.

                                                                1. 19

                                                                  While I do find Pony interesting from a technical perspective, I’ll admit that publishing every point release changelog to lobsters is an extremely generous understanding of what some of us would like the “release” tag to showcase.

                                                                  Major features? Sure. Security fixes, yes. But:

                                                                  Pony 0.23.0 is primarily a bug fix release but does also include some additional changes. Upgrading is recommended. There is a single breaking change caused by the implementation of RFC 56.

                                                                  is arguably less interesting.

                                                                  1. 4

                                                                    Yeah, I’d be fine with a post when notable, interesting things have been added/removed/changed. Pony is an interesting language! But yeah, posting this one seems like it is wasting a bit of goodwill.

                                                                    1. 2

                                                                      as I write this, this story is hidden by 8, +10, -5 spam, which indicates to me that might be better to refrain unless there is a major milestone, or an interesting new feature that might spur some discussion.

                                                                      Adding some additional commentary about why the breaking type change is interesting or useful might help; there are enough language implementation nerds who read lobsters regularly that it could generate some discussion, especially since the capability types are one of pony’s most compelling and novel features.

                                                                      1. 4

                                                                        I wont be posting releases to lobsters anymore.

                                                                        1. 3

                                                                          I don’t think anyone wants you to stop posting releases. They are just expecting more from each post.

                                                                          1. 1

                                                                            Perhaps. Either way. Not submitting them anymore.

                                                                    1. 1

                                                                      Does anyone know of a good introduction to property based testing?

                                                                      1. 1

                                                                        At what level? Lectures by the quickcheck guy are usually a good basic introduction.

                                                                        1. 1

                                                                          http://propertesting.com/ is a nice one, targeted at Erlang.

                                                                          1. 1

                                                                            This blog for the hypothesis Python library has a lot of great articles about how to use this stuff in “enterprise-y” software.

                                                                            To be honest it was way more convincing to me than most other articles as to the utility of this stuff for higher-level applications

                                                                          1. 4

                                                                            Can anyone help me understand why Metal was designed? Apple’s a heavy hitter in Khronos, right? So what was it that they felt like they couldn’t accomplish with OGL/OCL? Are there non-Mac targets that support Metal?

                                                                            1. 6

                                                                              OpenGL is a tired old API that is too high level for high performance graphics work. At the time when Metal was being developed folks were working on lower level APIs to expose the GPU more, like Mantle and DirectX 12, and Metal was Apple’s offering. I believe Mantle eventually evolved into Vulkan, but for some reason Apple is continuing to promote Metal. It’s a nicer API for Swift users, but that’s about it. I would have preferred that they’d make a safe API over Vulkan for Swift like Vulkano, they seem to be under some weird impression that they’ll be able to trap devs in their platform with their own, proprietary API. Or maybe they just can’t bear to give up all the sunk cost.

                                                                              1. 2

                                                                                they seem to be under some weird impression that they’ll be able to trap devs in their platform with their own, proprietary API

                                                                                Is it not working quite well for Microsoft with DirectX?

                                                                              2. 1

                                                                                As I vaguely recall, it started on ios as a way to utilize their graphics chips faster and more efficiently (lower overhead).

                                                                              1. 5

                                                                                Thankfully there’s MoltenVK, an implementation of the Vulkan API on top of Metal. Khronos has thankfully worked to get it open sourced for us! I’ve not used it before, so can’t claim it works as advertised, but if I was doing graphics work that’s where I’d head these days. Very sad that Apple continues to dig in its heels on this.

                                                                                1. 3

                                                                                  Thought this was really neat. I’ve often thought that formal methods would be super handy on the front end, and likewise, I’ve also thought that formal methods folks could also learn a ton from UI folks. Great to see some work done towards bridging the gap!

                                                                                  1. 3

                                                                                    One of the killer features of the Alloy specification language is that Daniel Jackson deeply cares about UIs. It makes using the IDE way more pleasant than any comparable FM.

                                                                                    1. 1

                                                                                      I previously submitted this work that applied formal methods to GUI. It’s more complex but integrates with Spin. The visual formalism in this article reminded me of what Tersus looked like years ago when I looked at it. I’m not sure what it looks like now.

                                                                                    1. 2

                                                                                      The whole thing is great, but one idea that seems particularly useful in arbitrary languages without regard to how it fits with other features is to specify the list of globals used by a function. In Python-like syntax, imagine this:

                                                                                      def draw_quad(origin, left, up) [m]:
                                                                                          m.Position3fv(origin - left - up)
                                                                                          m.Position3fv(origin + left - up)
                                                                                          m.Position3fv(origin + left + up)
                                                                                          m.Position3fv(origin - left + up)
                                                                                      

                                                                                      Now you get a guarantee that the function uses zero globals besides m.

                                                                                      1. 4

                                                                                        In PHP you have something like that, global variables are not accesible from inside functions unless you specifically allow each one you want

                                                                                        $m = new M();
                                                                                        function draw_quad($orgin, $left, $up){
                                                                                            global $m; // or $m = $_GLOBALS['m'];
                                                                                            $m->Position3fv($origin - $left -$up);
                                                                                            $m->Position3fv($origin + $left - $up);
                                                                                            $m->Position3fv($origin + $left + $up);
                                                                                            $m->Position3fv($origin - $left + $up);
                                                                                        

                                                                                        in practice, I haven’t found useful global variables other then the contextual ones ($_POST. $_GET, $_SESSION), which are superglobal and always defined

                                                                                        1. 2

                                                                                          I’d like to see something similar but generalised to “contextual” environmental bindings, rather than traditional global vars. And a compiler that ensures that somewhere in all call chains the binding exists. But you might want a global in some cases, or a “threadlocal”, or an “import” of some sort, or something like the context in react, etc.

                                                                                          Some mechanism in which the compiler makes sure the environmental dependencies are fulfilled, without necessarily requiring that value be propagated explicitly through each owner/container between provider and consumer.

                                                                                          1. 4

                                                                                            I can’t find it but Scala has an extra context var that is passed with function invocation.

                                                                                            And early Lisps had dynamic scope, meaning that a var bound to the next occurrence up the call stack.

                                                                                            Both of these mechanisms supply the building blocks for AoP, so that a programmer can mixin orthogonal properties.

                                                                                            1. 3

                                                                                              And early Lisps had dynamic scope, meaning that a var bound to the next occurrence up the call stack.

                                                                                              Today they still have it - see DEFVAR and DEFPARAMETER in Common Lisp.

                                                                                              1. 2

                                                                                                I can’t find it but Scala has an extra context var that is passed with function invocation.

                                                                                                In Scala you can use implicit parameters for this:

                                                                                                def foo(origin: Vec3, left: Vec3, up: Vec3)(implicit m: Context) {
                                                                                                    m.Position3fv(origin - left - up)
                                                                                                    m.Position3fv(origin + left - up)
                                                                                                    m.Position3fv(origin + left + up)
                                                                                                    m.Position3fv(origin - left + up)
                                                                                                }
                                                                                                

                                                                                                In Haskell you could use a reader/writer/state monad thingy. In Koka or Eff you could use effects.

                                                                                                1. 2

                                                                                                  Yeah, Scala’s context is probably closest to what I’m thinking of, from what I know of it.

                                                                                                2. 4

                                                                                                  You can kinda get this with effect types. Effect types let you label certain functions as using resource A or B, and then you can have a documentation mechanism for what dependencies are used, without passing stuff around.

                                                                                                  It can still get a bit heavy (at least it is in Purescript), but less so than dependency injection

                                                                                                3. 1

                                                                                                  A compiler or analyzer should be able to tell you that just from what variables or expressions go into the function. A previously-declared global would be one of the arguments. Why do we need to declare it again in the function definition?

                                                                                                  1. 1

                                                                                                    See my final sentence. “Now you get a guarantee that the function uses zero globals besides m.” The program documents/communicates what it uses. The compiler ensures the documentation is always up to date.

                                                                                                    In my example, m is not one of the arguments. Because m is a global, lexically accessible anyway in the body of the function. There’s no redundancy here.

                                                                                                    1. 0

                                                                                                      I’m saying a compiler pass should be able to do the same thing without a language feature. I think it won’t be important to that many people. So, it will probably be optional. If optional, better as a compiler pass or static analysis than a language feature. It might also be an easy analysis.

                                                                                                      1. 3

                                                                                                        You’re missing the point. It’s a list of variables that the code can access anyway. What would a compiler gain by analyzing what globals the function accesses?

                                                                                                        There are many language features that help the compiler do its job. This isn’t one of them. The whole point is documentation.

                                                                                                        (Yes, you could support disabling the feature, using say syntax like [*]. C++ has this. Just bear in mind that compilers also don’t require code comments, and yet comments are often a good idea. SImilarly, even languages with type inference encourage documenting types in function headers.)

                                                                                                  2. 1

                                                                                                    What if the Position3fv method uses global variable n? You also need to specify that for draw_quad. This quickly blows up like checked exceptions in Java and people want shortcuts.

                                                                                                    1. 2

                                                                                                      The problem with checked exceptions is that they discourage people from using exceptions. But there’s no problem in discouraging people from using global variables.

                                                                                                      I don’t mean to imply that I want all code to state what it needs all the time. In general I’m a pretty liberal programmer. It just seems like a good idea to give people the option to create “checkable documentation” about what globals a piece of code requires.

                                                                                                  1. 6

                                                                                                    Sometimes I wonder why recent languages don’t support multiple [return] values. We are even using destructuring assignment as a poor man’s multiple values. It solves the problem described in the article w/o cluttering the functions code with conditionals

                                                                                                    1. 2

                                                                                                      Most recent languages honestly do (Python, Elixir, Go, Rust, Kotlin, and Swift (I think) to name a few), and they’re getting backported in some others (C# 7 and C++17, for example) by improving tuples.

                                                                                                      I think the bigger issue with most of these languages is they don’t have anything like ML/Rust/Erlang-style fail-if-the-return-looks-like-this mode that lets you effectively use them as if they were exceptions when you want—e.g., the frequent pattern in Erlang/Elixir of Foo, ok = some_fun(), or Rust’s let v = bar()?;. Without that, you either do what Ruby’s doing here (throw an exception if exception: true is passed), or you have to explicitly check yourself (where Go is the extreme example of if foo, err := bar(); err != nil { ... } being a constant refrain).

                                                                                                      1. 1

                                                                                                        I don’t now about Elixir, Go or Kotlin, but neither Python or Go support multiple-values. In Go you have to accept to assign all the values the function returns, which kinda defeats the whole purpose of having multiple return values in the first place! (I checked in a REPL just to be sure). I haven’t kept up with recent developments in Python3 but AFAIK Pythonistas use lists or dictionaries to mimic multiple-values. Similarly in ES6 people use dictionaries + destructuring assignment in ES6. For Example

                                                                                                        const foo = () => ({a: 1, b: 2})
                                                                                                        
                                                                                                        const {a} = foo()
                                                                                                        
                                                                                                        console.log(a)
                                                                                                        

                                                                                                        Prints 1. The ES6 approach is better than Golangs and Python solution but it still forces the caller to reuse the name the callee decided on[0] plus all the return values are allocated on the stack.

                                                                                                        AFAICT Rust again doesn’t support multiple value but uses pattern matching (which destructuring assignment is one example of) to mimic them.

                                                                                                        Multiple values are useful for more than exception handling btw, one example is the division operator

                                                                                                        [0]: Yeah I know one can rebind the name, but the syntax is cumbersome to use and one still have to remember the name of the value as opposed to its purpose, which is easier to remember).

                                                                                                        1. 2

                                                                                                          Well, now I’m confused. On the one hand, you say,

                                                                                                          …neither Python [n]or Go support multiple-values

                                                                                                          …and then immediately link to a REPL that starts with “Go supports multiple return values,” and even shows an example of ignoring some of the returned values. Likewise, while I agree with you that Python uses sequences for multiple returns behind-the-scenes, the use in practice looks like

                                                                                                          def foo(a, b): return b, a
                                                                                                          c, d = foo(1, 2)
                                                                                                          

                                                                                                          which is indistinguishable in practice from what I do in an ML. (And in Python 3.5 and later, you can even do things like a, b, *rest = foo().) Python even has multiple-return division operations (see math.fmod from as far back as Python 2.7).

                                                                                                          Could you give an example of a language that does what you want, and how it differs from the previous? I feel as if we’re using the same words, but for radically different things.

                                                                                                          1. 2

                                                                                                            I’m sorry I wasn’t more clear in my previous reply.

                                                                                                            Could you give an example of a language that does what you want, and how it differs from the previous?

                                                                                                            Common Lisp.

                                                                                                            and how it differs from the previous?

                                                                                                            • The caller doesn’t have to be aware that the callee supports multiple values unless they want to use it.
                                                                                                            • The compiler can determine at the call site how many values are going to be used and allocate appropriately.

                                                                                                            which is indistinguishable in practice from what I do in an ML.

                                                                                                            Yes, and to the best of my knowledge MLs don’t support multiple values. Pattern matching enables one to mimic multiple values, which is what most recent language are doing and it provides at least 90% of the value of having multiple values.

                                                                                                            The Go REPL example shows that the caller must be aware of the amount of values the callee is returning, which leads to clumsy UX. I’m guessing you wanted to link to modf but I’m not sure what you were getting at. The CL example of / shows how we can re use the same function instead of having mod and div. Similarly be taken advantage retrieving the value in a map where nil indicates absence without affecting the UX of the ‘happy path’ (ie. nil is not possible a value in the map).

                                                                                                            –––––––

                                                                                                            It is kinda ironic that my comment was motivated out of the idea of less is more in language design, in light of all the feature creep I’m seeing recently in JS and Ruby, but it appears that the approach of recent languages is better example of less is more ¯_(ツ)_/¯

                                                                                                            1. 1

                                                                                                              to the best of my knowledge MLs don’t support multiple values

                                                                                                              Returning multiple values (like in Go) is just a special case of tuples, which most MLs support. So you don’t need an extra language feature for this. But I wouldn’t use this for errors like Go does - it makes more sense to use a variant/sum type for this.

                                                                                                    1. 3

                                                                                                      I am trying to solve a similar/related problem in a personal project of my own that specifies a grammar and language for generating configurations. It’s very much a work in progress but has what looks like some parallels to this languages goals.

                                                                                                      My attempts that are in no way ready for anyone to use for serious things is at: https://crates.io/crates/ucg and documented at: https://docs.rs/ucg/0.1.0/ucglib/

                                                                                                      1. 4

                                                                                                        I’d also recommend checking out Dhall: https://github.com/dhall-lang/

                                                                                                        1. 1

                                                                                                          I’ve seen dhall and liked some of what it was doing. It’s good to see movement in this space. We don’t really need more configuration languages though. What we need are good ways to manage all the different types of configuration languages we currently have that isn’t a template language.

                                                                                                      1. 5

                                                                                                        Finally implemented dependent record types in Pikelet! Now working on adding some simple support for pattern matching.

                                                                                                        1. 3

                                                                                                          As I see it, despite the wave of blog posts, Rust is still niche. Its usage is very small compared to the massively popular languages. I predict that unless Rust changes a lot and makes it easy for anyone to use, its usage will remain comparable to Haskell or Lisp usage: widely loved or desired language but too weird for mass adoption.

                                                                                                          Let’s see in five years how this plays out. Maybe I’ll be wrong.

                                                                                                          1. 11

                                                                                                            I don’t disagree and that makes me sad for what we as programmers ask for from our tools. I went through something like this a long time ago. I chose the “easy route” over the “difficult” one. I wish I hadn’t.

                                                                                                            In my case, I went with C over Ada because Ada frustrated me and it was hard to get it to compile. That my C programs were easy to compile but crashed all the time on me was never something that I saw as problematic until I gained a lot more experience.

                                                                                                            I wrote a bit about that a couple years back.

                                                                                                            https://www.monkeysnatchbanana.com/2016/02/22/learning-from-ada/

                                                                                                            1. 3

                                                                                                              Good write-up. That should be a submission instead. :)

                                                                                                              1. 4

                                                                                                                Thanks. Already submitted a couple years ago. Once from me is enough. If it ever reappears on lobste.rs, it will be because of someone else.

                                                                                                            2. 3

                                                                                                              I’d like to like Rust, but it seems a complex language.

                                                                                                              I’m not talking about the borrow checker, but the large number of features it proposes.

                                                                                                              There must be a way to get a good statically typed safe language that is simple… or not?

                                                                                                              1. 2

                                                                                                                Which features do you think can be removed without sacrificing safety while maintaining usability?

                                                                                                                1. 2

                                                                                                                  What a wonderful and deep question!

                                                                                                                  The honest answer is: I don’t know. :-)

                                                                                                                  However I usually do not consider simple anything that qualify as syntactic sugar (eg macros). Generics are useful but increase the language complexity. Same goes for Traits or Closures.

                                                                                                                  Note that I like each of these features.

                                                                                                                  But I would like more a safe language where there is always one single obvious way to do each thing.

                                                                                                                  1. 5

                                                                                                                    But I would like more a safe language where there is always one single obvious way to do each thing.

                                                                                                                    Have you used Rust in anger yet? It is not that far from this ideal. Certainly closer than a lot of other languages I have experience with, although I think perfection on this front isn’t really attainable.

                                                                                                                    Macros are a good candidate to remove, in theory. But it’s a classic example of letting perfection be the enemy of good. Simple use of macros saves me a lot of annoyance, especially when writing tests. I’ve managed to stay away from more complex uses in all but one instance in my four years of working in Rust nearly daily. Not everyone has avoided them though.

                                                                                                                    I don’t see how you could get a safe language with low level control without generics though. I can almost live happily without generics in a language like Go, but not Rust. Traits come right along with that as the implementation of useful generics (not the only choice, but you need something).

                                                                                                                    I’m surprised to see you mention closures. They are as standard of a feature as they come.

                                                                                                                    Keep in that Rust isn’t just after safe and static types here. We also want zero overhead abstractions. Combining all three creates a sharp knife that cuts through the design space.

                                                                                                                    Note that I can’t actually answer my question. I am way too buried in Rust to have the requisite perspective. But. I can at least challenge others on it. :)

                                                                                                                    1. 1

                                                                                                                      I can at least challenge others on it.

                                                                                                                      And you are welcome! I did not tried Rust on any real world project, so my opinion is pretty weak, based on general language considerations, reading the book and few toy programs (written before Rust 1.0).

                                                                                                                      Macros are a good candidate to remove, in theory. […] I’ve managed to stay away from more complex uses…

                                                                                                                      That’s a smell of a design issue: people should not need to pick “the good parts” of a language. ;-)

                                                                                                                      I don’t see how you could get a safe language with low level control without generics though. […]

                                                                                                                      By simple composition.

                                                                                                                      You might object that it’s boring and verbose, and you’re probably right.

                                                                                                                      But it make the language simpler.
                                                                                                                      Note that I’m not advocating the Go approach, which is another complex language, just with a different kind of complexity.

                                                                                                                      I’m surprised to see you mention closures.

                                                                                                                      Again, a cool feature on their own, but they further increase the language complexity.

                                                                                                                      And again, without them, programs will be more verbose.
                                                                                                                      But language’s verbosity might force the programmer to keep things simple and small.

                                                                                                                      We also want zero overhead abstractions.

                                                                                                                      And this is another thing I like about Rust. But, while I know nothing about language design (and it’s pretty out of my sphere of competences) my insight is that it could be simpler without sacrifying safety or zero overhead abstractions.

                                                                                                                      Note that such complexity is not for free: it locks the language to one single LLVM based implementation, that is hard to port to new hardware or new operating systems.

                                                                                                                      1. 4

                                                                                                                        That’s a smell of a design issue: people should not need to pick “the good parts” of a language. ;-)

                                                                                                                        I don’t think so. To be clear, that’s what I meant by letting perfect be the enemy of good. No design is perfect, just like no battle plan survives first contact with the enemy. Many uses of macros do paper over shortcomings in the expressivity of the type system, but fixing that requires more features, like higher kinded polymorphism or integer generics or specialization. I think you’d say all of those things are too complex. Some of those things are or will be coming to Rust, and macros have allowed, in certain circumstances, folks to solve problems that maybe they wouldn’t have otherwise.

                                                                                                                        By simple composition.

                                                                                                                        Note that Rust’s type system already favors composition today, in a way that works well with generics. (i.e., Rust does not have something like class inheritance.)

                                                                                                                        You might object that it’s boring and verbose, and you’re probably right.

                                                                                                                        But it make the language simpler.

                                                                                                                        Very strongly disagree, and my counter argument has nothing to do with “boring” or “verbose,” and everything to do with feasibility. A fundamental design proposition of Rust is that folks can build safe APIs that may or may not use unsafe internally. This is very common in the implementation of generic data structures like hash maps, btrees and growable arrays (vectors). If you didn’t have generics, I don’t know how you would build a single implementation that could be reused for data structures like that in a way that has zero overhead. Rebuilding those data structures isn’t practical, since getting the best performance possible requires unsafe code.

                                                                                                                        You can thumb your nose at this and just say these are symptoms of design flaws, sure, but removing generics would certainly not make using the language simpler. It would be much much harder and more complex. What would be more interesting is to suggest the removal of generics and also provide an idea on how to solve the problem of reusability. Because of the desire to limit the scope of unsafe, reusability is a critical aspect of writing code in Rust, because you really do not want to be re-litigating the implementation of unsafe code. It is much better to do it once, carefully audit it, and get as many others to use it as possible.

                                                                                                                        You might say that dealing with unsafe in this way is itself a flaw. And that the language should be able to permit more things safely. Maybe. But I think that’s an open question at this point, and almost certainly comes with extra type system expressivity, which may make things more complexity. But, that is certainly something Rust can improve on in the future. Nevertheless, it’s hard for me to see an alternative to the core design principle of building safe APIs over unsafe internals.

                                                                                                                        Note that such complexity is not for free: it locks the language to one single LLVM based implementation, that is hard to port to new hardware or new operating systems.

                                                                                                                        No it doesn’t?

                                                                                                                        Anyway, I guess this conversation has run its course. I’d definitely encourage you to keep thinking about concrete suggestions to make a different programming language that accomplishes what Rust does, but in a way that’s simpler. I can maybe imagine some tweaks here and there, but I think omitting some of the big picture features you’ve listed (without replacing them with something else) would be a serious mistake. The fact that you don’t want closures hints to me that we may have fundamentally different opinions that cannot be overcome.

                                                                                                                        1. 2

                                                                                                                          Wow! :-D

                                                                                                                          I think you have largerly overestimated my competence about language design tradeoffs!

                                                                                                                          But frankly, given the quality of your answer, I do not regret the misunderstanding. Thanks! ;-)

                                                                                                                          All you say is reasonable (and informative).
                                                                                                                          And despite being fluent in several different language, I’ve no real clue to propose alternatives. To me Rust feels complex. Not really difficult, just complex.

                                                                                                                          But (and sorry if I poke you more, hoping for an even greater response :-D), all the features I named can be defineds as (properly checked) syntactic sugar.

                                                                                                                          Whatever you can do with macro, generics and even closures, you can do manually. And whatever you can do manually, you can do by generating code.
                                                                                                                          The point is, actually, if you can write or generate boring and safe code.

                                                                                                                          I agree that without such syntactic sugar, some programming tasks become overly verbose.
                                                                                                                          And boring. But this might discourage complex programs, which is a Good Thing (TM).

                                                                                                                          Note that such complexity is not for free: it locks the language to one single LLVM based implementation, that is hard to port to new hardware or new operating systems.

                                                                                                                          No it doesn’t?

                                                                                                                          Do you mean that we have a Rust compiler not based on LLVM IR?

                                                                                                                          I missed that! What a great news!

                                                                                                                          1. 4

                                                                                                                            To me Rust feels complex. Not really difficult, just complex.

                                                                                                                            I see. I understand that, yes. I guess the thing that is more interesting to me is whether it is necessary complexity or incidental complexity. If it’s necessary, then great. We need to pick ourselves up by the bootstraps and find a way to teach people that complexity until it becomes easier to learn. If it’s incidental, then the design needs more work. Of course, all designs will have some amount of incidental complexity, but if there’s a huge area of the design space that Rust is missing, then it might be possible that there is in fact a lot of incidental complexity! Or perhaps changes in hardware in the future will alter the calculus we use today to decide things. It has certainly had an impact on C’s design trade offs.

                                                                                                                            And whatever you can do manually, you can do by generating code.

                                                                                                                            Note that generics are, to some extent, an interface to a code generation tool. It is through an automatic process called monomorphization, but that’s basically all it’s doing internally: code generating. There are other approaches to generics that languages like Java take that don’t necessarily require code generation, but they tend to have performance trade offs that would be unacceptable in a language like Rust.

                                                                                                                            Regardless, for Rust, I’m very skeptical that a simple code generation tool would be feasible in practice, but it is of course untested.

                                                                                                                            Do you mean that we have a Rust compiler not based on LLVM IR?

                                                                                                                            Note that you said that it “locks” the language to one single LLVM based implementation, but there is nothing about Rust that makes it true. A different statement that I might agree with is, “The complexity of the language makes it difficult to build alternative implementations.” :-)

                                                                                                                            With that said, there is actually a Rust compiler that doesn’t depend on LLVM called mrustc. I believe it compiles to C. It is obviously a work in progress, but IIRC, it has bootstrapped rustc itself, which makes it worth taking serious IMO. :-)

                                                                                                                            1. 2

                                                                                                                              And despite being fluent in several different language, I’ve no real clue to propose alternatives. To me Rust feels complex. Not really difficult, just complex.

                                                                                                                              Yeah, Rust is relatively complex. I like the language, and I get a huge amount of leverage out of it, but I think there are some things that could be simplified, combined, and made more powerful and general in a future incarnation of a systems language. But the bones of Rust is still far simpler than C++, and much saner than C, and it has a great ecosystem, so it’s the one I pick!

                                                                                                                              I’m putting my money where my mouth is by tinkering on some ideas for future langs, but don’t be expecting anything this decade! Building languages is hard, especially ones that try to tackle the domains that Rust works in.