I've come across a pretty basic task for functional programming, I can't solve in javascript:
I'm trying wo write a (higher order) function that takes two arrays domain
and image
and returns a function. The returned function should return the n
-th element of the image
when given that n
-th element of the domain:
Example:
let f = makeFunction([a,b,c],[d,e,f]);
f(a) //returns d
f(b) //returns e
f(c) //returns f
f(x) //any other value returns undefined
First I was trying to solve this problem like it's commonly done in functional programming. (Example in Racket)
(define makeFunction
(lambda (domain image)
(lambda (x)
(cond
[(= x (first domain)) (first image)
[else ((makeFunction (rest domain) (rest image) x)]
)
)
)
)
However something like this isn't possible in js, since the Function constructor doesn't create a closure ( see here ). Therefor my second attempt was to simply stringify the arrays and include them in the function definition (I also included input checking):
function makeFunction(domain, image){
if(new Set(domain).size==domain.length&&domain.length==image.length){
return new Function("x",`return ${JSON.stringify(image)}[${JSON.stringify(domain)}.indexOf(x)]`);
}else{
throw new Error("The lists don't allow the creation of a function, because they either don't have the same length or the domain isn't unique.");
}
}
This function works as long as primitive data types are the only ones the domain
contains. But as soon as it includes objects, or even cyclic data, I've no idea how to make this function work...
//works as expected:
let f = makeFunction([1,2,3,4,"foo"],[4,3,2,1,"bar"]);
//always returns undefined:
let g = makeFunction([{foo:"bar"},{bar:"foo"}],[{bar:"foo"},{foo:"bar"}]);
//returns an error, since JSON.stringify doesn't work on cyclic data:
let cyclicData = {};
cyclicData.self = cyclicData;
let h = makeFunction([cyclicData],["foo"]);
Hopefully you can help me with this one :)
You noticed the pain you experience with new Function
but you could've constructed a function using the function
keyword like you did for makeFunction
.
The following JavaScript function literal syntaxes below create lexicographic closures. [1] We'll be using the last syntax because it's closest to Racket
makeFunction param1 param2 ... statement1 statement2 ...
makeFunctionparam1 param2 ... statement1 statement2 ...
makeFunction param1 param2 ... statement1 statement2 ...
makeFunction param1 param2 ... expression
Your Racket program translates directly into JavaScript so long as we provide the first
and rest
functions. Note that isEmpty
was added so that makeFunction ([], [])
still works
const isEmpty = (xs = []) => xs.length === 0 const first = (xs = []) => xs [0] const rest = (xs = []) => xs.slice (1) const makeFunction = (domain = [], image = []) => x => isEmpty (domain) ? undefined : x === first (domain) ? first (image) : makeFunction (rest (domain), rest (image)) (x) const f = makeFunction ([ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ]) console.log (f ('a')) // a console.log (f ('b')) // b console.log (f ('c')) // c console.log (f ('x')) // undefined
Array destructuring assignment and default arguments allow us to see another way we could write our functions
const Empty =
Symbol ()
const isEmpty = ([ x = Empty, ...xs ]) =>
x === Empty
const first = ([ x = Empty, ...xs ]) =>
x
const rest = ([ x = Empty, ...xs ]) =>
xs
Or we can skip creating intermediate functions first
and rest
and use destructuring syntaxes directly in makeFunction
const Empty = Symbol () const makeFunction = ([ key = Empty, ...keys ], [ value = Empty, ...values ]) => x => key === Empty ? undefined : x === key ? value : makeFunction (keys, values) (x) const f = makeFunction ([ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ]) console.log (f ('a')) // a console.log (f ('b')) // b console.log (f ('c')) // c console.log (f ('x')) // undefined
As @user3297291 points out, you should be looking at Map
as it can look up a key in logarithmic time – compared to linear (slower) time used by makeFunction
[1] Functions using arrow ( =>
) syntax have also have a lexicographical this
value
You can return a function that returns a function, this will create a closure for you.
Example:
makeFunction = (domain, image) => ((element) => (image[domain.findIndex(d => d === element)]))
f = makeFunction([1,2,3,4, 'foo'], [4,3,2,1, 'bar']);
f(1) // 4
f(2) // 3
...
Have you looked at using javascript's Map
? Its get
method is almost what you're trying to implement:
const makeFunction = (domain = [], image = []) => { const m = new Map(pairs(domain, image)); return m.get.bind(m); }; let a = { foo: "bar" }; let f = makeFunction([1,2,3,4,"foo"],[4,3,2,1,"bar"]); let g = makeFunction([ a ]); let h = makeFunction([ a ], [ 1 ]); console.log(f(1)); console.log(g(a)); console.log(h(a)); // This still won't work though: console.log(h({ foo: "bar" })); function pairs([x, ...xs], [y, ...ys], ps = []) { return x && y ? pairs(xs, ys, [...ps, [x, y]]) : ps; };
Its keys however are still checked "by reference". If you want { foo: "bar" } == { foo: "bar" }
you'll have to write some custom equality comparer...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.