7 ms·
The main benefit of classes is as a way to define custom types. Otherwise you are stuck with the types built into the language, which are not designed to help e
by INTPnerd 7y ago
The main benefit of classes is as a way to define custom types. Otherwise you are stuck with the types built into the language, which are not designed to help ensure correct program state, logic, and behavior. When you use classes in this way, I call it type oriented programming (TOP). Why would I compare classes to types? Just pass in the initial value at construction time, define the "operations" on that type by creating methods for your class, and define which other types those operations work with by setting the types of parameters those methods work with. Make the class immutable, always returning a new instance with the new updated value passed into the constructor. Why did I mention these types helping the program be more correct? This should be used as the primary form of contracts. But these contracts are very simple to use, you just specify the relevant type in the parameters and return values, and you are done defining the contract for any given method. For example, let's say a method should only work with integers larger than zero, instead of either accepting an Int or an UnsignedInt, which would allow 0, you could define your own class called PositiveInt. It would be designed to throw an exception if you pass anything smaller than 0 into the constructor. Then instead of writing code inside the method that makes sure the user of the method is following the contract, you just specify PositiveInt as the parameter type. If the contract is violated, the exception will be thrown as early as possible, before the method is even called, helping programmers catch the original source of the problem. This also makes your code more readable, because you can see exactly what each method accepts and returns just by looking at the method signature. When you start thinking this way, you will notice many core types are missing from the language, that should have been there from the beginning. Fortunately you now know how to build them yourself.
- xamuel 7y agoI use C structs in the same way. I think it's fine to use classes for this purpose. The problem with classes is that certain people go crazy with inheritance and polymorphism, things which sound smart on paper but almost always lead to horrific unmaintainable code in practice.
- INTPnerd 7y agoThis is a good point. Right now Kotlin is my favorite language. By default classes and methods are closed for inheritance, and therefore for polymorphism. I like the way this is the default, but you can override it when/if necessary. It also has data classes, which in my opinion is what most classes should be. I think when a class is not be a good fit for data classes, this is a code smell.
- paulmd 7y agoYeah, basically the lesson of the last 20 years of Java and C# and so on is that inheritance sucks and what you mostly are after is composition.
- mumblemumble 7y agoNot only were custom types invented decades before classes, but the languages with the strongest emphasis on type safety tend to either lack them or consider them to be unidiomatic.
- INTPnerd 7y agoWhich languages would you say are really good for defining your own types? Would they be a good fit for the example I provided where a function needs to accept integers larger than zero? Do they also allow you to define your own operations on those types? That is the part where classes seem to be a good fit, methods are basically operations supported by a type.
- FreeFull 7y agoYou can go with more of a Haskell/Rust style approach, where you declare a type, and then define operations using either plain functions, or using something kinda similar an interface (typeclasses in Haskell, traits in Rust).
- mumblemumble 7y agoFor the integers larger than zero example, I'd say the earliest archetype for a good design (that I know of) is Ada, which lets you declare a type with a limited numeric range like so: type MyType is range min .. max There's already a built-in for positive integers, which is defined as subtype Positive is Integer range 1 .. Integer'Last; Note the subtype there. Ada recognizes that a positive integer is a type of integer, but not the other way around. And it enforces that in the type checking: You can pass any Positive into a function that accepts Integer, but you can't just pass an Integer into a function that accepts Positive. This happens even though they're not classes and this isn't OOP. Ada does have object-oriented constructs, but they are a later addition to the language. I have never used Ada professionally, but my understanding (based on book learning) is that it tends to be used conservatively. It's similar in OCaml. Despite the O standing for "object-oriented", creating classes isn't necessarily considered idiomatic. The other tools in the chest tend to be conceptually simpler, and therefore to be preferred when they will get the job done. "define your own operations" is a requirement I'm having a hard time making sense of. To me, that is just another way of saying, "define functions", which is a feature of every language I've used except for one really ancient dialect of BASIC.
- chmod775 7y ago> The main benefit of classes is as a way to define custom types. Otherwise you are stuck with the types built into the language I'm going to stop you right there, because plenty of languages (especially functional ones) have ways of declaring custom complex types without using classes.
- INTPnerd 7y agoWhich languages would you say are really good for defining your own types? Would they be a good fit for the example I provided where a function needs to accept integers larger than zero? Do they also allow you to define your own operations on those types? That is the part where classes seem to be a good fit, methods are basically operations supported by a type.
- damnyou 7y agoSum types and typeclasses in Haskell (or enums and traits in Rust) are a much better fit than classes and the mess that is operator overloading.
- INTPnerd 7y agoThanks! These definitely sound like what I should explore next in my journey.
- PhaseLockk 7y agoA typical way to handle this in a functional language would be to create a datatype with a name like "PositiveInt", which just contains an Int inside it. However, in a language like OCaml, you can make it so that users of this type cannot directly create it, and instead must use a function like "makePosInt", which would check that its argument is positive, then give you back a value of type PositiveInt containing your data. I'm not too experienced with this though, so this is pretty much the extent of my knowledge on this topic.
- nicoburns 7y agoRust is excellent for this. You can define: struct PositiveInteger(u32); Then give it a constructor (which in rust is just a regular static function) that checks the non-zero variant. You can define method on this type, and make it implement traits (which are kind of like interfaces). The best bit: there is zero runtime cost to this, the memory-representation of this type is identical to that of the underlying u32. Rust-style enums which can contain data, and also have method implemented on them are even better. Doesn't mean classes (structs in Rust) aren't useful, but once you use a language that allows you to define other kinds of custom types, they seem very restrictive when they're the only available tool.
- Koshkin 7y agoIn my experience, custom types often turn out either being so restrictive as to be less than useful or being leaky abstractions. (This might be an example of just where a healthy engineering compromise is unavoidable.)
- tpaschalis 7y agoAn excellent article on type-driven design and development was posted on HN [1][2], as darkkindness put it "Encode invariants in your data, don't enforce invariants on your data" [1] https://news.ycombinator.com/item?id=21476261 https://news.ycombinator.com/item?id=21476261 [2] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
- ken 7y ago> For example, let's say a method should only work with integers larger than zero I dislike this example. The numeric systems of every programming language I've ever used has been (more or less) terrible, precisely because there are extremely common and simple arithmetic types, just like this, which it's terrible at representing. Half of the "modern" languages I've seen just provide the thinnest possible wrapper around the hardware registers ("Int32"!). (What if I need to accept an integer in the range 5 < x < 10 instead? Am I supposed to define a new class for every range?) Instead of saying we need a system of user-definable classes so every user can fix the numerics on their own, for each program they write, I'd say we should fix our numeric systems to support these common cases, and then re-evaluate whether we really need classes. Are there non-numeric analogues to this type of value restriction? Maybe. It doesn't seem like a very common one, but it is an interesting one. Perhaps what we really want is "define type B = type A + restriction { invariant X }". I can't think of any examples offhand in my own work, but that could be because I haven't had that language feature so I wasn't looking for places to apply it.
- CuriousSkeptic 7y agoI guess it comes down to an unresolved duality. One being the idea of types as describing shape of data. In the best of cases perhaps some semantics tied to the data (how the bits are to be interpreted) Than there is the the other view, the Curry-Howard one. Where types describes proofs and invariants of the program it self, and how interesting properties of program compositions can be ensured. It seems much time is wasted when people holding one perspective debates with people holding the other. Perhaps we should have separate words for these concepts.
- strbean 7y ago> When you use classes in this way, I call it type oriented programming (TOP). Watch out, you might make topmind mad! https://wiki.c2.com/?TopMind https://wiki.c2.com/?TopMind
- voldacar 7y agohave you heard of structs?