Remix.run Logo
b_e_n_t_o_n 3 hours ago

Hmm, IME the preferred type systems are structural - a function shouldn't care what the name is of the struct passed to it, it should just work if it has the correct fields.

lmm 2 hours ago | parent | next [-]

I think that's backwards - ultimately everything on a computer is just bytes, so if you push that philosophy to the limit then you would write untyped functions and they can "just work" on any input (just not necessarily giving results that are sensible or useful if the input is wrong). The point of a type system is to help you avoid writing semantically wrong code, to bring errors forward, and actually the most important and valuable use case is distinguishing values that are structurally identical but semantically different (e.g. customer ID vs product ID, x coordinate vs y coordinate, immutable list vs read view of mutable list, sorted vs unsorted...).

b_e_n_t_o_n 2 hours ago | parent [-]

I think the structural type approach leans heavily into the "computation is just data and its transformations", so it makes sense for it to treat data as the most important thing. You end up thinking less about classification and more about the transformations.

I'm not saying the nominal approach to types is wrong or bad, I just find my way of thinking is better suited for structural systems. I'm thinking less about the semantics around product_id vs user_id and more about what transforms are relevant - the semantics show up in the domain layer.

Take a vec3 for example, in a structural system you could apply a function designed for a vec2 on it, which has practical applications.

lmm 10 minutes ago | parent | next [-]

> I'm not saying the nominal approach to types is wrong or bad, I just find my way of thinking is better suited for structural systems. I'm thinking less about the semantics around product_id vs user_id and more about what transforms are relevant - the semantics show up in the domain layer.

But that domain layer should make use of the type system! That's where the type system is most useful!

valenterry an hour ago | parent | prev [-]

> I think the structural type approach leans heavily into the "computation is just data and its transformations"

But it's never "just data". My password is different in many ways than my username. Don't you ever log/print it by accident! So even if structurally the same, we MUST treat it different. Hence any approach that always only looks at things structurally is deeply flawed in the context of safe software development.

b_e_n_t_o_n 39 minutes ago | parent | next [-]

Yeah you bring up a good point. A { name: string } dict needs to be treated differently from a { user_pw: string } dict. The difference is that happens in the domain layer instead of the type layer.

an hour ago | parent | prev [-]
[deleted]
seanmcdirmid 2 hours ago | parent | prev | next [-]

Structural type systems mostly don’t support encapsulation (private members that store things like account numbers) without some sort of weird add on, while nominal type systems support encapsulation directly (because the name hides structure). The canonical example is a cowboy and picture that both have a draw method.

b_e_n_t_o_n 2 hours ago | parent [-]

Both Go and TS are structural and support encapsulation fine, I'm not sure why that would be an issue.

seanmcdirmid 2 hours ago | parent [-]

TS doesn’t really. TS simply treats private fields as public ones when it comes to structural type checks. TS is unsound anyways, so not providing hard guarantees about field access safety is right up its alley. More to the point, if you specify a class type with private fields as a requirement, whatever you plug into that requirement has to have those private fields, they are part of the type’s public signature.

To get where structural type systems fall down, think about a bad case is when dealing with native state and you have a private long field with a pointer hiding in it used in native calls. Any “type” that provides that long will fit the type, leading to seg faults. A nominal type system allows you to make assurances behind the class name.

Anyways, this was a big deal in the late 90s, eg see opaque types https://en.wikipedia.org/wiki/Opaque_data_type.

b_e_n_t_o_n an hour ago | parent [-]

Typescript had to support JS's quirks... :/

   class Foo {
      public bar = 1;
      private _value = 'hello';
      static doSomething(f: Foo) {
         console.log(f._value);
      }
   }
   class MockFoo { public bar = 1; }
   let mock = new MockFoo();
   Foo.doSomething(mock); // Fails
Which is why you'd generally use interfaces, either declared or inline.

In the pointer example, if the long field is private then it's not part of the public interface and you shouldn't run into that issue no?

o11c 3 hours ago | parent | prev | next [-]

The critical problem with structural typing is that it requires weird and arbitrary branding when dealing with unions of singletons.

whilenot-dev 7 minutes ago | parent | next [-]

Branding doesn't need to be weird and arbitrary, see Pythons NewType: https://docs.python.org/3/library/typing.html#typing.NewType

b_e_n_t_o_n 2 hours ago | parent | prev | next [-]

You mean like if you have two types which are identical but you want your type system to treat them as distinct? To me that's a data modelling issue rather than something wrong with the type system, but I understand how it can sometimes be unavoidable and you need to work around it.

I think it also makes more sense in immutable functional languages like clojure. Oddly enough I like it in Go too, despite being very different from clojure.

andyferris 2 hours ago | parent | prev [-]

If I understand you correctly - in popular structurally typed languages, sure.

It seems ok in upcoming languages with polymorphic sum types (eg Roc “tags”) though?

stirfish 3 hours ago | parent | prev [-]

> should just work if it has the correct fields.

Correct fields by...name? By structure? I'm trying to understand.

b_e_n_t_o_n 2 hours ago | parent [-]

By name, type, and structure. In typescript for example:

   let full_name = (in: { first: string, last: string }) => in.first + " " + in.last
Then you can use this function on any data type that satisfies that signature, regardless of if it's User, Dog, Manager etc.