简体   繁体   中英

Construct function from domain and image arrays in Javascript

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM