From bb6e742f5f1571bcf195d85a12f90de06c538ebf Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 20 May 2025 16:53:04 +0200 Subject: [PATCH] simplify suggestions ordering --- src/App.tsx | 10 +++++--- src/CallBlock.tsx | 58 ++++++++++++++++++++++++------------------- src/ExprBlock.tsx | 47 +++++++++++------------------------ src/InputBlock.tsx | 37 +++++++++++++-------------- src/LambdaBlock.tsx | 12 ++++----- src/LetInBlock.tsx | 24 +++++++++--------- src/configurations.ts | 4 ++- src/eval.ts | 27 +++++++++++++------- 8 files changed, 109 insertions(+), 110 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2f620e8..c571039 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,10 @@ import { useEffect, useState } from 'react'; import './App.css'; import { ExprBlock, type ExprBlockState } from './ExprBlock'; import { GlobalContext } from './GlobalContext'; -import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations"; +import { biggerExample, emptySet, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations"; import { actionShortcuts } from './actions'; +import { scoreResolved, type ResolvedType } from './eval'; +import type { InputBlockState } from './InputBlock'; const examples: [string, ExprBlockState][] = [ @@ -16,6 +18,7 @@ const examples: [string, ExprBlockState][] = [ ["higher order 2" , higherOrder2Params ], ["push Bool" , pushBool ], ["inc" , inc ], + ["empty set" , emptySet ], ]; type AppState = { @@ -163,9 +166,8 @@ export function App() { state={appState.history.at(-1)!} setState={pushHistory} onCancel={() => {}} - suggestionPriority={() => { - // console.log('suggestionPriority of App, always 0'); - return 0; + score={(resolved: ResolvedType) => { + return scoreResolved(resolved, () => 0); }} /> diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index 05f868b..ba01e9a 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -1,15 +1,14 @@ import { useContext } from "react"; -import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock"; import { EnvContext } from "./EnvContext"; -import { addFocusRightMost, evalCallBlock2, evalEditorBlock, removeFocus, scoreResolved, type ResolvedType } from "./eval"; +import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval"; +import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock"; import { GlobalContext } from "./GlobalContext"; import { Value } from "./Value"; -import "./CallBlock.css"; -import { initialEditorState } from "./configurations"; -import { CallContext } from "./CallContext"; import { getActions } from "./actions"; +import "./CallBlock.css"; +import { CallContext } from "./CallContext"; export interface CallBlockState { kind: "call"; @@ -17,48 +16,61 @@ export interface CallBlockState { input: ExprBlockState; } -interface CallBlockProps< +export interface CallBlockProps< FnState=ExprBlockState, InputState=ExprBlockState, > extends State2Props {} -function nestedFnProperties({state, setState, suggestionPriority}: CallBlockProps, env) { +function nestedFnProperties({state, setState, score}: CallBlockProps, env) { const setFn = (callback: SetStateFn) => { setState(state => ({...state, fn: callback(state.fn)})); } const onFnCancel = () => { setState(state => state.input); // we become our input } - const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority( + const scoreFn = (fnSuggestion: ResolvedType) => { + return computePriority( fnSuggestion, - evalEditorBlock(state.input, env)[0], - suggestionPriority, + evalExprBlock(state.input, env)[0], + score, env, ); - return {state: state.fn, setState: setFn, onCancel: onFnCancel, suggestionPriority: fnSuggestionPriority}; + }; + return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn}; } -function nestedInputProperties({state, setState, suggestionPriority}: CallBlockProps, env) { +function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) { const setInput = (callback: SetStateFn) => { setState(state => ({...state, input: callback(state.input)})); } const onInputCancel = () => { setState(state => addFocusRightMost(state.fn)); // we become our function } - const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority( - evalEditorBlock(state.fn, env)[0], // fn *may* be set - inputSuggestion, // suggestions will be for input - suggestionPriority, // priority function we get from parent block - env, - ) - return {state: state.input, setState: setInput, onCancel: onInputCancel, suggestionPriority: inputSuggestionPriorirty}; + const scoreInput = (inputSuggestion: ResolvedType) => { + return computePriority( + evalExprBlock(state.fn, env)[0], // fn *may* be set + inputSuggestion, // suggestions will be for input + score, // priority function we get from parent block + env, + ); + } + return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput}; +} + +function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { + // dirty, but works: + const [fnR, env2] = recomputeTypeVarsForEnv('', fn, env); + const [inR, env3] = recomputeTypeVarsForEnv('', input, env2); + const [resolved] = evalCallBlock2(fnR, inR, env3); + const score = scoreResolved(resolved, outPriority); + return score; } export function CallBlock(props: CallBlockProps) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const addParam = getActions(globalContext, props.setState).c; - const [resolved] = evalEditorBlock(props.state, env); + const [resolved] = evalExprBlock(props.state, env); return @@ -80,12 +92,6 @@ export function CallBlock(props: CallBlockProps) { ; } -function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { - const [resolved] = evalCallBlock2(fn, input, env); - const score = scoreResolved(resolved, outPriority); - return score; -} - function FunctionHeader(props) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); diff --git a/src/ExprBlock.tsx b/src/ExprBlock.tsx index f10e158..e702b7f 100644 --- a/src/ExprBlock.tsx +++ b/src/ExprBlock.tsx @@ -2,14 +2,14 @@ import { useContext } from "react"; import { getType } from "dope2"; -import { CallBlock, type CallBlockState } from "./CallBlock"; +import { CallBlock, type CallBlockProps, type CallBlockState } from "./CallBlock"; import { EnvContext } from "./EnvContext"; import { GlobalContext } from "./GlobalContext"; -import { InputBlock, type InputBlockState } from "./InputBlock"; -import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock"; -import { LetInBlock, type LetInBlockState } from "./LetInBlock"; +import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock"; +import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock"; +import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock"; import { Type } from "./Type"; -import { evalEditorBlock, type ResolvedType } from "./eval"; +import { evalExprBlock, type ResolvedType } from "./eval"; import "./ExprBlock.css"; import { Input } from "./Input"; @@ -26,48 +26,31 @@ export type SetStateFn = (state: InTy export interface State2Props { state: InType; setState: (callback: SetStateFn) => void; - suggestionPriority: (suggestion: ResolvedType) => number; + score: (suggestion: ResolvedType) => number; } interface ExprBlockProps extends State2Props { onCancel: () => void; } -export function ExprBlock({state, setState, suggestionPriority, onCancel}: ExprBlockProps) { +export function ExprBlock(props: ExprBlockProps) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const renderBlock = { - input: () => ExprBlockState)=>void} - suggestionPriority={suggestionPriority} - onCancel={onCancel} - />, - call: () => ExprBlockState)=>void} - suggestionPriority={suggestionPriority} - />, - let: () => ExprBlockState)=>void} - suggestionPriority={suggestionPriority} - />, - lambda: () => ExprBlockState)=>void} - suggestionPriority={suggestionPriority} - />, + input: () => , + call: () => , + let: () => , + lambda: () => , }; - const [resolved] = evalEditorBlock(state, env); - const actions = getActions(globalContext, setState); + const [resolved] = evalExprBlock(props.state, env); + const actions = getActions(globalContext, props.setState); const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) => [shortcut, (e) => { e.preventDefault(); action(); }])) return - {renderBlock[state.kind]()} + {renderBlock[props.state.kind]()}
 :: 
@@ -76,7 +59,7 @@ export function ExprBlock({state, setState, suggestionPriority, onCancel}: ExprB text="" suggestion="" onEnter={() => {}} - onCancel={onCancel} + onCancel={props.onCancel} onTextChange={() => {}} extraHandlers={extraHandlers} /> diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index e6ecd8d..d54513d 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -3,13 +3,12 @@ import { memo, useContext, useEffect, useMemo, useRef, useState } from "react"; import { getType, prettyT, trie } from "dope2"; import { EnvContext } from "./EnvContext"; -import type { Dynamic, ResolvedType } from "./eval"; +import type { Environment, ResolvedType } from "./eval"; import "./InputBlock.css"; import { Type } from "./Type"; import type { ExprBlockState, State2Props } from "./ExprBlock"; import { attemptParseLiteral } from "./eval"; import { Input } from "./Input"; -import { initialEditorState } from "./configurations"; import { CallContext } from "./CallContext"; import { getActions } from "./actions"; import { GlobalContext } from "./GlobalContext"; @@ -33,37 +32,35 @@ export interface InputBlockState { focus: boolean } -export type SuggestionType = ['literal'|'name', string, Dynamic]; +export type SuggestionType = ["literal"|"name", string, ResolvedType]; export type PrioritizedSuggestionType = [number, ...SuggestionType]; -interface InputBlockProps extends State2Props { +export interface InputBlockProps extends State2Props { onCancel: () => void; } -const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) => number): PrioritizedSuggestionType[] => { +const computeSuggestions = ( + text: string, + env: Environment, + score: InputBlockProps['score'], +): PrioritizedSuggestionType[] => { const literals = attemptParseLiteral(text, env); - const ls: SuggestionType[] = [ // literals - ... literals.map((lit) => ["literal", text, lit]), + ... literals.map((resolved) => ["literal", text, resolved]), // names ... trie.suggest(env.names)(text)(Infinity) - .map(([name,type]) => [ - "name", - name, { - ...type, - substitutions: type.substitutions || new Map(), - kind: type.kind || "value", - }]), + .map(([name, resolved]) => ["name", name, resolved]), ] // return []; // <-- uncomment to disable suggestions (useful for debugging) return ls - .map((suggestion) => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) + .map((suggestion: SuggestionType) => + [score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) .sort(([priorityA], [priorityB]) => priorityB - priorityA) } -export function InputBlock({ state, setState, suggestionPriority, onCancel }: InputBlockProps) { +export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) { const {text, focus} = state; const globalContext = useContext(GlobalContext); const env = useContext(EnvContext); @@ -72,7 +69,7 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In const [i, setI] = useState(0); // selected suggestion idx const singleSuggestion = trie.growPrefix(env.names)(text); - const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]); + const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]); useEffect(() => { @@ -176,20 +173,20 @@ interface SuggestionProps { suggestion: PrioritizedSuggestionType; } -function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, name, dynamic] }: SuggestionProps) { +function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, text, resolved] }: SuggestionProps) { const onMouseEnter = j => () => { setI(j); }; const onMouseDown = j => () => { setI(j); - onSelect([priority, kind, name, dynamic]); + onSelect(); }; return
- ({priority}) ({kind}) {name} :: + ({priority}) ({kind}) {text} ::
} diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index d8a2a7a..f5ddbab 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -4,7 +4,7 @@ import { eqType, getSymbol, reduceUnification } from "dope2"; import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock"; import { EnvContext } from "./EnvContext"; -import { evalEditorBlock, makeInnerEnv, makeTypeVar } from "./eval"; +import { evalExprBlock, makeInnerEnv, makeTypeVar } from "./eval"; import "./LambdaBlock.css"; import { Type } from "./Type"; @@ -17,14 +17,14 @@ export interface LambdaBlockState { expr: ExprBlockState; } -interface LambdaBlockProps< +export interface LambdaBlockProps< FnState=ExprBlockState, InputState=ExprBlockState, > extends State2Props { } -export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) { +export function LambdaBlock({state, setState, score}: LambdaBlockProps) { const env = useContext(EnvContext); const setParamName = paramName => setState(state => ({ @@ -38,7 +38,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName); - const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv); + const [exprResolved] = evalExprBlock(state.expr, staticInnerEnv); const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType; @@ -76,9 +76,9 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr state={state.expr} setState={setExpr} onCancel={() => setState(state => state.expr)} - suggestionPriority={(s) => { + score={(s) => { // console.log('suggestionPriority of lambdaInner... just passing through'); - return suggestionPriority(s); + return score(s); }} /> diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index 491471c..6ab4cb1 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -2,7 +2,7 @@ import { useContext } from "react"; import { ExprBlock, type ExprBlockState } from "./ExprBlock"; import { EnvContext } from "./EnvContext"; -import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval"; +import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval"; import { type State2Props } from "./ExprBlock"; import { GlobalContext } from "./GlobalContext"; @@ -17,7 +17,7 @@ export interface LetInBlockState { inner: ExprBlockState; } -interface LetInBlockProps extends State2Props { +export interface LetInBlockProps extends State2Props { } export function LetInBlock(props: LetInBlockProps) { @@ -31,7 +31,7 @@ export function LetInBlock(props: LetInBlockProps) {
} -function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPriority}) { +function DeclColumns({state: {name, value, inner, focus}, setState, score}) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); @@ -40,11 +40,11 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr const valueSuggestionPriority = (suggestion: ResolvedType) => { const innerEnv = makeInnerEnv(env, name, suggestion); - const [resolved] = evalEditorBlock(inner, innerEnv); - return scoreResolved(resolved, suggestionPriority); + const [resolved] = evalExprBlock(inner, innerEnv); + return scoreResolved(resolved, score); }; - const [valueResolved] = evalEditorBlock(value, env); + const [valueResolved] = evalExprBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved); return <> @@ -65,7 +65,7 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr setState(state => state.inner)} // keep inner /> @@ -75,18 +75,18 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr } ; } -function InnerMost({state, setState, suggestionPriority}) { +function InnerMost({state, setState, score}) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); - const [valueResolved] = evalEditorBlock(state.value, env); + const [valueResolved] = evalExprBlock(state.value, env); const innerEnv = makeInnerEnv(env, state.name, valueResolved); const onCancel = () => setState(state => state.value); if (state.inner.kind === "let" && globalContext?.syntacticSugar) { @@ -94,7 +94,7 @@ function InnerMost({state, setState, suggestionPriority}) { ; } @@ -103,7 +103,7 @@ function InnerMost({state, setState, suggestionPriority}) { diff --git a/src/configurations.ts b/src/configurations.ts index 558fcb2..3abb256 100644 --- a/src/configurations.ts +++ b/src/configurations.ts @@ -137,4 +137,6 @@ export const higherOrder2Params: ExprBlockState = {"kind":"let","focus":false,"i export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}}; -export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}}; \ No newline at end of file +export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}}; + +export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"text"},"focus":true}}; \ No newline at end of file diff --git a/src/eval.ts b/src/eval.ts index 33adff0..4b2da89 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -4,7 +4,7 @@ import type { ExprBlockState } from "./ExprBlock"; import type { InputValueType } from "./InputBlock"; const IS_DEV = (import.meta.env.MODE === "development"); -const VERBOSE = false; +const VERBOSE = true; export interface Environment { names: any; @@ -46,7 +46,7 @@ export type ResolvedType = Dynamic | DeepError | Unknown; class NotFoundError extends Error {} -export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { +export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { if (s.kind === "input") { return evalInputBlock(s.text, s.value, env); } @@ -88,8 +88,8 @@ export function evalInputBlock(text: string, value: InputValueType, env: Environ } export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] { - const [fnResolved, env2] = evalEditorBlock(fn, env); - const [inputResolved, env3] = evalEditorBlock(input, env2); + const [fnResolved, env2] = evalExprBlock(fn, env); + const [inputResolved, env3] = evalExprBlock(input, env2); if (VERBOSE) { console.log('==== evalCallBlock ===='); console.log('env :', env); @@ -103,6 +103,9 @@ export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: En } export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { + if (occurring(fnResolved.t).intersection(occurring(inputResolved.t)).size > 0) { + throw new Error(`Precondition failed: function (${prettyT(fnResolved.t)}) and its input (${prettyT(inputResolved.t)}) have overlapping typevars!`); + } if (getSymbol(fnResolved.t) !== symbolFunction) { // not a function... if (isTypeVar(fnResolved.t)) { @@ -143,7 +146,7 @@ const inverseUnification = (uni, inverse) => { ); } -function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] { +export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] { const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], env.nextFreeTypeVar); const newResolved: ResolvedType = { ...resolved, @@ -281,9 +284,9 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e } export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] { - const [valueResolved] = evalEditorBlock(value, env); + const [valueResolved] = evalExprBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved); - return evalEditorBlock(inner, innerEnv); + return evalExprBlock(inner, innerEnv); } const prettyRU = (rUni: Map) => { @@ -301,7 +304,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En console.log('===================================') } - const [exprResolved] = evalEditorBlock(expr, staticInnerEnv); + const [exprResolved] = evalExprBlock(expr, staticInnerEnv); const lambdaT = fnType(_ => paramType)(_ => exprResolved.t); // This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function: const reduced = reduceUnification(exprResolved.unification); @@ -331,7 +334,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En t: paramTypeSubstituted, unification: new Map(), }); - const [result] = evalEditorBlock(expr, innerEnv); + const [result] = evalExprBlock(expr, innerEnv); if (result.kind === "value") { return result.i; } @@ -410,17 +413,23 @@ export function attemptParseLiteral(text: string, env: Environment): Dynamic[] { export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) { const bias = outPriority(resolved); + + console.log('scoreResolved...', resolved); if (resolved.kind === "value") { + console.log('best:', 2, bias); return 2 + bias; } else if (resolved.kind === "unknown") { + console.log('ok:', 1, bias); return 1 + bias; } if (resolved.e instanceof UnifyError) { + console.log('bad:', -1, bias); return -1 + bias; // parameter doesn't match } else { + console.log('worst:', -2, bias); return -2 + bias; // even worse: fn is not a function! } }