4 ms·
And Kotlin data classes: https://kotlinlang.org/docs/reference/data-classes.html https://kotlinlang.org/docs/reference/data-classes.html
by McDev 7y ago
And Kotlin data classes: https://kotlinlang.org/docs/reference/data-classes.html https://kotlinlang.org/docs/reference/data-classes.html
- q3k 7y ago... all of these are likely inspired by the extremely powerful namesake of Java Records: Haskell Records [1]. [1] - http://learnyouahaskell.com/making-our-own-types-and-typeclasses#record-syntax http://learnyouahaskell.com/making-our-own-types-and-typecla...
- taeric 7y agoI'm going on a limb and saying the namesake goes back to pascal. If not farther.
- kryptiskt 7y agoThe original COBOL had them, maybe it's not surprising, it's a pretty natural metaphor for a business language. Fortran, Lisp and Algol 60 didn't have them. So I guess we can thank the bean counters for the idea.
- tombert 7y agoGonna take an unpopular opinion here, but I completely hate Haskell records. I'm sure that there's some kind of purity to them, but the fact that a two records can't share field names ends up making them incredibly difficult to use for anything practical. Granted, using the makeFields function with Lenses allows you to kind of avoid the clashing, but that in itself feels like somewhat of a hack.
- teruakohatu 7y agoFor those of us who don't do haskell, can you give us an example of what does not work and then work around?
- dwohnitmok 7y agoFor an example of clashing: data Person = Person { id :: Int, name :: Text } data Account = Account { id :: Int, # Oh no! Name clash amount :: Int } This is because `id` isn't really a "field," among other thing it's a generated function so that id :: Amount -> Int id (Amount actualId _) = actualId This particular case is compounded by the fact that `id` is already the name of the identity function in Haskell's standard library (one reason why many projects that roll their own standard library rename `id` to `identity`). For the partiality: data AllowedItems = Person { id :: Int, name :: Text} | JustANormalInteger Int name (JustANormalInteger 5) # Blows up at runtime That is name has the type AllowedItems -> Text, even though JustANormalInteger 5 is also a member of AllowedItems The workaround for clashing is to prefix your fields with your type name (luckily Haskell still has module namespaces so you don't need a globally unique name, just one unique for your module). The workaround for partiality is to disallow a type with a record to be anything other than that record (e.g. disallow JustANormalInteger or require that Person {...} must first be an independent type before it can be used in AllowedItems). The talk of lenses is a way to generalize the notion of field accessors and talk about the "path" generated by a series of field accessors in a first class way. E.g. you might imagine that in a standard OO language myClass.field0.subField0.subSubField0 could have a stand-alone concept of `field0.subField0.subsubField0` as a path through `myClass` that you could then use either to get a value (get(field0.subField0.subSubField0, myClass)) or set a value (set(field0.subField0.subSubField0, myClass, newValue)). Moreover, if you can talk about the paths through a class in a first-class way, what's to say that this path must actually correspond to a true field in a class? It could just be anything "field-like." For example: lens = integer.lastDigit.leastSignificantBit set(345, lens, 0) // Yields 344 even though the individual digits of myInteger aren't actually generally truly fields in any OO language nor are the individual bits of a number. Because you have now removed the need for true "fields," lenses are one strategy to get around the hackiness of fields in Haskell and in fact in many ways represent an advance in expressiveness over "true" fields (although they suffer from not really being integrated into the language in the way faux-fields are currently in Haskell).
- dwohnitmok 7y agoWhoops... apparently I was reading bash while writing this comment. Comments in Haskell should be `--` not `#`.
- dwohnitmok 7y agoI 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
- jmtd 7y ago> but the fact that a two records can't share field names That is awkward sometimes yes. Record fields are functions, and you can't have two functions with the same name in the same namespace. You can, however, use namespaces to import two separate modules with conflicting names. It does mean you have to define the two different records in two different modules. If, you want different records to have common fields because they are conceptually related, you probably want to use type classes.
- dwohnitmok 7y ago> If, you want different records to have common fields because they are conceptually related, you probably want to use type classes. I'm not a fan of ad-hoc typeclasses. I know that various approaches in modern Haskell advocate for them, but I think that ad-hoc typeclasses are a smell for overuse of typeclasses when plain-old data types should suffice. I am a very big proponent of demanding that typeclasses have associated laws. Otherwise you can descend into the same hierarchy mess that plagues statically-typed class-based languages. And sometimes you just want to have different fields be named the same thing without an underlying profound relationship between the two.
- kccqzy 7y agoFunctional languages tend to have name resolution as a separate step before type checking. OO languages tend to intermingle name resolution and type checking. For example in OO languages `obj.func(1, 2)` will typically require the compiler to know the type of obj before knowing which func method is being referred to here. In Haskell however the equivalent expression is `func obj 1 2`. The compiler has already performed name resolution and knows exactly what func is. So that when doing type-checking, it is not only possible for typing information to propagate from object to method, but also from function to object. That is to say, it is possible that the compiler initially knows what type func has, but has no idea what type obj has; it will only do so after type inference using the type of func. This makes type inference more general and more elegant. I suspect it also makes the implementation of name resolution easier. Unfortunately as you have pointed out, it's practically more of a nuisance. Also note that this applies to more than just names of fields associated with a type; it even applies to type names associated with a type. For example if you have class G a where data N a :: * The name N is directly usable without even mentioning G. This would require, say in C++, you to write `G::N` to refer to an inner class: class G { public: class N; }; The reasoning is of course the same.
- deleted 7y ago[deleted]
- pron 7y agoKotlin's data classes are quite different from the other three (Scala's case classes, Java's records and Haskell records), and those three are inspired by ML's records, but the name "record" predates even ML.