1. 51

  2. 14

    Yay! It’s been great to have you on the team!

    In my second week I implemented a change to make a certain pattern more ergonomic. It was refreshing to be able to build the initial functionality and then make a project wide change, confident that given it compiled after the change I probably hadn’t broken anything. I don’t think I would have had the confidence to make such a change as early on in the Ruby projects I’ve worked on previously.

    Yeah, this is what made me consistently frustrated and annoyed at my previous work place (they used Ruby, Elixir, and JS). Static types are a bit of a trade off, and not perfect, but I’m thankful to finally be able to use them in my day job.

    1. 11

      I think the same problem exists even in statically typed languages. Java people talk about the open-close principle all the time, but I think it terribly encourages overengineering and bloat (due to append-only programming). What you really want is a language (and a codebase that isn’t trying hard to thwart it) that gives you the confidence to change anything as the requirements change.

      1. 4

        Sandi Metz hammers this home. I can’t recommend POODR and every talk she gives on youtube enough. It’s all about the cost of maintaining the code.

        1. 3

          I hadn’t heard about Sandi Metz as a non-Rubyist, so went ahead and listened to some of her talks. Her messages are exactly what I would’ve applauded before I met Haskell and functional programming. When you have a very powerful type system behind your back, you’re not afraid of writing code that mixes abstraction levels. You’re not afraid of hard-coding that configuration value, because when you need it, the time it takes to refactor it out to a configuration file is exactly as much as it is now, and the risk is zero in either case. So, in the long term, you end up with a simpler codebase, because you didn’t introduce needless complexity fearing that you might need it later (that’s a very valid concern in Ruby or Java.).

          The principles I recommend to junior Haskellers are much more simple.

          1. Stick to DRY as much as possible
          2. Make sure your non-local assumptions are reflected in the types (purity is a side-effect of that principle)
          3. Don’t work around abstractions, go in and refactor them
          1. 2

            Yeah, as I said in another comment, I think this is why Prince is such a nice code base, even after 16 years. Although Rust isn’t pure like Mercury or Haskell, it still seems to provide similar benefits as well.

          2. 1

            Yeah, Sandi Metz’s stuff is really great from what I’ve seen!

          3. 2

            Aka The Smalltalk Advantage

            1. 1

              I think it’s fine to have to change code when requirements change, so I’m less strict about following open-close stuff from the Java world. What I’m looking for is tooling that helps me know what to change and where - that’s something that can really help when requirements change underneath you. I definitely find that type systems in Rust, Haskell, OCaml, etc to give that power to refactor in a far more ruthless way than Java’s does - although I find Java a step up from say, Ruby, where there tends to be much fear around changing things, even in with a good test suite in place.

            2. 1

              Would unit tests help in this case? I’ve regularly had something compile and later segfault , (not sure if that’s possible with rust), whereas often the tests caught that.

              1. 3

                Barring compiler bugs Rust’s memory model guarantees no segfaults in “safe Rust”. Use of the unsafe keyword allows dereferencing of raw pointers so if you get that wrong you could get a segfault. Our code only uses unsafe for FFI with the existing application so it’s possible to get that wrong but when writing and testing the Rust in isolation we’ve written no unsafe (although the standard library does make use of it).

                1. 3

                  Barring compiler bugs Rust’s memory model guarantees no segfaults in “safe Rust”.

                  That’s not actually true, stack overflows are for instance not exactly hard to cause. Segfaults just don’t violate memory safety.

                2. 2

                  You can still get panics and stuff in safe Rust, and there are plenty of things that you definitely need tests for. You don’t need to worry about segfaults if you’re in safe code though (barring bugs in Rust and the standard library).

                  The change in question was more an API change - something that would be tedious and error-prone to do a find and replace for when using Ruby. The type system can do a pretty good job of telling you where you need to change things at the the exact place where you need to make that change.

              2. 2

                I noticed you submitted the Helix article as well. Do you use it in production?

                1. 6

                  No, we do use the FFI quite a bit though - most of the higher level code at YesLogic is implemented in Mercury and we communicate to Rust via the C FFI.

                  1. 5

                    That might be worth a writeup itself on the ups and downs of Mercury in production.

                    1. 6

                      Disclaimer: I don’t work on the Mercury part of the code myself, so this is just what I’ve gleaned from outside:

                      According to my boss it’s given us around a 10%ish advantage (in units of ‘something’)? But yeah, for a 16 year old code base, and it looks extremely well maintained, with very little accumulated tech debt getting in the way of development. I’m sure that is largely due to the highly competent people behind it, but Mercury offers similar properties to ML, Haskell, Rust, etc. in that you can refactor it quite heavily, constantly simplifying things over time while preventing too much of it becoming brittle and hard to maintain.

                      I think perhaps these days Haskell might have been a better choice given the fact that it has developed a larger community, but at the time Prince was started Haskell wasn’t really in the position it was today.

                      In practice it seems like the logic programming side of things doesn’t seem to be used all that much. It’s kind of surprising given our domain - layout, which involves a bit of constraint solving. Most of our code tends to be functional/imperative rather than declarative because often it’s better to implement your own domain-specific solving. The lack of meta-programming features in Mercury can also tend us towards making codegen scripts which is a bit of a pain.

                      The heavy use of persistent data structures can be a pain in tight loops, but generally it’s ok for our high level layout code. In the past we’ve used C for that, but we are now transitioning that low level code to Rust because we know how error prone C can be. It also means we can better take advantage of some of the ecosystem our friends at Mozilla are making, and contribute back as well!

                      So yeah, in general Mercury seems to be serving us well, and we’ve got no plans to rewrite everything in another language!

                      1. 1

                        Very interesting! You said the logic programming side of things doesn’t get used much. Looking at web site a while back made me think Mercury was primarily a logic language with some functional stuff in it. What do you mean it’s mostly functional/imperative? Is only a small part in Mercury or does Mercury let you write code that way?

                        1. 2

                          Oh, it’s like in Haskell - you can thread through IO ‘state’ in order to do imperative effectful stuff. Eg. just as Haskell is functional, but you can write it in an imperative way, Mercury is declarative but you can also write it in a pretty imperative way. It’s a bit clunkier in Mercury though, because you’re explicitly passing that state around, rather than letting type class elaboration do that for you.

                          I think the main parts that see a bunch of use are being able to use multiple combinations of argument modes for the same predicate, which kind of gets around Rust’s issue of not being able to abstract over the way you pass arguments. This is quite neat for code reuse, but not a massive win.

                  2. 2

                    No, it was just an interesting article.