Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@thejohnfreeman
Created August 7, 2019 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thejohnfreeman/31c890be622596bcd30004a9e5cb52c0 to your computer and use it in GitHub Desktop.
Save thejohnfreeman/31c890be622596bcd30004a9e5cb52c0 to your computer and use it in GitHub Desktop.
// Objects.ts
type ObjectOf<T> = {
[key: string]: T;
};
function map<A, B>(
input: ObjectOf<A>,
f: (a: A, key: string) => B
): ObjectOf<B> {
const output = {} as ObjectOf<B>;
Object.keys(input).forEach(key => {
output[key] = f(input[key], key);
});
return output;
}
function zipWith<A, B, C>(
ayys: ObjectOf<A>,
bees: ObjectOf<B>,
f: (a: A, b: B) => C
): ObjectOf<C> {
const output = {} as ObjectOf<C>;
Object.keys(ayys).forEach(key => {
output[key] = f(ayys[key], bees[key]);
});
return output;
}
type Pluck<F extends ObjectOf<object>, A extends keyof F[keyof F]> = {
[K in keyof F]: F[K][A]
};
function pluck<F extends ObjectOf<object>, A extends keyof F[keyof F]>(
functor: F,
attribute: A
): Pluck<F, A> {
const output = {} as Pluck<F, A>;
Object.keys(functor).forEach((key: keyof F) => {
output[key] = functor[key][attribute];
});
return output;
}
pluck({ a: { k: 1 }, b: { k: "c" } }, "k").a;
pluck({ a: { k: 1 }, b: { k: "c" } }, "k").b;
pluck({ a: { k: 1 }, b: { k: "c" } }, "k1");
// ViewModel.ts
interface ViewModel<V, R> {
value: V;
repr: R;
}
interface ViewModelConstructor<
I,
V extends I = I,
R = V,
M extends ViewModel<V, R> = ViewModel<V, R>
> {
construct(initValue?: M | I): M;
}
// FieldViewModel.ts
class FieldViewModel<V, R = V> implements ViewModel<V, R> {
public constructor(public value: V, public repr: R) {}
}
type FieldViewModelConstructor<
I,
V extends I = I,
R = V
> = ViewModelConstructor<I, V, R, FieldViewModel<V, R>>;
// ArrayViewModel.ts
class ArrayViewModel<V, R, M extends ViewModel<V, R>>
implements ViewModel<V[], R[]> {
public constructor(public readonly items: M[]) {}
public value: V[] = this.items.map(item => item.value);
public repr: R[] = this.items.map(item => item.repr);
}
type ArrayViewModelConstructor<
I,
V extends I = I,
R = V,
M extends ViewModel<V, R> = ViewModel<V, R>
> = ViewModelConstructor<I[], V[], R[], ArrayViewModel<V, R, M>>;
// There is no ViewModelConstructorArray because arrays are homogeneous.
// They are not constructed with an array of ViewModelConstructors.
// They are constructed with a single element ViewModelConstructor.
// GroupViewModel.ts
type ViewModelGroup = ObjectOf<ViewModel<unknown, unknown>>;
type ValueGroup<G extends ViewModelGroup> = Pluck<G, "value">;
type ReprGroup<G extends ViewModelGroup> = Pluck<G, "repr">;
class GroupViewModel<G extends ViewModelGroup>
implements ViewModel<ValueGroup<G>, ReprGroup<G>> {
public constructor(public readonly members: G) {}
public value: ValueGroup<G> = pluck(this.members, "value");
public repr: ReprGroup<G> = pluck(this.members, "repr");
}
// TODO: Use a real Proxy to make this easier.
function makeGroupProxy<G extends ViewModelGroup>(
gvm: GroupViewModel<G>
): ValueGroup<G> {
const proxy = {} as ValueGroup<G>;
Object.keys(gvm.members).forEach(key => {
Object.defineProperty(proxy, key, {
enumerable: true,
get: () => gvm.members[key].value,
set: value => (gvm.members[key].value = value)
});
});
return Object.freeze(proxy);
}
type GroupViewModelConstructor<G extends ViewModelGroup> = ViewModelConstructor<
Partial<ValueGroup<G>>,
ValueGroup<G>,
ReprGroup<G>,
GroupViewModel<G>
>;
type ViewModelConstructorGroup = ObjectOf<
ViewModelConstructor<unknown, unknown, unknown, ViewModel<unknown, unknown>>
>;
type ViewModelGroupIsomorphicTo<CG extends ViewModelConstructorGroup> = {
[K in keyof CG]: ReturnType<CG[K]["construct"]>
};
// ViewModels.ts
type IntegerViewModel = FieldViewModel<number, string>;
function integer(): FieldViewModelConstructor<number, number, string> {
return {
construct(initValue: IntegerViewModel | number): IntegerViewModel {
return initValue instanceof FieldViewModel
? initValue
: new FieldViewModel(initValue, "" + initValue);
}
};
}
export function array<
I,
V extends I = I,
R = V,
M extends ViewModel<V, R> = ViewModel<V, R>
>(
ctor: ViewModelConstructor<I, V, R, M>
): ArrayViewModelConstructor<I, V, R, M> {
return {
construct(
initValues: ArrayViewModel<V, R, M> | I[] = []
): ArrayViewModel<V, R, M> {
return initValues instanceof ArrayViewModel
? initValues
: new ArrayViewModel(
initValues.map(initValue => ctor.construct(initValue))
);
}
};
}
function group<G extends ViewModelConstructorGroup>(
ctors: G
): GroupViewModelConstructor<ViewModelGroupIsomorphicTo<G>> {
return {
construct(
initValues:
| GroupViewModel<ViewModelGroupIsomorphicTo<G>>
| Partial<ValueGroup<ViewModelGroupIsomorphicTo<G>>> = {}
): GroupViewModel<ViewModelGroupIsomorphicTo<G>> {
return initValues instanceof GroupViewModel
? initValues
: new GroupViewModel(map(ctors, (ctor, key) =>
ctor.construct(initValues[key])
) as ViewModelGroupIsomorphicTo<G>);
}
};
}
// tests.ts
const ic1 = integer();
const i1 = ic1.construct(0);
i1.repr;
i1.value;
const avmc1 = array(integer());
const avm1 = avmc1.construct([1, 2, 3]);
console.log('avm1.repr = ["1", "2", "3"]', avm1.repr);
console.log("avm1.value = [1, 2, 3]", avm1.value);
const r0 = avm1.repr[0];
const v1 = avm1.value[0];
const avm2 = avmc1.construct(avm1);
console.log('avm2.repr = ["1", "2", "3"]', avm2.repr);
console.log("avm2.value = [1, 2, 3]", avm2.value);
console.log("avm2.items", avm2.items);
const gvmc1 = group({
age: integer()
});
const gvm1 = gvmc1.construct({ age: 32 });
console.log("gvm1.members.age :: FieldViewModel<number, string>", gvm1.members
.age as FieldViewModel<number, string>);
console.log(
"gvm1.value.age = 32 :: number",
gvm1.value.age as number,
typeof gvm1.value.age
);
console.log(
'gvm1.repr.age = "32" :: string',
gvm1.repr.age as string,
typeof gvm1.repr.age
);
const avmc2 = array(gvmc1);
const avm3 = avmc2.construct([{ age: 32 }]);
console.log(
"avm3.items :: GroupViewModel<{ age: FieldViewModel<number, string> }>[]",
avm3.items
);
console.log(
"avm3.value[0].age = 32 :: number",
avm3.value[0].age as number,
typeof avm3.value[0].age
);
console.log(
'avm3.repr[0].age = "32" :: string',
avm3.repr[0].age as string,
typeof avm3.repr[0].age
);
const gvmc2 = group({
series: array(integer())
});
const gvm2 = gvmc2.construct({ series: [1, 2, 3] });
console.log("gvm2.value.series = [1, 2, 3]", gvm2.value.series);
console.log(
"gvm2.value.series[0] = 1 :: number",
gvm2.value.series[0] as number,
typeof gvm2.value.series[0]
);
gvmc2.construct();
gvmc2.construct({});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment