Live data from Hacker News

Don't Be Afraid of Types

lmika.org

61–70 of 233 posts

Re: Don't Be Afraid of Types

#61
post #2

my code became much easier to maintain once i stopped thinking of it as writing "algorithms" and "processes" and started thinking of it as a series of type conversions. structuring what lives where became easier, naming things became systematic and consistent, and writing unit tests became simple.

This resonates strongly! In Python, I often now find myself declaring Pydantic data structures, and then adding classmethods and regular methods on them to facilitate converting between them. It makes for great APIs (dot-chaining from one type to another), well-defined types (parse, don’t validate) and keeps the code associated with the type.

Exactly!

  class Profile:
    ...
  
  class User:
    @classmethod
    def from_profile(cls, profile: Profile) -> 'User':
      ...
   
    def to_profile(self) -> Profile:
      ...
...are about all the methods I need in my data records. Three simple rules though:

1. Keep isomorphisms to one class only: Don't put two def to_${OTHER_MODEL_NAME} in each class, instead (like you said) create one static mapping (@classmethod) and one instance mapping

2. Add a mapping to the one class that feels more generalized out of the two: A more generalized data model will probably be used a lot more throughout the application

3. The creation of instances should be pure: If a mapping has side effects and needs to await something then it isn't just a mapping - first resolve all necessary dependencies, then do the mapping

Re: Don't Be Afraid of Types

#62
post #50

Earlier quoted context omitted.

> Don't all types need names, regardless of what language you use? No. - In very dynamic languages (like javascript), most types arguably don't have names at all. For example, I can make a function to add 2d vectors together. Even though I can use 2d vectors in my program, there doesn't have to be a 2d vector type. (Eg, const vecAdd = (a, b) => ({x: a.x+b.x, y: a.y+b.y}) ). - Most modern languages have tuples. And tu…

The named integer range thing is interesting. I guess it depends on what you goal is. Could you use asserts? Could you wrap the integer in an object and embed the restriction logic there?

Doable in TypeScript[0], but I'd wager if that's really necessary.

[0]: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogdgVwL... from StackOverflow[1]

[1]: https://stackoverflow.com/questions/39494689/is-it-possible-...

Re: Don't Be Afraid of Types

#63
post #50
post #9

Earlier quoted context omitted.

> Every darn little thing in Java needs a name. Don't all types need names, regardless of what language you use? Look at typescript, and how it supports structural typing. They don't seem to have a problem with names. Why do you think Java has that problem when nominal type systems simplify the problem? > There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly. Naming things is hard,…

> Don't all types need names, regardless of what language you use? No. - In very dynamic languages (like javascript), most types arguably don't have names at all. For example, I can make a function to add 2d vectors together. Even though I can use 2d vectors in my program, there doesn't have to be a 2d vector type. (Eg, const vecAdd = (a, b) => ({x: a.x+b.x, y: a.y+b.y}) ). - Most modern languages have tuples. And tu…

> Arguably, c is a Color object. But actually, c must be either Color::Green or Color::Blue. The compiler understands this and uses it in lots of little ways. But unfortunately we can't actually name the restricted type in the program.

I think that's less a question whether you can, but rather whether you should or shouldn't design it that way... (I'll use TypeScript here for the simpler syntax)

It'd be perfectly fine to do something like this:

  type ColorR = 'red';
  type ColorG = 'green';
  type ColorB = 'blue';
  
  type ColorRGB = ColorR | ColorG | ColorB;
But which constraint should your new type ColorGB (your variable c) adhere to?

  // constraint A
  type ColorGB = ColorG | ColorB;

  // constraint B
  type ColorGB = Exclude;
I'd argue if the type ColorGB is only needed in the derived form from ColorRGB within a single scope, then just let the compiler do its control flow analysis, yes - it'll infer the type as constraint B.

But if you really need to reuse the type ColorGB (probably some categorization other than all the colors), then you'd need to pay close attention to your designed constraint.

Re: Don't Be Afraid of Types

#64
post #50

Earlier quoted context omitted.

> Don't all types need names, regardless of what language you use? No. - In very dynamic languages (like javascript), most types arguably don't have names at all. For example, I can make a function to add 2d vectors together. Even though I can use 2d vectors in my program, there doesn't have to be a 2d vector type. (Eg, const vecAdd = (a, b) => ({x: a.x+b.x, y: a.y+b.y}) ). - Most modern languages have tuples. And tu…

The named integer range thing is interesting. I guess it depends on what you goal is. Could you use asserts? Could you wrap the integer in an object and embed the restriction logic there?

You could do it with asserts or as a wrapped type, but both of those approaches are only checked at runtime. That means you don’t get runtime errors instead of compiler errors. It also limits the compiler’s ability to take advantage of the constraint to optimise. Also asserts need to be added everywhere - by both you and the compiler. And wrapped types are annoying to use since you lose access to all the standard arithmetic operators.

It would be better if the type system could encode types like this directly. Better for ergonomics and better for optimisation.

Re: Don't Be Afraid of Types

#65
post #50

Earlier quoted context omitted.

> Don't all types need names, regardless of what language you use? No. - In very dynamic languages (like javascript), most types arguably don't have names at all. For example, I can make a function to add 2d vectors together. Even though I can use 2d vectors in my program, there doesn't have to be a 2d vector type. (Eg, const vecAdd = (a, b) => ({x: a.x+b.x, y: a.y+b.y}) ). - Most modern languages have tuples. And tu…

> Arguably, c is a Color object. But actually, c must be either Color::Green or Color::Blue. The compiler understands this and uses it in lots of little ways. But unfortunately we can't actually name the restricted type in the program. I think that's less a question whether you can , but rather whether you should or shouldn't design it that way... (I'll use TypeScript here for the simpler syntax) It'd be perfectly fi…

I really wish I could do in rust what you're doing here in typescript.

Say I have some enum like ColorRGB here. In some contexts, only a limited subset of variants are valid - say, ColorGB. There's a few ways to code this - but they're all - in different ways - horrible:

1. Use ColorRGB in all contexts. Use asserts or something to verify that the value is one of the expected variants. This fails at expressing what I want in the type system - and the code is longer, slower and more error prone as a result.

2. Have two enums, ColorRGB and ColorGB. ColorRGB is defined as enum ColorRGB { Red, Restricted(ColorGB) }. This lets me encode the constraint - since I can use ColorGB explicitly. But it makes Color harder to use - since I need to match out the inner value all over the place.

3. Have two enums, ColorRGB and ColorGB which both have variants for Green and Blue. Implement conversion methods (impl From) between the two types. Now I have two types instead of one. I have conversions between them. And I'll probably end up with duplicate methods & trait impls for ColorRGB and ColorGB.

Luckily this doesn't come up that often. But - as your typescript example shows - it can just be expressed directly in the type system. And LLVM already tracks which variants are possible throughout a function for optimisations' sake. I wish rust had a way to express something like Exclude.

Re: Don't Be Afraid of Types

#66

Earlier quoted context omitted.

The named integer range thing is interesting. I guess it depends on what you goal is. Could you use asserts? Could you wrap the integer in an object and embed the restriction logic there?

Doable in TypeScript[0], but I'd wager if that's really necessary. [0]: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogdgVwL... from StackOverflow[1] [1]: https://stackoverflow.com/questions/39494689/is-it-possible-...

> but I'd wager if that's really necessary.

I think its more useful in a language like rust, because the compiler can use that information to better optimize the emitted assembly.

Re: Don't Be Afraid of Types

#67
In Java you can’t just create a type. You are creating a mini app.

Classes in Java hold logic and internal mutating state. I don’t want to create this just because I need a type.

Really you want to create a struct or an interface as a type.

Re: Don't Be Afraid of Types

#68
As somebody who is afraid of types (and also, who hates types, because we all hate what we fear), may my point of view serve as balance: you don't need a type system if everything is of the same type. Programming in a type-less style is an exhilarating and liberating experience:

assembler : everything is a word

C : everything is an array of bytes

fortran/APL/matlab/octave : everything is a multi-dimensional array of floats

lua : everything is a table

tcl : everything is a string

unix : everything is a file

In some of these languages there are other types, OK, but it helps to treat these objects as awkward deviations from the appropriate thing, and to feel a bit guilty when you use them (e.g., strings in fortran).

Re: Don't Be Afraid of Types

#69
post #9
post #4

The issue is names. Every darn little thing in Java needs a name. If there is no good name, that's a hint that maybe you don't need a new type. Obligatory Clojure example: (defn full-name [{:keys [first-name last-name]}] (str first-name " " last-name)) This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destruct…

> Every darn little thing in Java needs a name. Don't all types need names, regardless of what language you use? Look at typescript, and how it supports structural typing. They don't seem to have a problem with names. Why do you think Java has that problem when nominal type systems simplify the problem? > There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly. Naming things is hard,…

The types of closures are unnamable in C++ and Rust; each closure has a unique type that can't be written out. Function types (that is, "function item" types) in Rust are also unnamable.

Re: Don't Be Afraid of Types

#70
People might be afraid of types because in OOP land there's the idea that types aren't mere containers for data.

You have to use encapsulation, inheritance and polymorphism. Fields and properties shouldn't have public setters. You assign a value only through a method, otherwise you make the gods angry.

You have gazillions of constructors, static, public, protected, private and internal. And you have gazillions of methods, static, public, private, protected and internal.

You inherit at least an abstract class and an interface.

You have at least some other types composed in your type.

Unless you do all this, some people might not consider them proper types.

I had my engineering manager warn me that data classes are "anemic models". Yes, but why? "We should have logic and methods to set fields in classes". "Yes, but why?" "It's OOP and we do DDD and use encapsulation." "Yes, but why? Imagine we have immutable records that hold just data and static classes as function containers, and those functions just act on the records, return some new ones and change no state. This way we can reason with ease about our goddam software, especially if we don't encapsulate state and mutate it all over the place, Uncle Bob, be damned." He shook his head in horror. He probably thinks I am a kind of heretic.

Post reply on HN