Threads for rebeccaskinner

  1. 1

    How does this whole article manage to not say “grapheme” once?

    1. 2

      I really wanted to keep the article focused on debugging rather than spending more time on unicode terminology than was necessary to follow the example, but I guess I could have added that term in. I noticed a couple of typos in the article as well, so if I have an opportunity to make edits I can try to add an aside if you think the article is worse off for the terminology I used.

      1. 2

        I find that any use of the word “character” can be dangerous in most contexts, but especially since you have to basically explain grapheme clusters anyway (talking about ZWJ etc) it seemed odd to not at least name the thing being explained.

    1. 7

      Hey everyone, I hope you enjoy this article. I wanted to disclose that I wrote this article in part to promote my book Effective Haskell. I also wrote the article because a lot of people find debugging in Haskell to be tricky, or don’t expect that you can do normal print style debugging in a pure language. I thought having a tutorial for how to use common debugging techniques would be helpful.

      1. 2
         printf "%c (0x%x): %s: Count: %d -> %d" char (ord char) (show $ classifyCharacter char)
           count count
        

        If we run this code, we get exactly what we’d hoped: we’re adding and subtracting when we should be, and we correctly count 4 characters in the file:

        I’m pretty sure what you actually wanted here was:

        printf "%c (0x%x): %s: Count: %d -> %d" char (ord char) (show $ classifyCharacter char) 
          count newCount
        

        Nice article though, I think that Debug.Trace is very underrated when it comes to debugging Haskell code, particularly recursive code. I’ve used this many times in the decade plus I’ve been using Haskell:

        foo x y 
            | traceShow ("foo",x,y) False = undefined
            | otherwise = foo y (x+y)
        

        which gives an output like:

        ("foo",1,1)
        ("foo",1,2)
        ("foo",2,3)
        ("foo",3,5)
        ...
        
      1. 28

        I wish the project success- there’s a lot of innovation still to happen in programming languages and I’m always glad to see new ones getting built.

        That said, I’m a little skeptical of the recent trends in optional typing. I don’t dismiss the idea that we might be able to make it work, but my experience so far has been that optional typing ends up being a worst-of-both-worlds scenario. You don’t really get the benefits of a strong static type system, since you have to deal with potentially untyped values, but you still spend some amount of time appeasing the type checker for the code that you did end up trying to make well typed. In the best case scenario you end up with an un-typed language, but more often than not they turn out to have all of the safety and reliability of untyped languages with the quick development time and short iteration cycles of a typed language.

        1. 16

          Agreed! The following struck me as a pretty typical “pitch” for optional / gradual typing, but it doesn’t make sense (to me):

          When prototyping, don’t specify any types, and Stanza will behave like a dynamically-typed scripting language. But when confident in your design, gradually add in types to make your code bulletproof.

          I tend to think about programs as transformations applied to data, so the literal first thing I think about is the types (the data). I don’t write the transformations until I’ve got the types mostly worked out anyway, so there’s no point at which I want to omit the types.

          1. 9

            I agree. I see optional typing as a way of adding some benefits of static typing to dynamic languages when you can’t start over from scratch. I don’t see the point of starting a language from scratch with optional typing, unless there is some high-value externality that induces it (Typescript wanting compatibility with Javascript, for instance).

            1. 7

              my experience so far has been that optional typing ends up being a worst-of-both-worlds scenario. You don’t really get the benefits of a strong static type system, since you have to deal with potentially untyped values, but you still spend some amount of time appeasing the type checker for the code that you did end up trying to make well typed

              As far as I can tell, every optionally typed language is simply going about it wrong: The default is that the programmer has to prove something to the type system, otherwise you get a static type error. A better default would be type annotations that cooperate with a runtime contract system for which only guaranteed runtime failures are converted to static errors. For cases where you do in fact want to force static checking, you could signal some assertion as static.

              Another way to think about it is that type systems produce three-valued logics: proven, unproven, and disproven.

              What happens when something is unproven is the key design point: Most type systems error. Unsound type systems “assume” the unproven thing is true. A contract system “checks” the unproven things at runtime. We should flip the default form “assume” to “check”, which requires runtime support. Explicit assume, runtime-check, and static-assert syntaxes could provide escape hatches for unsafe eliding of runtime checking, pushing out the boundaries of statically verified code, and requiring static proof respectively.

              1. 4

                You’ll like Pytype, which only errors on disproven code: https://github.com/google/pytype#how-is-pytype-different-from-other-type-checkers

                1. 2

                  Neat!

                  The other big one I’ve heard of (but have not tried) is Erlang’s “Dialyzer” based on “success types”: https://easychair.org/publications/paper/FzsM

                  Unfortunately, I don’t think either of these perform have a runtime checking/contract system.

                  1. 1

                    we even quote and link to stanza in our typing faq :) never actually used stanza beyond a few toy programs, but i’m a big fan of the design and ideas behind it.

                  2. 3

                    A better default would be type annotations that cooperate with a runtime contract system for which only guaranteed runtime failures are converted to static errors.

                    This is how SBCL handles Common Lisp’s optional type annotations by default, “Declarations as Assertions”: “all type declarations that have not been proven to always hold are asserted at runtime.”

                    1. 1

                      Sadly, as far as I can tell from the CL implementations I’ve seen, they only support the declaration of primitive types and arrays thereof. Things get really interesting once you get to user-defined type constructors, higher-kinded types, first-class functions, mutable objects, etc. Especially true if you want to preserve non-functional properties, such as constant space usage or predictable runtime. For example, there are trivial cases where contracts can cause behavior to become accidentally quadratic with naive implementations.

                      1. 2

                        It doesn’t include your entire wishlist, but CL’s type system does support user-defined type constructors, arbitrary unions and intersections of types, etc., which is enough to do algebraic data types and some mildly fancy types like “nonempty list” or “integer outside the range [-500,500]”. There’s an overview here: https://alhassy.github.io/TypedLisp

                  3. 5

                    I think most people who reach for optional typing, based on the pitch they use, really want whole program type inference. Haskell and a few others have this. Most languages with “inference” just have local inference and so you still have to “think about type annotations” but there is no reason to require that.

                    1. 4

                      Speaking as someone who prefers dynamic typing (for live coding, creative coding, generative design, and hobby projects), the benefits I see in using type annotations in a dynamic language are: speeding up performance sensitive code, and getting high quality error messages when I misuse a library function. For the latter, I want error messages to tell me which argument was bad in a call to a library function – I don’t want a stack trace from the interior of a library function that requires me to understand the code.

                      Neither of these benefits requires that all type errors should be reported at compile time. If a statically typed library function runs fast and gives good error messages at runtime, then it meets the requirements.

                      The only time I expect to “fight the type checker” is when I have descended into the language’s statically typed low-level subset to write some high performance code.

                      I think that type annotations and compile time type errors are both excellent additions to a dynamically typed language. By the latter, I mean the compiler is able to deduce and report some run-time errors at compile time. This doesn’t lead to “fighting the type checker” if the static checker doesn’t report false positives (which static type systems will invariably do).

                      My reading of the Stanza docs is that Stanza is not a pure dynamic language. It switches to static type checking rules (inaccurately reporting false-positive type errors on good code) if all of your variables have type annotations. It’s a compromise that attempts to appeal to both static- and dynamic-typing users, but I suspect doesn’t give either side all that they want. So if, by “optional typing”, you mean this kind of compromise, then I may agree.

                      1. 1

                        that’s been my experience with Typescript, for sure.

                        1. 1

                          Typescript’s an interesting one, in that the strictness is adjustable and can be ramped up as you migrate your JS code over. For new projects you just start with super-strict settings.

                          1. 2

                            And yet it’s only so strict. No runtime checks and no runtime references to types.

                            1. 2

                              The whole point is to get rid of the need for runtime checks :)

                              1. 2

                                That’s just not possible if there’s any IO. Well… maybe it’s not technically not possible but it’s culturally not possible. Maybe if every library in the world used strict TypeScript and didn’t do casts then there wouldn’t need to be runtime checks.

                                1. 1

                                  Which is why a single any rapidly spreads out like cancer and turns your typedefs into lies.

                                  1. 2

                                    It doesn’t have to be any it can be as too. For example: await fetch().json() as MyAPIResponse

                              2. 1

                                That is the theoretical aim, but in practice it winds up just being a half-typed codebase IME. Kind of a shame tbh. But, it’s turned me off of believing this language direction is trustworthy, as opposed to better linter.

                          1. 8

                            Y’all do realize this is basically marketing spam for the author’s book Effective Haskell, right?

                            Like, complete with a play on the title of the book in this article’s title and the massive CTA at the end, right?

                            1. 9

                              I really am sorry that it came across this way. You’re right that the article is something I wrote because my publisher asked me to write something to help people get excited about Haskell to coincide with the launch of the book into beta. That said, I wrote the article, and shared it here, because I legitimately love Haskell and I think it’s a great choice and a lot more people would benefit from giving it another earnest chance as a useful language. A lot of things that pitch Haskell as a solution are deeply technical, and I thought that it might be more accessible to speak to people about Haskell in terms of the every day problems that they have and why I think Haskell is a useful choice for solving them.

                              1. 1

                                Thank you for the explanation!

                              2. 4

                                I give it leeway because Rebecca has been on Lobsters for a while and contributed as a good-faith community member. Though I would have appreciated it if she included a top-level comment on the submission saying it was for her new book.

                                1. 5

                                  Thanks for calling out that I should have written a top-level comment. It didn’t really occur to me to do so, but I’ll make it a point to do that in the future if anything I submit is related to the book. I apologize if it came off as spammy-, I really don’t want to do that.

                              1. 7

                                Better Resource Management

                                Certainly better than in many other languages but things like the bracket function (the “default” version of which is broken due to async exceptions lol oops) are rather “meh” compared to RAII-style ownership. Because nothing forces you to avoid resource leaks… well, now Linear Haskell can do that, but being a newly retrofitted extension it’s not gonna be instantly pervasive.


                                TBH, Haskell is in kind of an awkward spot these days:

                                • if you want to play with type-level magic (and proofs) Idris is a much better fit, all the type-level programming that was done with awkward hacks in Haskell becomes much easier in a fully dependent system;
                                • meanwhile if you want to get practical things done quickly and safely, Rust is absolutely kicking ass on all fronts and it’s really hard to pick anything else!
                                1. 10

                                  I love Rust, but I don’t think it’s a clear winner over Haskell personally.

                                  In Rust, the affine types and lack of garbage collection are really great when I’m working on low-level code. As someone who has written a lot of C and a lot of Haskell, Rust undeniably hits a lot of my requirements. For a lot of day-to-day work though, I still find that I’m much more likely to pick up Haskell. Little things like higher kinded types and GADTs end up being a big force multiplier for me being able to build the sorts of APIs that work best for me. I also really value laziness and the syntactic niceties like having universal currying when I’m working in Haskell.

                                  None of that is anything negative about Rust. I really admire what the Rust community has done. If anything, I think rustaceans are in a great position to leverage all of the things they’ve learned from Rust so that they can more quickly and easily dip a toe into Haskell and see if it might be useful to them sometimes. In the end I don’t think we have to view each other as competitors, so much as two languages that sit in somewhat different spots of the ecosystem that can learn and benefit one another.

                                  1. 7

                                    I think rustaceans are in a great position to leverage all of the things they’ve learned from Rust so that they can more quickly and easily dip a toe into Haskell and see if it might be useful to them sometimes.

                                    This is exactly where I am in my PL journey (outside of work). I’ve been writing Rust for 5 years and it’s a great language for all the reasons you mentioned and more. I also found Rust a nice intro to writing real world code that is more FP than OO (i.e: structs/record and traits/type-classes instead of classes and interfaces) while still having static types (I love lisps but I tend to work better with types). Now I’m getting into Haskell and so far the process have been fairly smooth and very enlightening. The type system is far more expressive and I can see myself being highly productive in Haskell (far more than Rust) not having to worry about memory management and some of the more restrictive aspects of the Rust type system. If the learning process continues I wouldn’t be surprised if Haskell becomes my “go-to” for most problems, but Rust is still there for when I care more about performance and resource usage.

                                    1. 2

                                      It will be interesting to see how attitudes towards resource collection shift with the advent of linear types in Haskell.

                                    2. 1

                                      Yes, my opinion is that Rust has successfully stolen the best ideas from Haskell and made them more palatable to a mass audience.

                                    1. 1

                                      Haskell is great, except for all the monad transformer stuff. That’s all an absolute nightmare. At this point, I just don’t really see a reason to use it over Rust for writing practical (i.e. non-research) software. Rust has the most important pieces from Haskell.

                                      1. 12

                                        My experience with monad transformers is that they can offer a lot of practical value. There’s a little bit of a learning curve, but I think that in practice it’s a one-time cost. Once you understand how they work, they don’t tend to add a lot of cognitive burden to understanding the code, and can often make it easier to work with.

                                        I do like some of the work people are doing with effects systems to address the down sides of monad transformers, and eventually we might move on, but for a lot of day to day work it’s just very common to end up doing a lot of related things that all need to, e.g. share some common information, might fail in the same way, and need to be able to do some particular set of side effects. A canonical example would be something like accessing a database, where you might have many functions that all need to access a connection pool, talk to the database, and report the same sorts of database related errors. Monad transformers give you a really practically effective way to describe those kinds of things and build tooling to work with them.

                                        1. 8

                                          What’s wrong with “all the monad transformer stuff”?

                                          1. 3

                                            Monads are mostly complexity for the sake of being able to imagine that your functions are “pure”. I have not found any benefits for such an ability, besides purely philosophical, at least in the way most functional programming languages are built. There are better ways, that can forgo the need for imagination, but the functional programming crowd doesn’t seem to find them.

                                            1. 15

                                              Monads are for code reuse, they’re absolutely, completely, not at all about purity.

                                              1. 3

                                                I have not found them any good for that use case either. The code I’ve seen usually ends up as a recursive monad soup, that you need to write even more code to untangle. They can work well in some limited contexts, but those contexts can often work just as well using other programming constructs in my opinion. Limited code reuse in general is a problem with many half-assed solutions that only work in limited contexts, for example inheritance, DSLs, composition(the OOP kind), etc. Monads are just another one of them, and honestly, they are just as, if not more easy to overuse as the other solutions.

                                                1. 9

                                                  I do not understand this perspective at all. traverse alone saves me an astonishing amount of work compared to reimplementing it for every data structure/applicative pair.

                                                  1. 2

                                                    The reason you need traverse at all is monads. It’s all complexity for the sake of complexity in my eyes.

                                                    1. 5

                                                      Not at all. traverse works for a far wider class of things than just monads. And even if a language didn’t have the notion of monad it would still benefit from a general interface to iterate over a collection. That’s traverse.

                                                      1. 3

                                                        general interface to iterate over a collection

                                                        So, a for loop? A map() in basically any language with first-class functions?

                                                        Anyways, my comment about needing traverse at all is in response of needing to reimplement it for many different data structures. The problem I see in that, is that the reason you get all of those data structures is because of Monads. There a lot less of a need to have such a function when you don’t have monads.

                                                      2. 3

                                                        The reason you need traverse at all is monads. It’s all complexity for the sake of complexity in my eyes.

                                                        How would you write, say,

                                                        traverseMaybeList :: (a -> Maybe b) -> [a] -> Maybe [b]
                                                        traverseEitherBoolSet :: (a -> Either Bool b) -> Set a -> Either Bool (Set b)
                                                        

                                                        in a unified way in your language of choice?

                                                        1. 3

                                                          On a good day, I’d avoid the Maybe and Either types that are used for error handling, and just have good old exceptions and no need any traversal. On a bad day, I’d probably have to use traverse, because Maybe and Either, are monads, and create this problem in the first place.

                                                          1. 1

                                                            I think if you prefer exceptions to Maybe/Either then you’re sort of fundamentally at odds with Haskell. Not saying this in a judgmental way, just that “exceptions are better than optional/error types” is not how Haskell thinks about things. Same with Rust.

                                                            Though, even in Python I typically write functions that may return None over functions that throw an exception.

                                                            1. 1

                                                              I think if you prefer exceptions to Maybe/Either then you’re sort of fundamentally at odds with Haskell.

                                                              I’m pretty sure by just disliking monads I’m at odds with Haskell as it currently is. But do note, that not all exceptions are crafted equally. Take Zig for example, where errors functionally behave like traditional exceptions, but are really more similar to error types in implementation. A lot nicer than both usual exceptions, and optional/error types in my opinion.

                                                              Though, even in Python I typically write functions that may return None over functions that throw an exception.

                                                              It really depends if the function makes sense if it returns a none. If you’re trying to get a key from cache, and the network fails, returning a None is fine. If you are trying to check if a nonce has been already used, and network fails, returning None is probably the wrong thing to do. Exceptions are a great way to force corrective behavior from the caller. Optional types have none of that.

                                                              1. 1

                                                                I don’t understand why you say Zig error types “behave like traditional exceptions”. My understanding is that if I have a function that returns a !u32, I can’t pass that value into a function that takes a u32.

                                                                Similarly, I don’t understand the idea that exceptions force correctional behavior. If I have a function that throws an exception, then I can just… not handle it. If I have a function that returns an error type, then I have to specify how to handle the error to get the value.

                                                                1. 1

                                                                  Yes, but essentially, you are either handling each error at the call site, or, more often, you bubble the error upwards like an exception. You end up with what I would call forcibly handled exceptions.

                                                                  Not correcting some behavior leads to your program dying outright with exceptions. If you handle the exception, I’d say you are immediately encouraged to write code that corrects it, just because of how the handling is written. With functions that return an error type, it’s very easy to just endlessly bubble the error value upwards, without handling it.

                                                                  1. 1

                                                                    With functions that return an error type, it’s very easy to just endlessly bubble the error value upwards, without handling it.

                                                                    If I have an Optional Int, and I want to put it in a function that takes an int, I have to handle it then and there. If I have an optional int and my function signature says I return an int, I must handle it within that function. The optional type can’t escape out, versus exceptions which can and do.

                                                          2. 2

                                                            I’d argue that these specific types are actually not very useful. If any error occurs, you don’t get _any _ results? In my experrience it’s more likely that we need to partition the successful results and log warnings for the failures. The problem with these rigidly-defined functions is that they don’t account for real-world scenarios and you just end up writing something by hand.

                                                            1. 1

                                                              Haskell’s standard library is anything but rigid in my opinion. Take the specific case of “something that contains a bunch of (Maybe item).

                                                              • If you want a list of all items inside Just but only if there is no Nothing anywhere, you write toList <$> traverse l.
                                                              • If you want a list of all items inside Just, you can write fold $ toList <$> l.
                                                              • If you want just the first item, if any, you write getFirst $ fold $ First <$> l
                                                              • If you want the last item, if any, you can write getLast $ fold $ Last <$> l

                                                              These are specific to Maybe, especially the First and Last, I’ll give you that. But functions from the stdlib can be snapped together in a huge number of ways to achieve a LOT of things succinctly and generally.

                                                              1. 1

                                                                OK, this doesn’t actually answer my question. Say I have a stream of incoming data. What I really want to do is validate the data, log warnings for the ones that fail, and stream out the ones that succeed.

                                                                1. 2

                                                                  Then use an API that’s designed for streaming processing of data, for example https://hackage.haskell.org/package/streamly

                                                              2. 1

                                                                I wrote up a few different operations on “collections of Maybe” or “collections of Either” in Haskell. The total number of lines of code required to express these operations using the standard Haskell library was around 12, including some superfluous type signatures. They cover all the cases in my other comment, as well as the “partitioning” you mention in your post. Here’s a gist:

                                                                https://gist.github.com/DanilaFe/71677af85b8d0b712ba2d418259f31dd

                                                    2. 9

                                                      Monads are mostly complexity for the sake of being able to imagine that your functions are “pure”.

                                                      That’s not what they’re for. Monad transformers (well, the transformers in the mtl with the nice typeclasses) in particular let you clearly define what effects each piece of code has. This ends up being pretty useful: if you have some sort of monad for, say, SQL server access, you can then see from a given function’s type if it does any SQL transactions. If you attempt to do SQL where you’re not supposed to, you get a type error warning you about it. I think that’s pretty convenient. There’s lots of examples of this. If you’re using the typeclasses, you can even change the effect! Instead of reading from an actual db, you could hand off mocked up data if you use one monad and real db info with the other. This is pretty neat stuff, and it’s one of my favorite features of Haskell.

                                                      I agree that they might not always be super clear (and monad transformers start to have pretty shit perf), but they’re not just for intellectual pleasure.

                                                      1. 1

                                                        Monad transformers (well, the transformers in the mtl with the nice typeclasses) in particular let you clearly define what effects each piece of code has.

                                                        Slight correction: they usually let you define what classes of effects a piece of code has. This of course can range in abstraction, from a very specific SQLSelect to an overly broad, and not at all descriptive IO. One problem often seen with this, is that methods often combine several different effects to achieve the result, which leads to either having an obnoxiously large function signature, or having to merge all the effects under the more generic one, whether that be the more useful SQL if you’re lucky and the method only touches the SQL, or the frankly useless IO, in both cases loosing a big part of the usefulness of it.

                                                        But the thing is, that you don’t need monads to achieve any of that anyways. If you represent external state (which the effects are meant to move away from you) as an input to a function, and the function outputs the same external state back, just with the commands it wants to do, a runtime can perform the IO, and bring you back the information on second function call. This of course might be somewhat counter-intuitive, as people are used for their main() function to be run only once, but it leads to another way of thinking, a one where you are more aware of what state you carry, and what external systems each function can interface with, as it lives straight in the function signature, only with an opportunity to hide inside a type to group several of them. This style would also naturally promote IO pipelining, since you easily can (and probably want to) submit more than one IO request at once. You can build the IO runtime on anything you want as well, be it io_uring, or a weird concoction of cloud technologies, if you provide your program with the same interface. It also brings the same testing possibilities, even slightly more, as making a golden data tests becomes ridiculously easy. More impressively, it brings the possibility of relatively easy time-travel debugging, as you only need to capture the inputs to the main function every function call to accurately replay the whole computation, and in part, enable to check some fixes without even re-doing the IO. I think this is a better way to move towards in functional programming, but I myself don’t have the time, or motivation in functional programming to push it that way.

                                                        1. 2

                                                          Classes of effects instead of effects is a distinction without a difference, right? I can define a monad typeclass that only puts things in a state and a monad typeclass that only takes things out instead of using StateT (in fact they exist and are called Reader and Writer), and I can get as granular as I’d like with it. The amount of specificity you want is entirely up to you.

                                                          I agree that the IO monad is pretty frustratingly broad. You’re also correct that you don’t need monads to do this sort of thing. I’m having a little bit of trouble understanding your replacement. You mean a function with external state a and pure inputs b with result c should have the type a -> b -> (c, a), right? What would you do when you want to chain this function with another one?

                                                          1. 1

                                                            No. Your main function’s signature looks like a -> a. And a runtime calls it again and again, taking the actions the function specified in the output type that contains the external state objects, performing them, and putting the results back into the same objects. Your other functions as such grow in a similar manner, for example a function that takes an external resource a and a pure input b, to for example submit a write request, it would look like a -> b -> a. An important thing to note, that it only submits a request, but doesn’t do it yet. It would only be performed once the main function ends, and the runtime takes over. As such, you couldn’t do reading as trivially as a -> b -> (a, c), as you cannot read the data out while “your” code is running. This isn’t great for usability, but that can in large part be solved by using continuations.

                                                            As a side note, I don’t particularly enjoy chaining. It’s another solution that is only needed because monads make it appear that the function isn’t performing IO, when it’s more useful for you to think that it does. With continuations, you could just make this look like several function calls in a row, with plain old exceptions to handle errors.

                                                            1. 2

                                                              This seems far more complex than using monads to me, but different people think in different ways. I don’t know what you mean by you don’t enjoy chaining— you don’t like sequencing code?

                                                              1. 1

                                                                I like sequencing code, but I don’t enjoy sequencing code with monads, since monads force the sequencing of code they touch to be different, just because they are monads.

                                                                1. 2

                                                                  Can you provide an example of monads changing the way you sequence code? That’s one of the major benefits of do-notation in my mind: you can write code that looks like it is executing sequentially.

                                                                  1. 2

                                                                    The do-notation is the problem. Why would sequencing functions that do IO would need to be different from sequencing functions that don’t? IO is something normal that a program does, and functional programming just makes it weird, because it likes some concept of ‘purity’, and IO is explicitly removed from it when the chosen solution are monads.

                                                                    1. 2

                                                                      Because functions that do IO have to have an order in which they execute. The machinery of a monad lets you represent this. I don’t care which side of (2+2) + (2+2) executes first, but I do care that I read a file before I try to display its content on screen.

                                                                      1. 1

                                                                        In the general case, you don’t care about the order the IO executes as long as you don’t have any dependencies between it. multiply(add(2, 2), 2) will always perform addition first, multiplication second, just like displayData(readFile(file)) will always read the file first, and display the data second. Compiler will understand this, without needing to distinguish the functions that do IO, from those that don’t. In the few cases where you don’t have any fully direct data dependencies, but still need to perform IO in specific order, you then may use specific barriers. And even with them, it would still feel more natural for me.

                                                                        1. 2

                                                                          In the general case, it’s impossible to determine which code might depend on the other. A contrived counter example would be writing to a socket of program a, that itself writes to the socket of program b, and then writing to the socket of program b. The order here matters, but no compiler would be able to determine that.

                                                                          In the few cases where you don’t have any fully direct data dependencies, but still need to perform IO in specific order, you then may use specific barriers.

                                                                          Yes, these specific barriers are exactly what monads provide.

                                                                          Can you provide an example of a monad restructuring how you want to sequence something? I’m very open to seeing how they fall short, I haven’t written Haskell in a long time (changed jobs) but I miss the structure monads give very often.

                                                                          1. 3

                                                                            Of course no compiler can determine all dependencies between IO. In other languages you don’t need to worry much about it, because in other languages the evaluation order is well defined. Haskell though, forgoes such definition, and with the benefits it brings, it also brings it’s problems, namely, the inability to easily order unrelated function evaluation. There is seq and pseq, but they are frowned upon because they break monads :). So the way the IO monad works is by introducing artificial data dependencies between each monad. This feels quite hacky to me. But do note that this is mostly a problem with Haskell, and many other functional programming languages that are full of monads could get rid of them without much change in the language semantics.

                                                                            Monads don’t change how I sequence something. But they do greatly annoy me, by needing special handling. It’s like mixing async and non-async code in other languages - either you go fully one way, or fully the other. Mixing both does not work well.

                                                                            1. 2

                                                                              seq and pseq are definitely not frowned upon!

                                                                              1. 1

                                                                                Monads don’t change how I sequence something.

                                                                                Then why did you say:

                                                                                I like sequencing code, but I don’t enjoy sequencing code with monads, since monads force the sequencing of code they touch to be different, just because they are monads.

                                                                                They also don’t need special handling. Do-notation is syntax sugar, but there’s nothing in Haskell that privileges monads outside of the standard library deciding to use them for certain things. They are just a typeclass, the same as any other.

                                                              2. 1

                                                                As a response to your edit: no, reading is still a class of actions. You can read a single byte, or you can read a million bytes, and those two are very different actions in my mind. Trying to represent such granularity in monads is difficult, and honestly, a waste of time, since you don’t need such granular control anyways. But this is mostly disagreements in definition at this point, so no need to discuss this further I think.

                                                              3. 1

                                                                Yeah, linear IO is a major motivator for my work on Dawn.

                                                            2. 8

                                                              This does not match my experience using monads at all.

                                                              1. 1

                                                                Monads arise naturally from adjoint functors. Perhaps they are not obvious, but that does not mean that they are artificially complex.

                                                                It sounds like you vaguely disapprove of effects-oriented programming, but you need to offer concrete alternatives. Handwaves are not sufficient here, given that most discussion about monads comes from people who do not grok them.

                                                                1. 3

                                                                  Monads arise naturally from adjoint functors. Perhaps they are not obvious, but that does not mean that they are artificially complex.

                                                                  Such technobabble explanations are why I try to distance myself from functional programming. While technically correct, they offer no insight for people who do not already understand what monads are.

                                                                  It sounds like you vaguely disapprove of effects-oriented programming, but you need to offer concrete alternatives. Handwaves are not sufficient here, given that most discussion about monads comes from people who do not grok them.

                                                                  I do, in this comment. It might not be the most understandable, it might not have the strong mathematical foundations, and it definitely is wildly different as to how people usually think about programs. But I still think that it can offer better understanding of the effects your program does, besides giving a bunch of other advantages.

                                                                  Also, I don’t disapprove of effects-oriented programming, it’s just that monads are a terrible way of doing it. I feel like there are a lot of better ways of making sure effects are explicit, my suggestion being one of them, effect handlers being the other one about which I learned recently.

                                                                  1. 2

                                                                    I looked this up and it seems that the idea that every monad comes up as an adjunction occurs, if you define a category based on that monad first. isn’t this totally cyclic?

                                                                    1. 3

                                                                      In many cases, the algebras for a monad will be things we already cared about. In fact, that was sort of the original point of monads – a way of abstractly capturing and studying a wide variety of algebraic theories together.

                                                                      For example, if you’re familiar with the list monad, its algebras are simply monoids, and so its Eilenberg-Moore category is (at least equivalent to) the category of monoids.

                                                                      There are other monads whose algebras would be groups, or rings, or vector spaces over a field K, or many others.

                                                                      But I think Corbin was probably not referring to the way in which every monad comes from at least one adjunction (or two, if you also throw in the one involving the Kleisli category), but rather that if you already have adjunctions hanging around, you get a monad (and a comonad) from each of them in a very natural way. If you’re familiar with order theory by any chance, this is a direct generalisation of how you get a closure operator from a Galois connection between partially ordered sets.

                                                                      This entire discussion is almost completely irrelevant to someone using monads to get programming tasks done though. As an abstraction of a common pattern that has been showing up in combinator libraries since the early days of functional programming, you can fully understand everything you need to know about it without any of this mathematical backstory.

                                                                      Why we recognise the monad structure in programming is mostly not really to be able to apply mathematical results – maybe occasionally there will be a spark of inspiration from that direction, but largely, it’s just to save writing some common code over and over for many libraries that happen to have the same structure. Maybe monad transformers take that an additional step, letting us build the combinator libraries a bit more quickly by composing together some building blocks, but these would all still be very natural ideas to have if you were just sitting down and writing functional programs and thinking about how to clean up some repetitive patterns. It would still be a good idea even if the mathematicians hadn’t got to it first.

                                                                2. 2

                                                                  It’s completely opaque to me how to get them to do what I need them to do. I found myself randomly trying things, hoping something would work. And this is for someone who found Rust lifetimes to be quite straightforward, even before NLL.

                                                              1. 44

                                                                Name popular OSS software, written in Haskell, not used for Haskell management (e.g. Cabal).

                                                                AFAICT, there are only two, pandoc and XMonad.

                                                                This does not strike me as being an unreasonably effective language. There are tons of tools written in Rust you can name, and Rust is a significantly younger language.

                                                                People say there is a ton of good Haskell locked up in fintech, and that may be true, but a) fintech is weird because it has infinite money and b) there are plenty of other languages used in fintech which are also popular outside of it, eg Python, so it doesn’t strike me as being a good counterexample, even if we grant that it is true.

                                                                1. 28

                                                                  Here’s a Github search: https://github.com/search?l=&o=desc&q=stars%3A%3E500+language%3AHaskell&s=stars&type=Repositories

                                                                  I missed a couple of good ones:

                                                                  • Shellcheck
                                                                  • Hasura
                                                                  • Postgrest (which I think is a dumb idea, lol, but hey, it’s popular)
                                                                  • Elm
                                                                  • Idris, although I think this arguably goes against the not used for Haskell management rule, sort of

                                                                  Still, compare this to any similarly old and popular language, and it’s no contest.

                                                                  1. 15

                                                                    Also Dhall

                                                                    1. 9

                                                                      I think postgrest is a great idea, but it can be applied to very wrong situations. Unless you’re familiar with Postgres, you might be surprised with how much application logic can be modelled purely in the database without turning it into spaghetti. At that point, you can make the strategic choice of modelling a part of your domain purely in the DB and let the clients work directly with it.

                                                                      To put it differently, postgrest is an architectural tool, it can be useful for giving front-end teams a fast path to maintaining their own CRUD stores and endpoints. You can still have other parts of the database behind your API.

                                                                      1. 6

                                                                        I don’t understand Postgrest. IMO, the entire point of an API is to provide an interface to the database and explicitly decouple the internals of the database from the rest of the world. If you change the schema, all of your Postgrest users break. API is an abstraction layer serving exactly what the application needs and nothing more. It provides a way to maintain backwards compatibility if you need. You might as well just send sql query to a POST endpoint and eliminate the need for Postgrest - not condoning it but saying how silly the idea of postgrest is.

                                                                        1. 11

                                                                          Sometimes you just don’t want to make any backend application, only to have a web frontend talk to a database. There are whole “as-a-Service” products like Firebase that offer this as part of their functionality. Postgrest is self-hosted that. It’s far more convenient than sending bare SQL directly.

                                                                          1. 6

                                                                            with views, one can largely get around the break the schema break the API problem. Even so, as long as the consumers of the API are internal, you control both ends, so it’s pretty easy to just schedule your cutovers.

                                                                            But I think the best use-case for Postgrest is old stable databases that aren’t really changing stuff much anymore but need to add a fancy web UI.

                                                                            The database people spend 10 minutes turning up Postgrest and leave the UI people to do their thing and otherwise ignore them.

                                                                            1. 1

                                                                              Hah, I don’t get views either. My philosophy is that the database is there to store the data. It is the last thing that scales. Don’t put logic and abstraction layers in the database. There is plenty of compute available outside of it and APIs can do precise data abstraction needed for the apps. Materialized views, may be, but still feels wrong. SQL is a pain to write tests for.

                                                                              1. 11

                                                                                Your perspective is certainly a reasonable one, but not one I or many people necessarily agree with.

                                                                                The more data you have to mess with, the closer you want the messing with next to the data. i.e. in the same process if possible :) Hence Pl/PGSQL and all the other languages that can get embedded into SQL databases.

                                                                                We use views mostly for 2 reasons:

                                                                                • Reporting
                                                                                • Access control.
                                                                                1. 2

                                                                                  Have you checked row-level security? I think it creates a good default, and then you can use security definer views for when you need to override that default.

                                                                                  1. 5

                                                                                    Yes, That’s exactly how we use access control views! I’m a huge fan of RLS, so much so that all of our users get their own role in PG, and our app(s) auth directly to PG. We happily encourage direct SQL access to our users, since all of our apps use RLS for their security.

                                                                                    Our biggest complaint with RLS, none(?) of the reporting front ends out there have any concept of RLS or really DB security in general, they AT BEST offer some minimal app-level security that’s usually pretty annoying. I’ve never been upset enough to write one…yet, but I hope someone someday does.

                                                                                    1. 2

                                                                                      That’s exactly how we use access control views! I’m a huge fan of RLS, so much so that all of our users get their own role in PG

                                                                                      When each user has it its own role, usually that means ‘Role explosion’ [1]. But perhaps you have other methods/systems that let you avoid that.

                                                                                      How do you do for example: user ‘X’ when operating at location “Poland” is not allowed to access Report data ‘ABC’ before 8am and after 4pm UTC-2, in Postgres ?

                                                                                      [1] https://blog.plainid.com/role-explosion-unintended-consequence-rbac

                                                                                      1. 3

                                                                                        Well in PG a role IS a user, there is no difference, but I agree that RBAC is not ideal when your user count gets high as management can be complicated. Luckily our database includes all the HR data, so we know this person is employed with this job on these dates, etc. We utilize that information in our, mostly automated, user controls and accounts. When one is a supervisor, they have the permission(s) given to them, and they can hand them out like candy to their employees, all within our UI.

                                                                                        We try to model the UI around “capabilities”, all though it’s implemented through RBAC obviously, and is not a capability based system.

                                                                                        So each supervisor is responsible for their employees permissions, and we largely try to stay out of it. They can’t define the “capabilities”, that’s on us.

                                                                                        How do you do for example: user ‘X’ when operating at location “Poland” is not allowed to access Report data ‘ABC’ before 8am and after 4pm UTC-2, in Postgres ?

                                                                                        Unfortunately PG’s RBAC doesn’t really allow us to do that easily, and we luckily haven’t yet had a need to do something that detailed. It is possible, albeit non-trivial. We try to limit our access rules to more basic stuff: supervisor(s) can see/update data within their sphere but not outside of it, etc.

                                                                                        We do limit users based on their work location, but not their logged in location. We do log all activity in an audit log, which is just another DB table, and it’s in the UI for everyone with the right permissions(so a supervisor can see all their employee’s activity, whenever they want).

                                                                                        Certainly different authorization system(s) exist, and they all have their pros and cons, but we’ve so far been pretty happy with PG’s system. If you can write a query to generate the data needed to make a decision, then you can make the system authorize with it.

                                                                                2. 4

                                                                                  My philosophy is “don’t write half-baked abstractions again and again”. PostgREST & friends (like Postgraphile) provide selecting specific columns, joins, sorting, filtering, pagination and others. I’m tired of writing that again and again for each endpoint, except each endpoint is slightly different, as it supports sorting on different fields, or different styles of filtering. PostgREST does all of that once and for all.

                                                                                  Also, there are ways to test SQL, and databases supporting transaction isolation actually simplify running your tests. Just wrap your test in a BEGIN; ROLLBACK; block.

                                                                                  1. 2

                                                                                    Idk, I’ve been bitten by this. Probably ok in a small project, but this is a dangerous tight coupling of the entire system. Next time a new requirement comes in that requires changing the schema, RIP, wouldn’t even know which services would break and how many things would go wrong. Write fully-baked, well tested, requirements contested, exceptionally vetted, and excellently thought out abstractions.

                                                                                    1. 6

                                                                                      Or just use views to maintain backwards compatibility and generate typings from the introspection endpoint to typecheck clients.

                                                                              2. 1

                                                                                I’m a fan of tools that support incremental refactoring and decomposition of a program’s architecture w/o major API breakage. PostgREST feels to me like a useful tool in that toolbox, especially when coupled with procedural logic in the database. Plus there’s the added bonus of exposing the existing domain model “natively” as JSON over HTTP, which is one of the rare integration models better supported than even the native PG wire protocol.

                                                                                With embedded subresources and full SQL view support you can quickly get to something that’s as straightforward for a FE project to talk to as a bespoke REST or GraphQL backend.. Keeping the schema definitions in one place (i.e., the database itself) means less mirroring of the same structures and serialization approaches in multiple tiers of my application.

                                                                                I’m building a project right now where PostgREST fills the same architectural slot that a Django or Laravel application might, but without having to build and maintain that service at all. Will I eventually need to split the API so I can add logic that doesn’t map to tuples and functions on them? Sure, maybe, if the app gets traction at all. Does it help me keep my tiers separate for now while I’m working solo on a project that might naturally decompose into a handful of backend services and an integration layer? Yep, also working out thus far.

                                                                                There are some things that strike me as awkward and/or likely to cause problems down the road, like pushing JWT handling down into the DB itself. I also think it’s a weird oversight to not expose LISTEN/NOTIFY over websockets or SSE, given that PostgREST already uses notification channels to handle its schema cache refresh trigger.

                                                                                Again, though, being able to wire a hybrid SPA/SSG framework like SvelteKit into a “native” database backend without having to deploy a custom API layer has been a nice option for rapid prototyping and even “real” CRUD applications. As a bonus, my backend code can just talk to Postgres directly, which means I can use my preferred stack there (Rust + SQLx + Warp) without doing yet another intermediate JSON (un)wrap step. Eventually – again, modulo actually needing the app to work for more than a few months – more and more will migrate into that service, but in the meantime I can keep using fetch in my frontend and move on.

                                                                            2. 2

                                                                              I would add shake

                                                                              https://shakebuild.com

                                                                              not exactly a tool but a great DSL.

                                                                            3. 21

                                                                              I think it’s true that, historically, Haskell hasn’t been used as much for open source work as you might expect given the quality of the language. I think there are a few factors that are in play here, but the dominant one is simply that the open source projects that take off tend to be ones that a lot of people are interested in and/or contribute to. Haskell has, historically, struggled with a steep on-ramp and that means that the people who persevered and learned the language well enough to build things with it were self-selected to be the sorts of people who were highly motivated to work on Haskell and it’s ecosystem, but it was less appealing if your goals were to do something else and get that done quickly. It’s rare for Haskell to be the only language that someone knows, so even among Haskell developers I think it’s been common to pick a different language if the goal is to get a lot of community involvement in a project.

                                                                              All that said, I think things are shifting. The Haskell community is starting to think earnestly about broadening adoption and making the language more appealing to a wider variety of developers. There are a lot of problems where Haskell makes a lot of sense, and we just need to see the friction for picking it reduced in order for the adoption to pick up. In that sense, the fact that many other languages are starting to add some things that are heavily inspired by Haskell makes Haskell itself more appealing, because more of the language is going to look familiar and that’s going to make it more accessible to people.

                                                                              1. 15

                                                                                There are tons of tools written in Rust you can name

                                                                                I can’t think of anything off the dome except ripgrep. I’m sure I could do some research and find a few, but I’m sure that’s also the case for Haskell.

                                                                                1. 1

                                                                                  You’ve probably heard of Firefox and maybe also Deno. When you look through the GitHub Rust repos by stars, there are a bunch of ls clones weirdly, lol.

                                                                                2. 9

                                                                                  Agree … and finance and functional languages seem to have a connection empirically:

                                                                                  • OCaml and Jane St (they strongly advocate it, mostly rejecting polyglot approaches, doing almost everything within OCaml)
                                                                                  • the South American bank that bought the company behind Clojure

                                                                                  I think it’s obviously the domain … there is simple a lot of “purely functional” logic in finance.

                                                                                  Implementing languages and particularly compilers is another place where that’s true, which the blog post mentions. But I’d say that isn’t true for most domains.

                                                                                  BTW git annex appears to be written in Haskell. However my experience with it is mixed. It feels like git itself is more reliable and it’s written in C/Perl/Shell. I think the dominating factor is just the number and skill of developers, not the language.

                                                                                  1. 5

                                                                                    OCaml also has a range of more or less (or once) popular non-fintech, non-compiler tools written in it. LiquidSoap, MLDonkey, Unison file synchronizer, 0install, the original PGP key server…

                                                                                    1. 3

                                                                                      Xen hypervisor

                                                                                      1. 4

                                                                                        The MirageOS project always seemed super cool. Unikernels are very interesting.

                                                                                        1. 3

                                                                                          Well, the tools for it, rather than the hypervisor itself. But yeah, I forgot about that one.

                                                                                      2. 4

                                                                                        I think the connection with finance is that making mistakes in automated finance is actually very costly on expectation, whereas making mistakes in a social network or something is typically not very expensive.

                                                                                      3. 8

                                                                                        Git-annex

                                                                                        1. 5

                                                                                          Not being popular is not the same as being “ineffective”. Likewise, something can be “effective”, but not popular.

                                                                                          Is JavaScript a super effective language? Is C?

                                                                                          Without going too far down the language holy war rabbit hole, my overall feeling after so many years is that programming language popularity, in general, fits a “worse is better” characterization where the languages that I, personally, feel are the most bug-prone, poorly designed, etc, are the most popular. Nobody has to agree with me, but for the sake of transparency, I’m thinking of PHP, C, JavaScript, Python, and Java when I write that. Languages that are probably pretty good/powerful/good-at-preventing-bugs are things like Haskell, Rust, Clojure, Elixir.

                                                                                          1. 4

                                                                                            In the past, a lot of the reason I’ve seen people being turned away from using Haskell based tools has been the perceived pain of installing GHC, which admittedly is quite large, and it can sometime be a pain to figure out which version you need. ghcup has improved that situation quite a lot by making the process of installing and managing old compilers significantly easier. There’s still an argument that GHC is massive, which it is, but storage is pretty cheap these days. For some reason I’ve never seen people make similar complaints about needing to install multiple version of python (though this is less off an issue these days).

                                                                                            The other place where large Haskell codebases are locked up is Facebook - Sigma processes every single post, comment and massage for spam, at 2,000,000 req/sec, and is all written in Haskell. Luckily the underlying tech, Haxl, is open source - though few people seem to have found a particularly good use for it, you really need to be working at quite a large scale to benefit from it.

                                                                                            1. 2

                                                                                              hledger is one I use regularly.

                                                                                              1. 2

                                                                                                Cardano is a great example.

                                                                                                Or Standard Chartered, which is a very prominent British bank, and runs all their backend on Haskell. They even have their own strict dialect.

                                                                                                1. 2

                                                                                                  GHC.

                                                                                                  1. 1

                                                                                                    https://pandoc.org/

                                                                                                    I used pandoc for a long time before even realizing it was Haskell. Ended up learning just enough to make a change I needed.

                                                                                                  1. 3

                                                                                                    The lack of contrast in the code blocks makes it difficult to read.

                                                                                                    1. 7

                                                                                                      I’ve just pushed a change to improve contrast. I hope you’ll find it easier to read now!

                                                                                                    1. 3

                                                                                                      Besides the purple code on purple background, this was very interesting!

                                                                                                      1. 4

                                                                                                        Sorry about that. I didn’t develop the sites color scheme with so many types in mind and I did notice it’s a bit hard to read on some monitors (but of course it looked fine on the monitor I was using to write the post). I’ll try to tweak the theme a bit soon to improve contrast.

                                                                                                        1. 3

                                                                                                          +1, and easy to understand. I definitely understand things much better.

                                                                                                          Thank you!

                                                                                                        1. 7

                                                                                                          It’s great to see folks talking about the practical immediate benefits you can get from nix. OP I hope you don’t let some of the negativity here get you down too much!

                                                                                                          For the detractors: Nix is one of those things where the benefits can be really hard to see at first. So much of what nix does can be done in other ways- so I think sometimes miss out that the real benefit of nix is that it offers a single cohesive and principled way to solve classes of problems that might otherwise require several different dedicated tools. Even narrowing down your view to just the case of version or dependency management, you can do most of what you’d do in nix with virtualenv or go modules or cabal sandboxes or cargo or whatever, but then you are dealing with the individual quirks of each of those different tools. Go up a level and you might also need to deal with alternatives or whatever other mechanism your particular OS uses to select between different system level packages. Up another level and you have to think about versions of docker containers or AMI images. Nix lets you handle all of that configuration coherently, with a single language and set of tools. Buying into nix for a project, your OS, or your entire approach to infrastructure can feel odd at first, but it has a huge force multiplier effect with itself when you buy into it.

                                                                                                          1. 13

                                                                                                            This article is absolute garbage full of both factually incorrect statements (there is absolutely nothing about functional programs that prevent you from having ad-hoc polymorphism, and in fact ad-hoc polymorphism is heavily used in both haskell (typeclasses) and rust (traits), neither of which are traditional OO languages). Although he acknowledges that his arguments are overly reductive, I see absolutely no reason to forgive it given that the dimensions he chooses to reduce the argument to are boring and uniformative, and his conclusion bland. This guy is a misogynist with nothing useful to contribute to the field of software engineering and is best left alone to howl in obscurity into the silence of history.

                                                                                                            1. 4

                                                                                                              there is absolutely nothing about functional programs that prevent you from having ad-hoc polymorphism

                                                                                                              He says exactly that.

                                                                                                            1. 3

                                                                                                              I’m pretty happy with my pinebook pro, but I think it suffers from the same problem as the pinephone: the hardware is underpowered for the default software configuration. Unlike the pinephone, there are a lot of good alternatives you can easily configure.

                                                                                                              My primary computer is a “portable in theory” mobile workstation that is just too big, and has too little battery life, to be reasonable to use if I want to work from the couch or from bed or something. I picked up the pinebook mostly to have a lightweight secondary laptop that I could use for web browsing and writing code. The initial plasma desktop experience was pretty miserable but with Sway set up and some lightweight services running, I can edit documents remotely on my other laptop with ssh, or and use Firefox locally, and the performance is pretty good.

                                                                                                              A tiling window manager is probably not a good out of the box experience for most users, but I expect if they changed the default to xfce or some other lightweight but more traditional looking desktop environment things would feel a lot more smooth.

                                                                                                              1. 4

                                                                                                                The cost of the mug example is a great illustration of the ways that mental shortcuts can lead to big problems when we’re thinking about software. The problem I see starts with the simplification that a mug is just a description and a price. In other words:

                                                                                                                type Mug = (Description, Price)
                                                                                                                

                                                                                                                or if we want to use more fundamental types

                                                                                                                type Mug = (String, Integer)
                                                                                                                

                                                                                                                The author then raises the perfectly reasonable question “What if we want to change the price of the mug”. So far so good, but the next step the author makes is an error caused by conflating two different things. The author says “of course the attribute shouldn’t change! it’s the same mug!” but of course that’s completely wrong. We’ve already defined Mug to be, by definition, the product of a price and a description.

                                                                                                                Instead, we have implicitly moved to a second definition of a Mug that has some other defining attribute. Now, our price is in fact some function of time or some other global property, and the identifier of items in the shop:

                                                                                                                type Mug' = String
                                                                                                                mugPrice : Mug -> Integer
                                                                                                                

                                                                                                                Equality isn’t actually broken, or even unexpected in this view of the world, it’s simply that we’ve switched into an alternative definition of Mug and price in the second part of the description.

                                                                                                                1. 2

                                                                                                                  Author here: If you read carefully the article you’ll see that I didn’t write: “a mug is a description and a price” but “a mug has a description and a price. It makes a big difference.

                                                                                                                  An object has a state. An object is not a state.

                                                                                                                1. 21

                                                                                                                  I’m a bit confused by the conclusion of this article.

                                                                                                                  At this point I think it is actually pretty stable. I consider the project a big success.

                                                                                                                  A huge part of this has been due to Haskell and its excellent ecosystem

                                                                                                                  But throughout the article, you see the failings of the haskell ecosystem: no IDE, poor refactoring tools, ridiculously slow compile times, a poor standard library, and unmaintained libraries. What exactly is the “excellent ecosystem” here? It sounds like this project succeeded despite haskell being the main language.

                                                                                                                  Personally I think it’s fine to use whatever you want, but this article reeks of a rose-colored reimagining of what haskell is because the author enjoys coding in haskell. Haskell isn’t typically used in production systems for projects like this and this seems to be another anecdote as to why it isn’t production-ready rather than a success story.

                                                                                                                  1. 19

                                                                                                                    Haskell is in kind of an odd place, there are some things that the author calls out that are problems (and thankfully the community has been taking great strides recently in starting to address a lot of them) but for a lot of people the benefits outweigh the problems. The challenge of course is that a lot of the things that are great about haskell don’t exist in other languages at all, and that makes it particularly hard to tell a compelling story about it. “Everyone” knows you need an IDE to be successful in writing code, but nobody misses tooling like hoogle, because just doesn’t exist outside of the haskell ecosystem. “Everyone” knows that build times are important, but haskell is one of the few communities that have really embraced nix (although that’s not a haskell specific tool) to widely use it for getting distributed caching, builds, and reproducibility.

                                                                                                                    I don’t claim haskell is the best language, or that it’s right for everyone, but there are good reasons to use it in production. The particular strengths and weakness of haskell mean you’re paying quite different costs than you would with some other more mainstream languages, but for a lot of people that really is worth it, and I don’t think it’s just for the rose colored glasses.

                                                                                                                    1. 10

                                                                                                                      It was hard to include everything I wanted to say as it is already quite long. I’m trying to offer an evaluation of using Haskell in prod. As such I am making sure to point out all the pain points, but I also point out some really strong areas.

                                                                                                                      I guess what I don’t spell out is that Haskell the language (and many of the libraries) are what I would describe as ‘best in class’. You write less code, that is simpler (once you learn Haskell) and has much less scope for bugs to creep in, compared to popular programming languages. Despite the warts in Haskell + ecosystem, it is stellar. And my conclusion is that it works well in prod too.

                                                                                                                      1. 5

                                                                                                                        Thanks a lot for this article! That’s exactly why I found it so interesting, a really practical and nonobvious list of pros & cons. It’s not often that I see articles like this. If you disregard the flamewar, the article got quite many upvotes, which means a lot of people found it interesting too :)

                                                                                                                        1. 2

                                                                                                                          Thanks!

                                                                                                                    1. 3

                                                                                                                      The one thing I am confused about is: what is the legal form of this? haskell.org is 501(c)3, and this one is? I searched through the whole page and did not find it.

                                                                                                                      I general, I like the page and the ethos statement. It gives a good idea on what to expect.

                                                                                                                      I liked

                                                                                                                      For many of us Haskell is more a way of life than a programming language.

                                                                                                                      which puts a human element in.

                                                                                                                      1. 4

                                                                                                                        Haskell.org board member here, and I was involved with helping set up HF. Hopefully I can clarify this.

                                                                                                                        The Haskell Foundation has been set up as a 501 (c) 3 charitable organization in New York. The paperwork there is currently backed up due to covid, and so in the mean time haskell.org are accepting donations and holding funds for the Haskell Foundation, which we will turn over once the HF paperwork has finished up.

                                                                                                                        We’ll be continuing to get some of this stuff up on that website over the next couple of weeks, unfortunately we had some bad timing and some things came down to the wire and I think there was some information, like this, that we just missed getting up on the first pass.

                                                                                                                        1. 2

                                                                                                                          Thanks! That clears things up. To be clear - I wasn’t in any way questioning things. I am just currently interested in researching foundations and was confused to see exactly this information missing. Thanks for explaining the money flow, that was indeed interesting!

                                                                                                                          Setting up a foundation in those times is really not fun. All power to you and thanks for your work!

                                                                                                                        2. 1

                                                                                                                          Their twitter says “a non-profit building the future of Haskell” but that’s as much as I could find.

                                                                                                                          https://twitter.com/haskellfound

                                                                                                                          1. 1

                                                                                                                            The instances of “Haskell Foundation” I found on Charity Navigator and IRS Determination Letter search are almost assuredly not this one. They’re much older organizations. I speculate that they’re really just getting started and haven’t yet done their IRS paperwork or have but it’s not yet hit the public feeds that CN and Guidestar and friends use. Once I see an EIN for Haskell Foundation on their site, then it’ll be safer to donate, presuming they’re incorporating in the US.

                                                                                                                          1. 2

                                                                                                                            I think there’s a little bit of value in discussing error handling and optional value handling techniques that the article brings up- they can be quite useful in a lot of circumstances, but I think it does more harm than good to claim that this is a general purpose explanation of monads because it both ignores some really valuable properties of monads (like the fact that they are mappable, or that they have a way to lift a value), and ignores some use cases that I think would help people see how general monads actually are (lists, probability distributions, continuations, state).

                                                                                                                            1. 9

                                                                                                                              It really bugs me that things like this continue to perpetuate the idea that web is the only kind of development. “Frontend” and “Backend” are not the only two categories of development, and continuing to perpetuate this idea is causing far too many people to focus on moving everything to the web, because they can’t see outside of the narrow sliver of the breadth of technology. I wish we could acknowledge that the web isn’t, and shouldn’t be, the end-all be-all of software development or technology delivery.

                                                                                                                              1. 2

                                                                                                                                I think that is because the majority of user-facing applications are web based. It seems to me like web-based software is just the most reliable, simple, and cross-platform way to deliver content. Because there are so many use cases, it has accumulated a lot of depth and knowledge, making guides like these necessary. It could also be a cultural thing - low level stuff, like kernels, operating systems, etc., have a culture formed around them of “just read the manual”.

                                                                                                                                I’m curious what other technology / kind of development you think could use a roadmap. My personal interest is in PLT+formal methods, but I guess I’m early enough in my learning that I can’t imagine a roadmap as large as the ones presented in the original submission. Maybe embedded development? Database systems?

                                                                                                                                1. 3

                                                                                                                                  My interest is in PLT as well, and I think that there’s actually quite a huge opportunity for a roadmap there. I know that as an autodidact in PLT I’d have loved a structured roadmap for how to understand type systems, category theory, compiler design, etc. I’ve seen roadmaps like this for data science; I bet you could also create them for mobile development, security, devops, game development, or just about any pursuit. Almost all fields these days are built on a large amount of prior work and any time there’s a lot of prior work I think creating a pedagogical ordering for people to pick up concepts can be helpful.

                                                                                                                                  1. 2

                                                                                                                                    Hi Rebecca, I haven’t thought about a roadmap, but I’m planning to build up a structured sitemap for boxbase blogposts and eventually extend to articles that cover things you mentioned, type systems, category theory, compiler design. I think it’ll come with the next upgrade on the site. Sometime next month when I think I’m ready to do that.

                                                                                                                                    If there’s some related subject I could cover that you immediately need, I’d appreciate to know.

                                                                                                                                    1. 2

                                                                                                                                      That sounds like a great roadmap! It would be really great if there was something that covered the path to modern type systems from like the typed lambda calculus and denotional semantics through systemF and then concluded with something like homotopy type theory. I know that I’ve struggled to get through all the literature on cubical type theory because there’s a bit of math and some more recent work that I’ve not been quite sure what the right order to pick it up is.

                                                                                                                              1. 12

                                                                                                                                I’ve been doing this for a few months and it’s a huge quality of life improvement. I don’t have a very heavy emacs config, but it still takes a couple of seconds to launch emacs- enough to make me reluctant to open it for quick edits. Emacs client makes that almost instantaneous. I also tend to open a lot of frames for different work contexts, and having a persistent client really makes it nicer to manage- especially lately as I’ve switched up my window manager and keyboard and have had a bad string of accidentally killing windows in my DE lately.

                                                                                                                                1. 33

                                                                                                                                  Someone on Reddit put it best:

                                                                                                                                  Java devs never fail to be parodies of themselves.

                                                                                                                                  This is so spot on.

                                                                                                                                  Java is actually a good language, it’s the ecosystem that kills it for me. By “the ecosystem”, I don’t just mean the tooling (e.g. Java build tools are universally awful AFAICT), but the developers themselves. So much Java I read is just “magic”. Magic annotations, magic dependency injection, interfaces over classes that there is only one of etc. etc.

                                                                                                                                  The author points out very good failures in the way its been architected, but the code in the OP isn’t all that strange looking to me as Java, and that’s a pretty damning statement.

                                                                                                                                  I wish Java had a better audience around it. The Kotlin ecosystem seems better, but I’ve never used it.

                                                                                                                                  1. 26

                                                                                                                                    (edit: my first pass at this came off a little overly negative in a way that I think betrays the seriousness of my point)

                                                                                                                                    I’m not sure Java actually is a good language. My first introduction to Java was as the primarily language that was used in college, and even as a pretty green programmer them (I’d started writing C and C++ a few years earlier in high school, but I was definitely not a good programmer), I found it awfully questionable. In the intervening 15 years I’ve managed to avoid it, until quite recently. It’s been really eye-opening to see how the language has evolved in that time, but not really in a good way. Perhaps it’s because I’ve largely written OOP off as a bad idea that shouldn’t have ever taken off like it did, and is certainly outstaying it’s welcome, but I find that Java, and even the JVM itself, to be a masters class in solving the wrong problem in the most complex possible way. The complexity in java seems to be like glitter, you can’t touch anything with getting covered in it, and once it’s on you you’ll never get it off. Even working with people that I generally hold in high regard as developers, I see that the ecosystem has forced them into patterns and architecture that I think is questionable- except it’s not because to do anything better would be to try to work against every single design decision in the language and ecosystem. There’s simply no reasonable way to write good Java, the best you can reasonably hope for is to write as little java as possible, and hope the absurd complexity giltter doesn’t spread to all of your connected services by way of the blind “the whole world is Java” assumptions that the JVM ecosystem wants to make on your behalf.

                                                                                                                                    I say Java here, but realistically I think that all JVM languages end up falling into the same gravitational well. I’ve been using Kotlin lately, and from what I’ve seen of Scala and Clojure they are all infected by the same inescabable fractally wrong view of the world that is imposed by the JVM, by way of the JVM itself being born from the primordeal ooze of bad decisions and oop-kool-aid that led to Java in the first place. Kotlin in particular suffers from being not only unable to escape the Java ecosystem, but also from generally being a poorly designed language. Everything it adds to java, it adds in such a superficial and impotent way that a slight breeze knocks over the facade and you realize you’re stuck back in the Kingdom of the Nouns all over again.

                                                                                                                                    1. 7

                                                                                                                                      I tend to agree about the java part. The constructs at your disposal requires you to write very very verbose code, even for simple things. But I disagree about the JVM bit. I find it a pretty good runtime system. Although it tend to eat its fair share of RAM, the GCs and the JIT are first class. Overall, you get pretty decent perf without too much thought. Also, Having written a lot of clojure, it’s vastly different from java, couldn’t be further from the Kingdom of the Nouns.

                                                                                                                                      1. 15

                                                                                                                                        The JVM feels to me like it was written for a world that just didn’t really ever happen. It promised cross platform compatibility, that never really materialized since there are only two real meaningful places where the jvm is heavily used these days (x86 linux servers and arm Linux phones). Even where the jvm itself is running on multiple platforms, it’s not running the same workloads across it. We would have been every bit as well off with a toolset that made native cross compilation feasible (go and rust), and probably would have been no worse off even with the old C and C++ cross compilation story. Love it or hate it, JavaScript is what actually fulfilled the promises that java made and never was able to keep.

                                                                                                                                        Other promises that JVM made either never made sense- language interoperability always existed before java, and exists outside of it now. All the JVM did was fracture the environment by making it nearly impossible to produce native code- it’s a vampire if you look at it in terms of interoperability, unless you want to use gcc to compile your java code. The isolation and runtime management is, consistently, 90% of the work involved in deploying any java application I’ve used, and at the end of the day everyone does that work twice now because most workloads are getting deployed in cloud native containers anyway- so the JVM is superfluous there. GC is a pain in the JVM and has been done as well elsewhere without requiring the rest of the baggage of its runtime and jitter.

                                                                                                                                        Looking at performance, I’m dubious that it has much going for it. It’s still not a contender in the same space as C or C++, and in many cases the pain of native interop make it slower than even python because python can rely on native code for a lot of heavy lifting. I’ve even seen fairly well optimized JVM code fail to keep up with reasonably (perf) naive Haskell.

                                                                                                                                        Even with instrumentation, the supposed killer feature of the jvm, I have yet to see anything I can’t get out of a native application with native tooling and instrumentation, and the case is getting weaker by the day as more and more application telemetry moves up and down the stack away from the application itself and into either tracing layers in front of services, or tooling built around things like ebpf that live very low down in the system and allow you to instrument everything.

                                                                                                                                        The JVM is at best a middle-of-the road performance language with a largely superfluous ecosystem. It might have been a good idea when it was created, and I have no doubt a lot of smart engineering went into its implementation, but it’s time we give it up and realize it’s a sunken cost that we need to leave to the history books as a quirky artifact of the peculiar compute and business environment of the early 90s.

                                                                                                                                        1. 3

                                                                                                                                          Clojure’s okay (and still way better than Java, IMO) but suffers from pretty poor error handling compared to other Lisp environments.

                                                                                                                                        2. 4

                                                                                                                                          I really don’t like how Java makes optimization of its runtime overcomplicated, then has the gall to make you deal with the complexity. There is no reason to be manually tuning GC and heap sizes when every other runtime, including CLR implementations, can deal with this efficiently and automatically. They might be complex, unlike the JVM, they’re not complex and making you deal with that complexity.

                                                                                                                                          1. 3

                                                                                                                                            Just curious what you dislike about Kotlin’s design? It seems like you make two points: that Kotlin can’t escape Java and, separately, that it’s poorly designed. I agree with the former, but in light of the former, I find Kotlin to be pretty well-designed. They fixed Java’s horrible nullness issues and even kludged in free functions to the JVM, which is neat. Data classes are a band-aid, but still help for the 80% of cases where they can apply. Same with sealed classes (I’d much prefer pattern matching akin to Rust, Swift, OCaml).

                                                                                                                                            1. 13

                                                                                                                                              My biggest issue is that everything feels like a kluge. Null tracking at the type level is fine, but they didn’t really go far enough with the syntax to make it as useful as it could have been- rust does better here by allowing you to lift values from an error context inside of a function. The language tries to push you toward immutability with val and var, but it’s superficial because you’re getting immutable references to largely mutable data structures without even getting a convenient deep copy. Extension methods are a fine way of adding capabilities to an object, but you can’t use them to fulfill an interface ala go, or outright extend a class with an interface implementation ala Haskell typeclasses, so you’re left with basically a pile of functions that swap an explicit argument for a this reference, and in the process you are conceptually adding a lot of complexity to the interface of an object with no good associated abstraction mechanism to be explicit about it. Even the nature of the language as a cross platform language that can target jvm, llvm, and web asm seems fundamentally flawed because in practice the language itself seems to lack enough of a stand alone ecosystem to ever be viable when it’s not being backed up by the jvm, and even if you did have a native or web ecosystem the design choices they made seem to be, as far as I can tell, about the worst approach I’ve ever seen to cross platform interoperability.

                                                                                                                                              Ultimately the features they’ve added all follow this pattern of having pulled a good idea from elsewhere but having an implementation that seems to not fulfill the deeper reason for the feature. The only underlying principle seems to be “make java suck less”. That is, of course, a bar buried so low in the ground it’s in danger of being melted by the earths core, and I would say they did cross over that bar- kotlin does suck less than Java, but what’s astonishing to me is how for such a low bar they still seem to have managed to cross over it just barely.

                                                                                                                                              1. 4

                                                                                                                                                I share every single one of those sentiments, but I’ve excused many of them specifically because of the limitations of being a JVM language. (interfaces at class definition, no const, no clones)

                                                                                                                                                I’ve almost taken it upon myself to periodically go and correct people in the Kotlin subreddit that val does not make things immutable, and that Kotlin has not cured the need for defensive copies in getters.

                                                                                                                                                I think the idea of making Kotlin cross-platform is totally stupid for those same reasons you point out. All of that is a limitation of wanting to be on the JVM and/or close to Java semantics. Why they hell would you want to export that to non-JVM platforms?

                                                                                                                                                Thanks for the response.

                                                                                                                                          2. 12

                                                                                                                                            I have a completely opposite opinion. Java is not the best language out there, I prefer Scala and Kotlin, but the selling point for me is the ecosystem: great tooling (tools simply work in lots of cases), great platform (lots of platforms are covered, really awesome backward compatibility, stability), great API (it might be far fetched, but I have a feeling that Java’s stdlib is one of the most feature-packed runtimes out there, if not the most?). The “magic” is the same problem as everywhere else; it’s magic until you know the details. Also just because there’s a dependency injection trend in the backend development world, it doesn’t mean that you should use DI in different projects. Interfaces of classes are a Java thing; it wouldn’t exist if the language was more sophisticated.

                                                                                                                                            Maybe I’m comparing Java’s ecosystem to C++ – because with C/C++, the tooling is in appalling state, the standard library is awful and I’m not sure what it tries to achieve at times. So I guess I have a very low standards to compare to :P

                                                                                                                                            1. 3

                                                                                                                                              Java has an incredibly rich ecosystem, that’s true. What annoys me though, is that every single tool in the Java ecosystem is written in Java, meaning you have a ton of CLI programs which take a couple of seconds just to heat up the JVM. Once the JVM is hot and ready to actually do work, the task is over and all that JIT work is lost.

                                                                                                                                              At least C/C++ tooling is fast :p

                                                                                                                                              1. 2

                                                                                                                                                That’s true, JVM startup time is a pain point. But there are several walkarounds for that:

                                                                                                                                                • some tools use build server approach (gradle), so that startup time is less slow ;)
                                                                                                                                                • some tools like sbt (scala build tool) use build server + shell approach, and it’s possible to use a thin client to invoke a command on this build server (e.g. ‘sbt-client’ written in rust). This makes Scala compilation take less time than compiling a C++ application.
                                                                                                                                                • GraalVM native-image is pushed right now, which allows to compile JVM (java, kotlin, scala) application to native code without the use of JRE. This allows writing tools that have non-noticeable startup time, just like tools written in e.g. Go. I was testing some of my small tools with it and it was able to compile a small Clojure app to native code. This tool had same startup speed than a C++ application. Unfortunately, GraalVM can’t compile every app yet, but they’re working on it ;)

                                                                                                                                                Also C/C++ tooling is fast, but C++ compilation is nowhere near being fast. Changing one header file often means recompilation of the first half of the project. Bad build system (e.g. in manually written Makefiles) that doesn’t track dependencies properly sometimes produces invalid binaries that fail at runtime, because some of the compilation units weren’t recompiled when they should be. It can be a real mess.

                                                                                                                                            2. 10

                                                                                                                                              I wish Java had a better audience around it.

                                                                                                                                              That doesn’t seem terribly likely to happen.

                                                                                                                                              1. Java was never aimed at programmers who value power, succinctness, and simplicity - or programmers who want to explore paradigms other than OO (although newer versions of the lanaguage seem to be somewhat relaxing the Kingdom of Nouns[1] restrictions). It was intended to improve the lives of C++ programmers and their ilk[2].

                                                                                                                                              2. Java is frequently used in large, corporate, environments where programmers are considered (and treated as) fungible. “The new COBOL”, as it were[3].

                                                                                                                                              3. The JVM itself allows programmers not falling into (1) and (2) to abandon the Java language itself - Clojure, Scala, Kotlin, and Armed Bear Common Lisp spring (heh) to mind. Most of the best JVM programmers I know aren’t actually using Java. Most of the ‘Java shops’ I’ve worked with in the past decade are now, really, ‘JVM shops’.

                                                                                                                                              My observation is that most - to be clear, not all - people who continue using Java in 2020 are forced to do so by legacy codebases, and / or companies that won’t let them adopt new languages, even JVM languages. I honestly believe this is the proximate cause of the audience problem you describe. (Not the root cause, mind you).

                                                                                                                                              Edited: I’m amused by the fact that the first two, nearly concurrent, replies both reference Yegge’s nouns blog post :)

                                                                                                                                              [1] http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html

                                                                                                                                              [2] “We were after the C++ programmers. We managed to drag a lot of them about halfway to Lisp.” - Gosling. http://www.paulgraham.com/icad.html

                                                                                                                                              [3] https://www.infoworld.com/article/3438158/is-java-the-next-cobol.html

                                                                                                                                              1. 5

                                                                                                                                                Java is a horrible language. No mortal can mentally hold on to a class hierarchy where inheritance is more than a few levels deep. Furthermore it is a bad way to add “another layer of abstraction” , because it just paints you more and more into a corner.

                                                                                                                                                (Clojure is a great language, you can add layers of abstraction to solve problems, without just digging yourself deeper.)

                                                                                                                                                1. 3

                                                                                                                                                  But one can write Java programs without abusing inheritance, and even pretty much without inheritance.

                                                                                                                                                  1. 1

                                                                                                                                                    Yes. I agree that Java is a horrible language, but class inheritance doesn’t even make the list of things I find poor about it.

                                                                                                                                                2. 3

                                                                                                                                                  I don’t agree that Java is a good language at all, but I wanted to hard-agree at the distaste for magic annotations and DI frameworks.

                                                                                                                                                  1. 1

                                                                                                                                                    Java is actually a good language, it’s the ecosystem that kills it for me. By “the ecosystem”, I don’t just mean the tooling (e.g. Java build tools are universally awful AFAICT), but the developers themselves. So much Java I read is just “magic”. Magic annotations, magic dependency injection, interfaces over classes that there is only one of etc. etc.

                                                                                                                                                    I don’t agree with this - in my experience, “magic” is “code that integrates my application-specific functionality with a massively feature-rich general purpose framework”. It’s magic in the sense that you need to understand the enclosing framework to understand why those annotations are there and their semantics, but they do real work. Work I’d have to do myself if I didn’t use them.

                                                                                                                                                    You don’t see this much in other languages, but it’s because “massively feature-rich general purpose frameworks” aren’t common outside of Java. The ones that do exist seem to have punted on important architectural decisions - you don’t need a dependency injection framework if your data layer objects are just global values (I’m looking at you, Django).

                                                                                                                                                    I’ve definitely felt this urge before - why do I need all this spring crap? Then I end up re-implementing half of that magic myself and not as well.

                                                                                                                                                    1. 1

                                                                                                                                                      What language has tooling that you like? Curious what you are comparing the Java build tools with

                                                                                                                                                      1. 2

                                                                                                                                                        Rust and Go (at least since Go modules) I find both intuitive and fast. I am still not sure how to properly build a Java application without an IDE.

                                                                                                                                                        1. 1

                                                                                                                                                          $ ./gradlew build

                                                                                                                                                    1. 2

                                                                                                                                                      Haskell memes are eating themselves, it would seem.

                                                                                                                                                      There are as many opinions on what constitutes Simple Haskell as there are people who have them. We don’t want to endorse any one view, but promote the the general idea.

                                                                                                                                                      Following that are three bland paragraphs about the corporate benefits of accessibility, maturity, and (not) leaking complexity.

                                                                                                                                                      Does anybody really want for their code to be used by corporate hackers who aren’t going to contribute any money or time back to their library? Does anybody really want to bend over backwards and ensure compatibility with a corporation-approved list of ancient standards and extensions? Not really, I bet. But everybody loves “simple”.

                                                                                                                                                      1. 6

                                                                                                                                                        For my open source projects I’m not too concerned with it, but as someone who’s dealt with one bait and switch after another in the corporate world taking Haskell jobs only to make it 6 or 12 months in and management starts getting irrationally twitchy about having the business successfully running on code that’s written in Haskell, I hope that empty gestures like this might be the start of the spin we need to stop Haskell from getting constantly evicted from the corporate world.