At least in Javascript you have JSDoc.
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, but don't criticize typing for a problem caused by your lack of imagination. A basic fallback strategy to name specialized types is to add adjectives. Instead of ObjectWithFirstNameAndLastName you could have NamedObject. You don't need to overthink it, with namespaces and local contexts making sure conflicts don't happen.
There are two mindsets: try to work around problems to reach your goals, and try to come up with any problem to find excuses to not reach your goals. Complaining about naming sounds a lot like the second.
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 tuples are usually anonymous. For example, in rust I could pass around 2d vectors by simply using tuples of (f64, f64). I can even give my implicit vector type functions via the trait system.
- In typescript you can have whole struct definitions be anonymous if you want to. Eg: const MyComponent(props: {x: number, y: string, ...}) {...}.
- There's also lots of types in languages like typescript and rust which are unfortunately impossible to name. For example, if I have this code:
#[derive(Eq, PartialEq)]
enum Color { Red, Green, Blue }
fn foo(c: Color) {
if c == Color::Red { return; }
// What is the type of 'c' here?
}
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.Rust can do the same thing with integers - even though (weirdly) it has no way to name an integer in a restricted range. For example, in this code the compiler knows that y must be less than 256 - so the if statement is always false, and it skips the if statement entirely:
https://rust.godbolt.org/z/3nTrabnYz
But - its impossible to write a function that takes as input an integer that must be within some arbitrary range.
[0]: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogdgVwL... from StackOverflow[1]
[1]: https://stackoverflow.com/questions/39494689/is-it-possible-...
I think its more useful in a language like rust, because the compiler can use that information to better optimize the emitted assembly.
It starts with the question of how I would like to design my sequences:
- Is it the range (0, 10] in steps of 1?
- Is it the range (0, 2^10) in steps by the power of 2?
- Is it the range (0.0, 2PI] in steps of EPSILON?
How would the compiler engineer generalize this optimization?
And the question would continue whether I'd be really able to define them that precisely before runtime. Most applications are just dealing with lightweight structures, mostly serialized JSON nowadays, and then even there are enough fuck-ups[0] where such an optimization wouldn't help at all.
I can imagine the places where they really matter are some heavy memory intensive data structures like deep neural networks, graphics or video and the like - for the time being they're just dealing with tensors of data type floatX, and that seems to be fine AFAIK.
I mean, I'd be really nice if the smaller memory footprint could come out-of-the-box during compilation. But all the CLI tools written in rust certainly don't have the use case to put this complication on the shoulders of compiler research.
[0]: https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times...
It would be better if the type system could encode types like this directly. Better for ergonomics and better for optimisation.
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<ColorRGB, ColorR>;
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.
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<ColorRGB, ColorR>.
This is a quote from Alan J. Perlis, not Rich Hickey, and it's certainly not what Rich is famous for.
It sounds like you're confusing things. Factories are a way to instantiate objects of a specific type a certain way. Singletons is just a restriction on how many instances of a type there can be.
These are not Java concepts, nor are they relevant to the topic of declaring and naming types.
To the point many think the famous patterns book used Java, when it is all about Smalltalk and C++ patterns.
Java's problem is that it's hugely successful, and to some it's the only language they ever experience in their formative years. Thus, because poor workmen always blames the tools, Java becomes the root of all evil.
People complain about this all the time but I'd rather take a verbose name than not knowing what is going on. Sometimes these naming conventions help.
Digging into older poorly named, structured and documented codebases is not fun.
Good comments are better than good function names.
No. Your statements are fundamentally wrong at many levels. Factories are tools to instantiate objects of a specific type a certain way. They are constructors that don't suffer from the limitations of constructors. A factory does not impose any requirement on object life cycle.
On the other hand, it may do something with the object it's instantiating before passing it back. Such as putting it into global scope, or tying it to something outside its chain. Factories that sit outside the class file are often prone to all kinds of grotesque later mucking-with that causes the instantiated object to bind to other scope, and never be properly garbage collected. That's what I'm talking about.
Build better constructors and you won't usually need factories. If you need factories as an API, build them next to the constructor.
Sufficiently flexible namespaces do solve most of these problems. Java is kind of perverse though.
Map m = new HashMap() {{ System.out.println("I am a unique subclass of HashMap with a single instance!");}};
Because the types in typescript don't need names. And the type "object with firstName and lastName" is one such type that doesn't need a name.
So:
> They don't seem to have a problem with names.
Yes. The problem is much smaller there, and mostly caused by programmer cultures that insist on naming everything.
Here's a simple example: https://github.com/dharmab/skyeye/blob/main/pkg/bearings/bea...
fullName = map list \(firstName, lastName) -> firstName + " " + firstName
and type it as `funfullName: (String, String)[] -> String`.I have worked on large scale systems in both types and untyped languages and I cannot emphasize strongly enough how important types are.
But one thing is certain: When you have that one function that is used 165 times throughout the code base, having a type checker is certainly going to help you when you add in the users middle initial.
In an ideal world :)
In the real world the customer doesn't know what they want and you can't fully guess what they want or need ahead of time no matter how many diagrams you draw.
Incidentally, one of the few good things that came out of the "agile" religion.
And the exact point I tried to communicate.
When you decide to have loose architectural structures, you might allow just writing these types of functions adhoc.
The further you go in the project, the more your strengthen the architecture, where needed.
Code that just works right now never scales.
There's no way to tell.
And as others replied, the issue of naming a type exists in all languages.
Why is it that dynamically typed languages usually develop static typing extensions (including Clojure)? Perhaps people don’t enjoy hunting down tedious spelling issues such as last-name vs family-name?
Duck typing is great, what's even better is documenting when when they need to quack or waddle.
class Command:
def execute(self) -> None:
# some implementation
and class Prisoner:
def execute(self) -> None:
# some other implementation
The implementor of the Prisoner class might not want the Prisoner class to be able to be slotted in where the Command class can be slotted in. Your type checker will be of no help here. If you use abstract base classes, your type checker can prevent such mistakes.So when it comes to your own code, the drawbacks of the structural Protocols in comparison the nominal ABCs are pretty big. The pros seem non-existent. The pro, I guess, is that you don't have to type the handful of characters "(Baseclass)" with every concrete implementation.
But they do have one major advantage: if you have third party code that you have no control over, and you have some part of this codebase that you want to replace with your own, and there's no convenient way to do something like the adapter pattern, because it's somehow a bit deeply nested then a Protocol is a great solution.
1. This (or maybe a less trivial form of this) will bite you in the ass when you end up using other people's unnamed types. Or even when you use your own unnamed types that come from code you haven't touched in three years.
2. That's what interfaces are for in Java. Or at least modern Java.
Sadly, at the beginning, many people came to Java from C/C++, and they did the thing we used to call "writing C/C++ code in Java".
The interfaces is the only thing i loved from it.
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:
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 destructuring to access keys `:first-name` and `:last-name` of a map passed in (so the type of the unnamed argument is just Map)This function works for anything that has a key `:first-name` and `:last-name`.
There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly.