diff --git a/docs/manual/09 Functional Programming.md b/docs/manual/09 Functional Programming.md index 11012329..b50bebd4 100644 --- a/docs/manual/09 Functional Programming.md +++ b/docs/manual/09 Functional Programming.md @@ -208,25 +208,25 @@ One way to achieve this is by wrapping the body of a normal function with `alway ```ts // normal function const getGreetingMessage = (country: string) => { - switch (country) { - case "de": return "Hallo"; - case "fr": return "Bonjour"; - case "es": return "Hola"; - default: return "Hello"; - } + switch (country) { + case "de": return "Hallo"; + case "fr": return "Bonjour"; + case "es": return "Hola"; + default: return "Hello"; + } } // reactive version const getGreetingMessageReactive = (country: RefOrValue) => { - // returns a Ref that gets updated reactively - return always(() => { - switch (country) { - case "de": return "Hallo"; - case "fr": return "Bonjour"; - case "es": return "Hola"; - default: return "Hello"; - } - }) + // returns a Ref that gets updated reactively + return always(() => { + switch (country) { + case "de": return "Hallo"; + case "fr": return "Bonjour"; + case "es": return "Hola"; + default: return "Hello"; + } + }) } ``` @@ -236,12 +236,12 @@ Although the created reactive function is called with `Ref` values, you don't ne ```ts // reactive function, returns a Ref that gets updated reactively when 'country' changes const getGreetingMessageReactive = reactiveFn((country: string) => { - switch (country) { - case "de": return "Hallo"; - case "fr": return "Bonjour"; - case "es": return "Hola"; - default: return "Hello"; - } + switch (country) { + case "de": return "Hallo"; + case "fr": return "Bonjour"; + case "es": return "Hola"; + default: return "Hello"; + } }) const country: Ref = $$("de"); diff --git a/functions.ts b/functions.ts index cd31a2b4..b5bab2d1 100644 --- a/functions.ts +++ b/functions.ts @@ -33,18 +33,18 @@ export function always(script:TemplateStringsArray, ...vars:any[]): P export function always(scriptOrJSTransform:TemplateStringsArray|SmartTransformFunction, ...vars:any[]) { // js function if (typeof scriptOrJSTransform == "function") { - // make sure handler is not an async function - if (scriptOrJSTransform.constructor.name == "AsyncFunction") { - throw new Error("Async functions are not allowed as always transforms") - } - const ptr = Pointer.createSmartTransform(scriptOrJSTransform, undefined, undefined, undefined, vars[0]); - if (!ptr.value_initialized && ptr.waiting_for_always_promise) { - throw new PointerError(`Promises cannot be returned from always transforms - use 'asyncAlways' instead`); - } - else { - return Ref.collapseValue(ptr); - } - } + // make sure handler is not an async function + if (scriptOrJSTransform.constructor.name == "AsyncFunction") { + throw new Error("Async functions are not allowed as always transforms") + } + const ptr = Pointer.createSmartTransform(scriptOrJSTransform, undefined, undefined, undefined, vars[0]); + if (!ptr.value_initialized && ptr.waiting_for_always_promise) { + throw new PointerError(`Promises cannot be returned from always transforms - use 'asyncAlways' instead`); + } + else { + return Ref.collapseValue(ptr); + } + } // datex script else return (async ()=>Ref.collapseValue(await datex(`always (${scriptOrJSTransform.raw.join(INSERT_MARK)})`, vars)))() } @@ -66,17 +66,17 @@ export function always(scriptOrJSTransform:TemplateStringsArray|SmartTransformFu */ export async function asyncAlways(transform:SmartTransformFunction, options?: SmartTransformOptions): Promise> { // return signature from Value.collapseValue(Pointer.smartTransform()) // make sure handler is not an async function - if (transform.constructor.name == "AsyncFunction") { - throw new Error("asyncAlways cannot be used with async functions, but with functions returning a Promise") - } - const ptr = Pointer.createSmartTransform(transform, undefined, undefined, undefined, options); - if (!ptr.value_initialized && ptr.waiting_for_always_promise) { - await ptr.waiting_for_always_promise; - } - else { - logger.warn("asyncAlways: transform function did not return a Promise, you should use 'always' instead") - } - return Ref.collapseValue(ptr) as MinimalJSRef + if (transform.constructor.name == "AsyncFunction") { + throw new Error("asyncAlways cannot be used with async functions, but with functions returning a Promise") + } + const ptr = Pointer.createSmartTransform(transform, undefined, undefined, undefined, options); + if (!ptr.value_initialized && ptr.waiting_for_always_promise) { + await ptr.waiting_for_always_promise; + } + else { + logger.warn("asyncAlways: transform function did not return a Promise, you should use 'always' instead") + } + return Ref.collapseValue(ptr) as MinimalJSRef } /** @@ -98,15 +98,26 @@ export async function asyncAlways(transform:SmartTransformFunction, option * ``` */ export function reactiveFn(fn: (...args: Args) => Awaited>>) { - return (...args: MapToRefOrVal) => always(() => { - const collapsedArgs = args.map(arg => Ref.collapseValue(arg, true, true)) as Args; - return fn(...collapsedArgs) - }); + return (...args: MapToRefOrVal) => always(() => { + const collapsedArgs = args.map(arg => Ref.collapseValue(arg, true, true)) as Args; + return fn(...collapsedArgs) + }); } type MapToRefOrVal = {[K in keyof T]: T[K] extends Ref ? T[K] : RefOrValue} +const getGreetingMessage = (country: RefOrValue) => { + return always(() => { + switch (country) { + case "de": return "Hallo"; + case "fr": return "Bonjour"; + case "es": return "Hola"; + default: return "Hello"; + } + }) +} + /** * Runs each time a dependency reference value changes. * Dependency references are automatically detected. @@ -127,46 +138,46 @@ type MapToRefOrVal = {[K in keyof T]: T[K] extends Ref ? T[ */ export function effect|undefined>(handler:W extends undefined ? () => void|Promise :(weakVariables: W) => void|Promise, weakVariables?: W): {dispose: () => void, [Symbol.dispose]: () => void} { - let ptr: Pointer; - - // make sure handler is not an async function - if (handler.constructor.name == "AsyncFunction") { - throw new Error("Async functions are not allowed as effect handlers") - } - - // weak variable binding - if (weakVariables) { - const weakVariablesProxy = {}; - for (const [k, v] of Object.entries(weakVariables)) { - const weakRef = new WeakRef(v); - Object.defineProperty(weakVariablesProxy, k, {get() { - const val = weakRef.deref() - if (!val) { - // dispose effect - ptr.is_persistent = false; - ptr.delete() - throw Pointer.WEAK_EFFECT_DISPOSED; - } - else return val; - }}) - } - const originalHandler = handler; - handler = (() => originalHandler(weakVariablesProxy)) as any; - } - - ptr = Pointer.createSmartTransform(handler as any, undefined, true, true); - ptr.is_persistent = true; - - return { - [Symbol.dispose||Symbol.for("Symbol.dispose")]() { - ptr.is_persistent = false; - ptr.delete() - }, - dispose() { - ptr.is_persistent = false; - ptr.delete() - } - } + let ptr: Pointer; + + // make sure handler is not an async function + if (handler.constructor.name == "AsyncFunction") { + throw new Error("Async functions are not allowed as effect handlers") + } + + // weak variable binding + if (weakVariables) { + const weakVariablesProxy = {}; + for (const [k, v] of Object.entries(weakVariables)) { + const weakRef = new WeakRef(v); + Object.defineProperty(weakVariablesProxy, k, {get() { + const val = weakRef.deref() + if (!val) { + // dispose effect + ptr.is_persistent = false; + ptr.delete() + throw Pointer.WEAK_EFFECT_DISPOSED; + } + else return val; + }}) + } + const originalHandler = handler; + handler = (() => originalHandler(weakVariablesProxy)) as any; + } + + ptr = Pointer.createSmartTransform(handler as any, undefined, true, true); + ptr.is_persistent = true; + + return { + [Symbol.dispose||Symbol.for("Symbol.dispose")]() { + ptr.is_persistent = false; + ptr.delete() + }, + dispose() { + ptr.is_persistent = false; + ptr.delete() + } + } } @@ -199,77 +210,77 @@ export async function transformAsync(depend export function map(iterable: Iterable, mapFn: (value: MaybeObjectRef, index: number, array: Iterable) => U, options?: {outType: O}): O extends "array" ? U[] : Map { - let mapped:U[]|Map - - // live map - if (Datex.Ref.isRef(iterable)) { - - // return map - if (options?.outType == "map") { - mapped = $$(new Map()) - - const iterableHandler = new IterableHandler(iterable, { - map: (v,k)=>{ - return mapFn(v,k,iterable) - }, - onEntryRemoved: (v,k) => { - (mapped as Map).delete(k) - }, - onNewEntry: (v,k) => (mapped as Map).set(k,v), - onEmpty: () => (mapped as Map).clear() - }) - // reverse transform binding - Datex.Pointer.bindDisposable(mapped, iterableHandler) - } - - // return array - else { - mapped = $$([]) - - // no gaps in a set -> array splice required - const spliceArray = iterable instanceof Set; - - const iterableHandler = new IterableHandler(iterable, { - map: (v,k)=>{ - return mapFn(v,k,iterable) - }, - onEntryRemoved: (v,k) => { - if (spliceArray) (mapped as U[]).splice(k, 1); - else delete (mapped as U[])[k]; - }, - onNewEntry: (v,k) => { - (mapped as U[])[k] = v - }, - onEmpty: () => { - (mapped as U[]).length = 0 - } - }) - // reverse transform binding - Datex.Pointer.bindDisposable(mapped, iterableHandler) - } - - } - - // static map - else { - if (options?.outType == "map") { - mapped = new Map() - let i = 0; - for (const val of iterable) { - mapped.set(i, mapFn(val, i++, iterable)) - } - } - else { - mapped = [] - let i = 0; - for (const val of iterable) { - mapped.push(mapFn(val, i++, iterable)) - } - } - - } - - return mapped as any; + let mapped:U[]|Map + + // live map + if (Datex.Ref.isRef(iterable)) { + + // return map + if (options?.outType == "map") { + mapped = $$(new Map()) + + const iterableHandler = new IterableHandler(iterable, { + map: (v,k)=>{ + return mapFn(v,k,iterable) + }, + onEntryRemoved: (v,k) => { + (mapped as Map).delete(k) + }, + onNewEntry: (v,k) => (mapped as Map).set(k,v), + onEmpty: () => (mapped as Map).clear() + }) + // reverse transform binding + Datex.Pointer.bindDisposable(mapped, iterableHandler) + } + + // return array + else { + mapped = $$([]) + + // no gaps in a set -> array splice required + const spliceArray = iterable instanceof Set; + + const iterableHandler = new IterableHandler(iterable, { + map: (v,k)=>{ + return mapFn(v,k,iterable) + }, + onEntryRemoved: (v,k) => { + if (spliceArray) (mapped as U[]).splice(k, 1); + else delete (mapped as U[])[k]; + }, + onNewEntry: (v,k) => { + (mapped as U[])[k] = v + }, + onEmpty: () => { + (mapped as U[]).length = 0 + } + }) + // reverse transform binding + Datex.Pointer.bindDisposable(mapped, iterableHandler) + } + + } + + // static map + else { + if (options?.outType == "map") { + mapped = new Map() + let i = 0; + for (const val of iterable) { + mapped.set(i, mapFn(val, i++, iterable)) + } + } + else { + mapped = [] + let i = 0; + for (const val of iterable) { + mapped.push(mapFn(val, i++, iterable)) + } + } + + } + + return mapped as any; } @@ -316,8 +327,8 @@ export function map(iterable: Iterable< */ export function toggle(value:RefLike, if_true:T, if_false:T): MinimalJSRef { return transform([value], v=>v?if_true:if_false, - // dx transforms not working correctly (with uix) - /*` + // dx transforms not working correctly (with uix) + /*` always ( if (${Runtime.valueToDatexString(value)}) (${Runtime.valueToDatexString(if_true)}) else (${Runtime.valueToDatexString(if_false)}) @@ -338,8 +349,8 @@ export const select = toggle; */ export function equals(a:RefLike|T, b: RefLike|V): Datex.Pointer { return transform([a, b], (a,b) => Datex.Ref.collapseValue(a, true, true) === Datex.Ref.collapseValue(b, true, true), - // dx transforms not working correctly (with uix) - /*`always (${Runtime.valueToDatexString(a)} === ${Runtime.valueToDatexString(b)})`*/) as any; + // dx transforms not working correctly (with uix) + /*`always (${Runtime.valueToDatexString(a)} === ${Runtime.valueToDatexString(b)})`*/) as any; }