question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Refactor Instance Constructors

See original GitHub issue

Currently, instances are constructed in the form of new T([parent]), where T is the class name

I’d like to propose switching to the format of: new Instance(className[, parent])

Reasons why I think new Part() is a bad design:

  1. Namespace collisions In some contexts, you cannot create variables of the name Part due to it already being reserved by a class. However, this class doesn’t really “exist” as a value.
  2. Extending Classes We have a bug where users can create classes in the form of class Foo extends Part {}. Bugs like this were introduced due to the new Part() design using classes which add to the value namespace.
  3. Using Instance Classes as Values Users can pass around these magic Instance classes as values. For example, foo(Part);. This doesn’t really make sense from a design perspective, since Part isn’t actually a value. We attempted to fix this using magic instance constructor values, but I think that goes against what users expect that value might be.
  4. Learning Curve We tell users that they should use new T() in place of T.new(), but for Instances there is an exception. I think switching this behavior to new Instance("Part") will make more sense to new users and is more on parity with the Roblox feature set.

Switching to new Instance(className[, parent]) solves all of these problems and simplifies the compiler design.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:6
  • Comments:9 (8 by maintainers)

github_iconTop GitHub Comments

6reactions
evaeracommented, Dec 21, 2018

I disagree with these changes.

  • This breaks general polymorphism. Consider a simple function like this which instantiates a class and returns the new instance:
function polymorphic<T>(something: new () => T): T {
   return new something()
}

While this is a rather contrived example, this pattern does have its use cases. Writing reusuable code is a cornerstone of well-designed software, and falling back to the new Instance("ClassName") pattern breaks the simplicity of this pattern, forcing us to implement a special case for Roblox Instances.

  • Instantiating one class shouldn’t give you a different one. In TypeScript, it’s very strange and unconventional that instantiating one class would give you another (e.g. new Instance("Part") gives you a Part, but you instantiated an Instance). This might seem commonplace coming from Lua, but this is not at all common practice and potentially confusing elsewhere. This case is essentially a flag parameter – which are best avoided.

  • The current pattern is standard TypeScript and gracefully integrates with the language. Roblox’s Instance system is painfully unconventional when compared to the other instantiable objects in the API (such as datatypes). When writing TypeScript, it seems obvious to me that class-like objects should act like TypeScript classes. I do not agree with the idea that we should break the patterns of this language feature found in nearly every TypeScript program just to align slightly better with the way Roblox implemented Instances.

    We can consider another aspect of roblox-ts: arrays. We have a crazy amount of methods and fields on arrays that aren’t present at all in Lua. It definitely benefits us as developers to augment arrays with methods for quality of life and to more closely resemble standard TypeScript. I think the same reasoning is applicable in this situation as well. While the pattern of using classes to represent Roblox Instances is not a direct 1:1 relationship with Lua, I strongly believe that it benefits us as TypeScript developers to do so. Not only do we get to write more conventional code, but we get to do so without needing to worry about special cases for Roblox Instances (think easy, polymorphic interoperability between your own classes and Instances – not possible after these changes!) or making any concessions.

    Sure, we might have to add an extra paragraph in the docs, but that seems like a small price to pay for the benefits we gain.


An example of existing code that will be irrevocably broken without hacks or a much more complex solution is my rbx-resources library. Right now, it can accept a class value and instantiate that for you or return it if it already exists:

const event = getResource(RemoteEvent, "SomeRemoteEvent");

But it also works with custom objects that you have saved in the game already, but obviously these custom types cannot be automatically instantiated if they are missing:

const sword = getResource<Model>("Weapon", "Darkheart");

The problem, if these proposed changes are implemented, is that without being able to use RemoteEvent directly as a value, the code can have no way to know if that object can be instantiated or not, as we’d be forced to resort to strings. The best solution would just be to wrap new Instance(className) in a try/catch and see if it works – ew.

This is just one example of something that becomes much harder to do if these changes become reality – and many similar polymorphic use cases will fall into the same bucket.


Counter-Points

  1. Namespace Collisions TypeScript code conventionally uses camelCase for variables, and PascalCase for classes. All of the current Instance types follow this converntion, so users should not find naming collisions with regular variables. But even if they are making custom classes, they should not make a class that shares the same name as a Roblox instance in the first place, because this breaks the type system – regardless of if the proposed changes are implemented or not, because classes always becomes types as well.

    Should users be allowed to deviate from this casing convention? Of course, but there is always going to be a penalty for being unconventional. Conventions do exist for a reason–compatibility, organization, and intuitiveness to name a few–so users should know what they’re getting into by going against the grain.

  2. Extending Classes While this is a caveat to using TypeScript classes to represent Roblox Instances and datatypes, it’s probably the only prominent one. I think the benefits we gain from having a standard and conventional class system far outweigh this point. We can just throw a TranspilerError for this, and in the future add it to our custom TSLint rules. But I don’t really think this is even an big issue to begin with, because it isn’t likely someone would accidentally bump into this.

  3. Using Instance Classes as Values I believe that this is a good thing! Right now we dynamically generate “class values” for instances to match other Roblox data types – a table with a new field that creates that type of Instance. I see this as a good thing, as we are normalizing the Roblox API and it opens up a world of new opportunities, notably my rbx-resources module as mentioned above.

  4. Learning Curve I don’t think making concessions like this because it makes coming from Lua easier is a good reason. Users of roblox-ts should learn how to use TypeScript and adopt its conventions rather than bringing baggage from Lua along. There are already a lot of things that are bad practice in TypeScript that users regularly do in Lua, so it’s not like this is the only mentality shift that will need to occur when switching to TypeScript. In fact, this is probably one of the least obtrusive ones.

3reactions
osyrisrblxcommented, Dec 21, 2018

It’s worth noting that users should expect us to have breaking changes before v0.1. We’re still very much in the “design” phase. But of course, if we do introduce a change like this, it would be announced via Discord.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Replace Constructor with Factory Method refactoring - JetBrains
Right-click and choose Refactor | Replace Constructor with Factory Method from the context menu. Choose ReSharper | Refactor | Convert |  ...
Read more >
Refactoring to Patterns: Creation | Replace Constructors with ...
Joshua Kerievsky presents six refactorings that target design problems in everything from constructors to overly complicated construction ...
Read more >
Is there a way to refactor these constructors? - Stack Overflow
Here are the constructors to my object private static class myObject { public myObject(int argA) { this.argA = argA; } public myObject(int ...
Read more >
Replace Constructors with Creation Methods - Industrial Logic
Constructors on a class make it hard to decide ... Replace the constructors with intention-revealing. Creation Methods that return object instances.
Read more >
invalid argument - Refactoring Away from Static Constructors
Refactoring Away from Static Constructors ... One of the great barriers to making code testable is the idea of the static constructor. This...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found