4 ms·
I don't know if I hate Haskell records, but I certainly agree they feel like an ugly hack. They're essentially just syntactic sugar for a standard product type
by dwohnitmok 7y ago
I don't know if I hate Haskell records, but I certainly agree they feel like an ugly hack.
They're essentially just syntactic sugar for a standard product type with auto-generated functions for access and update. Unfortunately those functions come with foot guns. Here's two:
1. As you pointed out, clashing field names (since fields don't truly exist but are rather functions)
2. Partiality of the generated functions if you have multiple constructors for a type, at least one of which is a record.
Lenses are great, but would benefit from first-class integration as the "blessed" way of interacting with records (this hopefully would also reduce their learning curve).
One of my top wishes for Haskell is proper row types a la Purescript. Purescript records and polymorphic variants are amazing. I would absolutely love if this https://github.com/natefaubion/purescript-checked-exceptions https://github.com/natefaubion/purescript-checked-exceptions was possible in Haskell.
- tombert 7y agoI would definitely support making Lenses an officially-integrated part of the language, since for the most part they do somewhat address my complaints. If they could make the syntax for it feel a little less hackey I think I wouldn't whine at all about records. I'm not super familiar with Purescript, so I can't comment on the row types; are they more-pleasant than Haskell's?
- dwohnitmok 7y agoYes! Syntactically Purescript essentially automatically gives you lightweight lenses for records. E.g. myValue { field0 { subField0 { subSubField0 = 5 }}} Semantically... oh boy, row types (i.e. extensible product types) and polymorphic variants (i.e. extensible sum types) are fantastic! I'd go so far as to say as long as you give me `newtype`, I would be in favor of row types and polymorphic variants completely replacing `data` declarations. -- forall x here means that our record can have other fields resetName :: forall x. { name :: String | x } -> { name :: String | x } resetName itemWithName = itemWithName { name = "Default" } -- Notice how Person is just a type synonym! -- And there's no data constructor -- { name :: String, id :: Int } is a type, not just a constructor type Person = { name :: String, id :: Int } type Pet = { name :: String, owner :: Person } bob = { name : "Bob", id : 0 } doggy = { name : "Doggy", owner : bob } -- Look they both work! bobReset = resetName bob doggyReset = resetName doggy -- And no clashes, because records are a first class type declaration bob.name == "Bob" doggyReset.name == "Default" Purescript unfortunately doesn't have built-in polymorphic variants (you can derive them from row types with a bit of type-level hackery), so a bit of this is pseudo-Purescript (mainly the case declaration, functionally you can get the same thing, it just looks slightly different). newtype TextTooShort = ... newtype TextNotValidASCII = ... findFirst5CharWord :: forall e. String -> Either (TextTooShort + e) String asciiNumberRepresentation :: forall e. String -> Either (TextNotValidASCII + e) (Array Int) processString :: forall e. String -> Either (TextTooShort + TextNotValidASCII + e) (Array Int) -- No lifting or wrapping of intermediate errors required! -- All the types still unify! processString = findFirst5CharWord `andThen` asciiNumberRepresentation -- But you still get exhaustivity checking! displayString :: String -> String displayString input = case (processString input) of Left TextTooShort -> "You didn't give me enough text!" Left TextNotValidAscii -> "Whoops no ASCII rep exists!" Right arrayOfInts -> show arrayOfInts