Due Date: July 5 before midnight;
Max Points: 100
Important Reminder: As per the course Academic Honesty Statement, cheating of any kind will minimally result in your letter grade for the entire course being reduced by one level.
To be turned in via the submission link on brightspace.
Please remember to justify all answers.
You are encouraged to use the web or the library but are required to cite any external sources used in your answers.
It may be the case that some questions cannot be answered as requested.
The solution to Project 1 implements error recovery using an internal undo facility. Discuss how you could extend this internal facility to an externally visible undo-redo facility. 10-points
The current undo facility grabs hold of the states of a cell in a
mapping between cell-id's and the corresponding cells before
making any assignment to it while executing a single update to
the spreadsheet. Generalizing this to a undo/redo facility would
be fairly straight-forward using two stacks: an undoStack
and a
redoStack
:
Reify the list of undo cell states into some kind of Undo
object.
As each spreadsheet update is processed, push its Undo
object
onto undoStack
.
To apply an external undo command, pop the last Undo
object
off undoStack
and apply its cell updates. However, before
each update is applied to a cell, save the state of the cell
in a new Undo
object and push the new Undo
onto the
redoStack
.
To apply an external redo command, the redoStack
should be
non-empty. In that case, pop the last Undo
object off
redoStack
and apply its cell updates. However, before each
update is applied to a cell, save the state of the cell in a
new Undo
object and push new Undo
onto the undoStack
.
Before any command other than an undo or redo, clear
redoStack
.
Note that in (b), it is probably a good idea to put a limit U
on
the size of undoStack
; i.e. only the last U
commands may be
undone; any attempt to undo more thant U
commands would results
in some kind of "no more undos" error.
Many of the functions passed as parameters to functions in the
JavaScript standard library provide the invoking object as a
parameter. For example, when
Array:map() is used as in array.map(f)
, the third
parameter passed to f()
will be array
. Give an example of a
situation where this behavior is useful. 5-points
One situation where the third argument is useful is when the
invoking object is an intermediate value in a chain of function
applications. For example, the following function applies
a function f()
to elements of array a
and then reduces
the mapped array to the sum of each mapped element multiplied
by the next mapped element:
where \(n\; \mathtt{==}\; \mathtt{arr}.\mathtt{length}\).
function fn<T>(f: (_ : T) => number, arr: T[]) { const n = arr.length; return arr. map(x => f(x)). reduce((acc, x, i, mapped) => { const next = (i === n - 1) ? 1 : mapped[i + 1]; return acc + x*next; }, 0); }
Note that the entire result of the first map()
is an
intermediate value which is used by the reduce()
(in the 4th
argument arr
to reduce
's function argument) to access the
value at the next index. Without this additional argument, it would be
necessary to explicitly store the result of the first map()
in a
variable.
Assume that you are a evil spammer. Discuss how would you use
JavaScript regular expressions within a program to harvest email
addresses from a document. Your program should cope with anti-spam
workarounds deployed by users like spelling out email addresses
using sequences like user at example dot com
. Your answer
should deal only with the use of regex's to extract the email
addresses; you may assume that the contents of the document are
available as a string. 10-points
A regex like the following provides a good starting point (whitespaces added for legibility):
/[^\@]+\@[^\.]+(\.[^\.]+)+ | \w+\s+at\s+\w+(\s+dot\w+)+ | [^\@]+\@(gmail|yahoo|...) | \w+\s+at\s+(gmail|yahoo|...)/i
The first alternative allows traditionally written email addresses
user@
domain where the domain contains at least one period.
The second alternative allows spelled out email addresses of the
form user at
domain where the domain contains at least
one occurrence of the word dot
.
The third alternative allows email addresses to be written as
simply user@
domain where the domain is simply the name
of popular email providers like gmail
or yahoo
.
The fourth alternative allows email addresses to be written as
simply user at
domain where the domain is simply the name
of popular email providers like gmail
or yahoo
.
Flag i
make the regex case-insensitive.
Discuss how you would implement a random()
function in
JavaScript (without using any library random functions). The
details of the random number generation algorithm are unimportant;
what is required is the setting up of the function so that
subsequent calls usually return different results. 5-points
The essential idea would be to retain a state like seed
but to
hide it within a closure. So something like the following IIFE:
const random = (function() { let seed = ...; return rand() { ... } })();
Assuming no earlier variable declarations, the following
JavaScript code will output 4
when run in non-strict mode.
x = 1; obj1 = { x: 2, f: function() { return this.x; } } obj2 = { x: 3, f: function() { return this.x; } } f = obj1.f.bind(obj2); console.log(obj1.f() - obj2.f() + obj2.f.call(obj1) + f());
Explain why this is so. 10-points
The call to obj1.f()
has this
set to obj1
; hence it will
return obj1.x
which is 2. The call to obj2.f() has this
set to
obj2
; hence it will return obj2.x
which is 3. The call to
obj2.f.call(obj1)
has this
set to obj1
; hence it will return
obj1.x
which is 2. Finally, the f()
call has this
bound to
obj2
, so it will return obj2.x
which is 3. Hence the value
printed will be 2 - 3 + 2 + 3
which is 4
.
Novice programmer Ben Novitia asks you for help in identifying the problem in the following nodejs code:
... const DIR = `${process.env.HOME}/my-stuff`; function readFile(filename) { fs.readFile(`${DIR}/${filename}`, function(err, result) { return result; }); } const contents = readFile('test.data'); console.log(contents);
What do you tell Ben? 5-points
fs.readFile()
is asynchronous. Hence the return result
statement
in its handler does not serve to return a value from readFile()
.
The only way to log the contents is to move the console.log()
statement into the handler for fs.readfile()
.
Multiple objects obj1
and obj2
have a common interface \(I\)
(i.e. the same set of methods) but all the methods in obj1
are async
whereas the methods in obj2
are synchronous.
There is a function f(obj1)
which is set up to expect a
read-only obj1
argument; so the code for f()
assumes that
obj1
methods always return promises. Hence calling f(obj2)
would not work because even though obj2
has the same methods
as obj1
, those obj2
methods do not return promises.
How would you set things up so that f(obj2')
can be successfully
called, where obj2'
is derived from obj2
.
You answer should not assume anything about \(I\), obj1
, obj2
or f()
; in particular, you should not assume that you have
access to the code for obj1
, obj2
or f()
. 15-points
The key is to set up asynchronous functions in obj2'
which wrap
the synchronous functions for obj2
.
So something like the following pseudo-code:
const obj2' = Object.assign({}, obj2); for (const [k, v] of Object.entries(obj2)) { if (typeof v === 'function') { obj2'[k] = async function(...args) { return v(...args); }; } } f(obj2');
The following is an attempt to log the connection information for a mongo database:
const MONGO_URL = 'mongodb://localhost:27017/data'; ... function getConnection(url=MONGO_URL) { const result = mongo.connect(url); return result; } const conn = getConnection(); console.log(conn);
Assuming that mongo
contains a MongoDB client object, what
is wrong with the above code? 5-points
mongo.connect()
is asynchronous. Hence the return result
statement
in does not serve to return the connection. Instead it will
return a Promise
for the connection; to get the connection,
it is necessary to either chain off the result using a
.then()
or await
the results of the connect()
call.
A web application needs to make asynchronous calls to independent web services \(s_1\) and \(s_2\), followed by an asynchronous call to a web service \(s_3\) which combines the results from \(s_1\) and \(s_2\) into a single result.
How would you set this up so as to maximize performance.
Assume that web service \(s_1\) is not particularly reliable and that an alternate web service \(s_1'\) is available which is functionally equivalent to \(s_1\). How would you modify your answer to the previous question to make the application more robust by using the results from either \(s_1\) or \(s_1'\) depending on which of \(s_1\) or \(s_1'\) responds? 15-points
The answers follow:
Since the calls to \(s_1\) and \(s_2\) are independent, they
should be run in parallel. Since they are asynchronous, they
can be set up to return promises and we run them in parallel
using something like Promise.all([s_1(), s_2()])
. Then the
result of those parallel calls can be provided sequentially to
service \(s_3\).
const results = await Promise.all([s_1(), s_2()]); return await s_3(...results);
Use Promise.any()
to run \(s_1\) or \(s_1'\) in parallel
with the first one which succeeds determining the result.
So something like:
const s1Promise = Promise.any([s_1(), s_1'()]); const results = await Promise.all([s1Promise, s_2()]); return await s_3(...results);
Note that Promise.any()
is preferable to Promise.race()
as
Promise.race()
will reject if the first settled promise rejects,
whereas Promise.any()
will reject only if all promise arguments
reject.
Write a function fib(n)
which can be used as in
for (f of fib(6)) console.log(f);
to print out successive Fibonacci numbers 1, 1, 2, 3, 5, 8. The
return value of fib()
is not allowed to be any kind of
collection. 5-points
Use generators:
function* fib(n) { if (n > 0) yield 1; if (n > 1) yield 1; let n1 = 1, n2 = 1; for (let i = 3; i <= n; i++) { const f = n1 + n2; n1 = n2; n2 = f; yield f; } }
Discuss the validity of the following statements. What is more important than whether you ultimately classify the statement as true or false is your justification for arriving at your conclusion. 15-points
this
for a fat-arrow function can be changed using call()
.
The await
keyword can only be used within a function
declared async
.
A JavaScript event handler will run soon after the event is received.
A function declared async
can be called without using an
await
.
A function called using await
must have been declared async
.
The answers follow:
For a fat-arrow function, this
is always the value which was
captured lexically within the scope in which the fat-arrow function
was defined and it is not possible to change it using call()
.
So the statement is false.
This statement was true until recently. However, in the past
couple of years, JavaScript has started allowing a top-level
await
at the top level, outside of any functions. Hence
the statement is false.
The event handler will run after the event is received and the currently running code has completed (because of JavaScript's run-to-completion semantics). Hence the statement is false.
A function declared async
can indeed be called without using
an await
as long as the caller is prepared to handle the
returned Promise
. Hence the statement is true.
A function which simply returns a Promise
but not declared
async
can be called using await
. In fact, even a normal
synchronous function can also be called using await
. Hence
the statement is false.