Closed christopherjbaker closed 5 years ago
@BigAB @mickmcgrath13 @DesignByOnyx @justinbmeyer You've all expressed an interest in a type system for can-observe. Any thoughts?
Can we put that in a separate library? imo it would be nice of there were proxy based observables that didn't have rigid type definitions and then types could be added on top, probably via decorators like in your example. I would think these decorators would be agnostic of the constructor/class they are extending, so they wouldn't need to be aware of can-observe.
@matthewp I'm not opposed to putting them in their own repo as long as they are usable on their own. Do you mean essentially that the above example doesn't extend ObserveObject
but that the type system still workes with the naked class?
I'm just wondering, do we need a runtime type system?
Would a static type checker (like flow or typescript) give us much of what we want, without re-inventing the wheel?
Of course static type checking wouldn't help with coercions, but maybe that could be it's own thing?
@BigAB a runtime type system gives type checking to people who aren't using flow / typescript (likely the vast majority of JS devs).
Also, I see this as similar to enforcement of "required" properties which is pretty common across frameworks even though "required" properties are solved by static type checking languages.
@matthewp I think it would be GREAT if this could be a standalone util. But I think there might be a few difficulties making something work for can-define, can-observe, and everything else ...
The big question w/ getters and setters is "What are you doing with the value"? What I mean by that is if you do a basic getter / setter, you need to store the value somewhere else. The following more or less what can-define
does (if "foo"
must be a string):
class Type(){
constructor(){
this._data = {}
}
get foo(){ return this._data.foo }
set foo(newVal) {
this._data.foo = canReflect.isMember( newVal, StringType) ?
newVal : canReflect.new(StringType, [newVal]);
}
}
These getter/setters will need to know where to store the value. It might be possible to use weakmaps (with some performance cost). But can-observe
's Proxies
have the underlying object to save values on.
Another problem will be keeping these things observable. If we added a getter / setter on the prototype, we need getting to call ObservationRecorder.add()
and setting to fire events. I'm not sure how this would work with can-observe
. With can-define
, we basically wire up a single getter
and setter
. The type conversion:
return canReflect.isMember( newVal, StringType) ? newVal : canReflect.new(StringType, [newVal])`)
Happens before the storing:
this._data.foo = RESULT;
Due to this complexity, I would build these decorators to work with can-observe
first. Once it's working, lets see if we can make something more generally useful.
Here's my opinion on those questions:
String
, Boolean
, Number
; probably never support strings.
API:If we are going to offer both validation and coercion, I think we should make it extremely clear which is chosen by making it two different functions: validateType
and coerceType
. Otherwise I think we should stick to validation.
I don't really have an opinion on the availability of string-based type specifiers, but I do think we should make it exceptionally easy to validate/coerce more complicated things like specific string patterns (such as phone numbers). I think a function is easy enough here, which can throw an error for validation fails and return a new value for coercion, or both for values that can't be coerced.
I think it being decorators primarily is fine, though I've found that they have become less popular in the JS ecosystem (I think primarily due to the instability of the proposals). I think the structure specified in this pr is a good one that is very simple to implement and use.
can-type
is going to take over this.
can-observe
needs a type system. At the very least, it should support as much as can-define (string, date, number, boolean, Type).What needs to be decided:
Examples: