1. 14

  2. 3

    The goal of ThisOrThat is to serve as a sum type with no tag, right? But it only supports two type arguments, and it would be cumbersome to nest. I think variants generalize the concept, I don’t think ThisOrThat offers any advantage over those?

    Gary Burgess has written a library for using variants with Argonaut.

    At work we have a large application with lots of sum types, but we have tags on all of them. Gary supports that too: taggedSum. The tag is worth the space it occupies, to me.

    1. 2

      That seems like it’s only relevant if you control the schema, which very often you don’t.

      1. 1

        You made a similar comment last time, and I never heard back from you. Here is my answer

        Maybe it would clear things up if you could post an example of some JSON that you think a schema can’t be written for?

        The most difficult object I can think of would be something like

        [null, 0, 0.0, [], {}]

        One way to make a schema for this, would be to make a variant that can accept nulls, numbers, lists and empty objects, and uses different constructors for all of these. It would then decode as [VariantNull, VariantInt 0, VariantScientific 0.0, VariantEmptyArray, VariantEmptyObject].

        So I think variants are exactly useful when you don’t control the schema.

        1. 1

          My comment was specifically about The tag is worth the space it occupies, to me, which isn’t really relevant if the schema I’m supporting doesn’t provide tags.

          What I’m talking about are schemas where the two different types, let’s say InterBankDeposit, CentralBank and CustomerDeposit occur in the same part of a larger schema, and can only be differentiated by the fields they have, not some tag. The usual Haskell way to deal with that would be something like

          data Deposit 
            = InterBank InterBankDeposit
            | CentralBank CentralBankDeposit
            | Customer CustomerDeposit
          parseJSON v = 
            InterBank <$> parseJSON v
            <|> CentralBank <$> parseJSON v
            <|> Customer <$> parseJSON v

          I’m not sure how your example with the array relates to this problem.

      2. 1

        TIL about variants! These purescript libraries definitely seem interesting and more ergonomic than nested ThisOrThats. Thank you for the feedback and learning opportunity!

      3. 2

        As a non-Haskell user, it’s amazing how poorly it handles JSON parsing. It’s the ugliest and most complicated JSON parsing of any language.

        Why do all of the libraries insist on de-serializing into user-defined Haskell objects? IME that’s almost never worth the hassle, and it’s 100x easier to just treat the incoming JSON as its own data structure and objects.

        1. 9

          You absolutely can just parse a json object into a map of keys to json values. That’s how a lot of parsers start out.

          The problem is that you need to write functions that work on some of the values inside of that json object. So what do you do? You can pass every function the entire json blob and have it try to extract the elements it needs, and possibly return an error if something was missing or invalid. That leads to a lot of duplicated effort though, since a lot of your functions will be looking at the same data. It’s also a pain to write a bunch of functions that might fail.

          An alternative would be to write one function that takes the json blob and tries to get all of the fields that it needs, and if one of them is missing then it fails. If everything exists, then it can call all of the functions you need to call. That would work great as long as you know ahead of time what fields you need and what functions you want to call, but it’s also a bit messy.

          It would be idea if you could just say “here’s a set of common fields that I want to work with, and I’ll check these fields once ahead of time. If they are all present and valid, then I’m good to go, otherwise I’ll report an error”. That’s exactly what these json libraries are doing. You write a type that says “here’s everything I expect to get and how I expect it to look”. Then you do the check once to make sure your json object has everything you need. Once you’ve done that, the rest of your code can happily assume that it’s getting valid data and you don’t have to worry about errors.

          1. 1

            I guess my point is that a runtime error for a missing key is just as useful (and probably more readable) than a runtime error from typing, so why bother with all of the typing and conversion boilerplate?

            1. 3

              There’s no reason for readability to be impacted either way, and in general the existence of types aren’t de facto boilerplate. Handling the errors up front simplifies all of the code that comes afterwards because you are assured that you have good inputs. Ad hoc error handling spread throughout your application causes a lot more boilerplate because you end up having to handle errors many more distinct places. It’s also a lot more error prone (you can forget to handle them) and makes testing harder (you have to test every function to make sure it can deal with errors)

              1. 1

                I’m not arguing against up-front verification or for dispersing error handling throughout the code, though.

                What I’m saying is that it’s a little clunky and awkward to use the type system for that purpose. IMO, of course.

                1. 2

                  If you don’t like types for that then you don’t really like types. That’s fine I guess, but I think it’s wrong to say the approach is clunky. It’s not, it’s just not aligned with the way you like to write code.

          2. 4

            Nothing insists on forcing you to work with your own data types, but not doing so is, frankly, insane. We could pass around the Value type everywhere, but anywhere I use it, I have to revalidate all my assumptions about the data: is foo.bar[3].baz a number? and is that number an Integer without any fractional part? Is there a string at home.page.next that is a valid URL?

            Most Haskell developers believe strongly in the parse, don’t validate mantra - if I parse my data and force the data into only valid shapes, then I know statically that I never need to re-check anything. If I have a Value, I know absolutely nothing other than I received a bytestring which parsed as valid JSON. if I have a CreatePayment, I know I have a field called createPaymentIdempotencyKey which is a valid UUID, I have a field called createPaymentAmount which contains a valid MoneyAmount, etc. I never need to check those assumptions again - I can’t have got a CreatePayment unless I had valid data.

            This also makes applications faster, I don’t have to do error handling all throughout the app - to look something up by its idempotency key, I already know I have a valid UUID, so I just serialise that in by DB interface, I don’t need to first extract it, hope it exists, deserialise it, ensure it’s the right format of string, then convert that; that was all handled by the parser.

            Dealing with JSON is fine for toy projects, but you need to get rid of it as soon as possible in anything doing real work. Applications become much simpler when you build a core application which only operates on valid data, and then wrap it in the code that protects it from the incorrect data, like an onion; bytes -> utf-8 -> json -> my data -> business logic data validation -> business logic.

            1. 1

              None of that makes the code not ugly, klunky, and awkward, though.

              And at the end of the day, if “foo.bar[3].baz” isn’t a number and the code expected it to be, it’s going to be discovered at runtime, regardless of the programming language.

              The difference is how much extra code has to be written to detect and handle that condition, and that’s where Haskell falls down compared to other languages, IMO.

              1. 5

                Here’s the valid Haskell version of that:

                key "foo" . key "bar" . ix 3 . key "baz" . _Number

                I write stuff like this all the time. Really not awkward!

                1. 3

                  On the contrary, absolutely no extra code is written, but in your preferred style, it’s scattered all throughout the codebase - I can never fully know if all my assumptions have been checked everywhere they need to be. But if I need to add a new constraint to my Haskell code, I know exactly where I need to do, to the parser.

                  I’m guessing you don’t write a whole lot of commercial or production software?

                  1. 2

                    On the contrary, absolutely no extra code is written, but in your preferred style, it’s scattered all throughout the codebase - I can never fully know if all my assumptions have been checked everywhere they need to be. But if I need to add a new constraint to my Haskell code, I know exactly where I need to do, to the parser.

                    I’m not sure you know what my “preferred style” is, just that I don’t like the burdensome, type heavy Haskell way of doing it.

                    I’m guessing you don’t write a whole lot of commercial or production software?

                    There’s no need to be condescending. I’ve written enough commercial and production software to realize Haskell adds extra up front development cost but doesn’t eliminate the most expensive bugs.

              2. 3