Hacker News new | ask | show | jobs
by INTPnerd 2294 days ago
Which 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.
6 comments

> Which 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?

I'm not an Ada expert, but it has excellent support for range-restricted integer types.[0]

Ada's 'discriminated types' are also fun. They let you create members which only exist when they're applicable. [1]

> Do they also allow you to define your own operations on those types

Looks like Ada supports operator overloading, yes. [2]

[0] https://www.adaic.org/resources/add_content/standards/05rm/h...

[1] https://www.adaic.org/resources/add_content/standards/05rat/...

[2] https://www.adaic.org/resources/add_content/standards/05aarm...

Ada is also great at separating the various aspects of OOP into separate language concepts, instead of going "everything is done with classes!!" as many popular languages do, leading to a lot of confusion in this thread.
A 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.

Rust 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.

That sounds like a class.
Yep. This whole discussion just seems like a C vs C++ styleguide slapfight.

Having the functions that operate on the struct attached directly to the struct declaration, vs having some functions that the first parameter is the struct on which the function operates, doesn't seem like a particularly meaningful distinction to me. OK, you like C-style programming in favor of C++-style programming, congrats. It's still a class either way.

The distinction you describe is not meaningful, but the key feature that separates classes from other forms of code organization/polymorphism, like typeclasses as in Haskell/Rust, is not that. It is inheritance.
I guess it's kind close to a C++ class. It's pretty different to a class in a language like Java, because all classes in Java are heap allocated and behind references.

Enums are are the better example of non-class types. For example, you can have:

    enum StringOrInt {
        String(String),
        Int(u32),
    }
And you can go ahead and implement methods on that type. Classes have "AND-state", not "OR-state". But a Type in general can have either kind of state.
That's equivalent to wrapping an enum in a class. Emulations of type hierarchies without OO often fail like this, having a A-or-B be literally the same type so losing out on type safety/forcing constant rechecking of the discriminant.
That can be done in Rust by having two different types implement the same interface
It's a product type[0], containing a single inner type of `u32`. Sometimes classes look like product types.

[0] https://en.wikipedia.org/wiki/Product_type

Data types are not classes.
Sum 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.
Thanks! These definitely sound like what I should explore next in my journey.
I believe you're looking for dependent types: https://en.wikipedia.org/wiki/Dependent_type
You actually don't need types or classes to do this. You could use design by contract, which is what I've done in Python and Perl, neither of which have very fancy type systems compared to something like Haskell.

With design by contract you can put in whatever fancy constraints you want on function parameters and return values, and those will be enforced.

As far as objects go, they're much more useful for me as just a means of passing state. Rather than using a bunch of global variables or having to pass in a ton of function arguments, I can just use an object which contains all the state I need.

Of course, having lots of state can usher in its own set of problems, and there's something to be said for trying to make your code as stateless as possible. But sometimes you need state, and maybe even a lot of it.