Functional programming style pattern matching in JavaScript - javascript

I'm writing compiler from kind of functional language to JS. Compiler would run in browser. I need to implement pattern matching mechanics in JS, because original language have one. I've found Sparkler and Z. Sparkler can't be executed in browser as far as I know and Z doesn't have all possibilities I need.
So my language have semantics like this:
count x [] <- 0
count x [ x : xs ] <- 1 + count x xs
count x [ y : xs ] <- count x xs
This is what happens in this snippet:
First line is definition of a function, which takes two parameters: some variable x and empty list, and returns zero.
Second line is definition of a function, which also takes two parameters: some variable x and list, which starts with x, and returns 1 + count(x, xs)
Fot this example I want to generate code like this:
const count = (x, list) => {
match(x, list) => (
(x, []) => {...}
(x, [ x : xs ]) => {...}
(x, [ y : xs ]) => {...}
)
}
How properly unfold this kind of pattern matching into ifs and ors?

General case
There is a proposal for Pattern Matching in ECMAScript, but as of 2018 it's in a very early stage.
Currently, the Implementations section only lists:
Babel Plugin
Sweet.js macro (NOTE: this isn't based on the proposal, this proposal is partially based on it!)
List case
Use destructuring assignment, like:
const count = list => {
const [x, ...xs] = list;
if (x === undefined) {
return 0;
} else if (xs === undefined) {
return 1;
} else {
return 1 + count(xs);
}
}

Using ex-patterns, you could write your example as follows. You need to use the placeholder names that come with the package (_, A, B, C, ... Z) but you can rename matched variables in the callback function with destructuring (an object containing all named matches is passed in as the first argument to the callback function).
import { when, then, Y, _, tail, end } from 'ex-patterns';
const count = list => (
when(list)
([], then(() => 0)) // match empty array
([_], then(() => 1)) // match array with (any) 1 element
([_, tail(Y)], then(({ Y: xs }) => 1 + count(xs))) // match array and capture tail
(end);
);
This also covers the case where list = [undefined, 'foo', 'bar'], which I don't think would be covered by the accepted answer.
To make the code more efficient, you can call count with an Immutable.js List instead of an array (no changes required). In that case, the tail portion of the array doesn't need to be sliced and copied into a new array on every loop.
As with the packages you mentioned, this doesn't run in the browser natively, but I guess that's not a major obstacle with modern bundling tools.
Here are the docs: https://moritzploss.github.io/ex-patterns/
Disclaimer: I'm the author of ex-patterns :)

I had a need for pattern matching and made something that works for me.
const count = patroon(
[_], ([, ...xs]) => 1 + count(xs),
[], 0
)
count([0,1,2,3])
4
See readme for more usage examples.
https://github.com/bas080/patroon
https://www.npmjs.com/package/patroon

Related

I keep getting this question wrong. Counting Bits using javascript

This is the question.
Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1's in the binary representation of i.
https://leetcode.com/problems/counting-bits/
And this is my solution below.
If the input is 2, expected output should be [0,1,1] but I keep getting [0,2,2]. Why is that???
var countBits = function(n) {
//n=3. [0,1,2,3]
var arr=[0];
for (var i=1; i<=n; i++){
var sum = 0;
var value = i;
while(value != 0){
sum += value%2;
value /= 2;
}
arr.push(sum);
}
return arr;
};
console.log(countBits(3));
You're doing way too much work.
Suppose b is the largest power of 2 corresponding to the first bit in i. Evidently, i has exactly one more 1 in its binary representation than does i - b. But since you're generating the counts in order, you've already worked out how many 1s there are in i - b.
The only trick is how to figure out what b is. And to do that, we use another iterative technique: as you list numbers, b changes exactly at the moment that i becomes twice the previous value of b:
const countBits = function(n) {
let arr = [0], bit = 1;
for (let i = 1; i <= n; i++){
if (i == bit + bit) bit += bit;
arr.push(arr[i - bit] + 1);
}
return arr;
};
console.log(countBits(20));
This technique is usually called "dynamic programming". In essence, it takes a recursive definition and computes it bottom-up: instead of starting at the desired argument and recursing down to the base case, it starts at the base and then computes each intermediate value which will be needed until it reaches the target. In this case, all intermediate values are needed, saving us from having to think about how to compute only the minimum number of intermediate values necessary.
Think of it this way: if you know how many ones are there in a number X, then you immediately know how many ones are there in X*2 (the same) and X*2+1 (one more). Since you're processing numbers in order, you can just push both derived counts to the result and skip to the next number:
let b = [0, 1]
for (let i = 1; i <= N / 2; i++) {
b.push(b[i])
b.push(b[i] + 1)
}
Since we push two numbers at once, the result will be one-off for even N, you have to pop the last number afterwards.
use floor():
sum += Math.floor(value%2);
value = Math.floor(value/2);
I guess your algorithm works for some typed language where integers division results in integers
Here's a very different approach, using the opposite of a fold (such as Array.prototype.reduce) typically called unfold. In this case, we start with a seed array, perform some operation on it to yield the next value, and recur, until we decide to stop.
We write a generic unfold and then use it with a callback which accepts the entire array we've found so far plus next and done callbacks, and then chooses whether to stop (if we've reached our limit) or continue. In either case, it calls one of the two callbacks.
It looks like this:
const _unfold = (fn, init) =>
fn (init, (x) => _unfold (fn, [...init, x]), () => init)
// Number of 1's in the binary representation of each integer in [`0 ... n`]
const oneBits = (n) => _unfold (
(xs, next, done) => xs .length < n ? next (xs .length % 2 + xs [xs .length >> 1]) : done(),
[0]
)
console .log (oneBits (20))
I have a GitHub Gist which shows a few more examples of this pattern.
An interesting possible extension would be to encapsulate the handling of the array-to--length-n bit, and make this function trivial. That's no the only use of such an _unfold, but it's probably a common one. It could look like this:
const _unfold = (fn, init) =>
fn (init, (x) => _unfold (fn, [...init, x]), () => init)
const unfoldN = (fn, init) => (n) => _unfold (
(xs, next, done) => xs .length < n ? next (fn (xs)) : done (),
init
)
const oneBits = unfoldN (
(xs) => xs .length % 2 + xs [xs .length >> 1],
[0]
)
console .log (oneBits (20))
Here we have two helper functions that make oneBits quite trivial to write. And those helpers have many potential uses.

Ramda.js transducers: average the resulting array of numbers

I'm currently learning about transducers with Ramda.js. (So fun, yay! 🎉)
I found this question that describes how to first filter an array and then sum up the values in it using a transducer.
I want to do something similar, but different. I have an array of objects that have a timestamp and I want to average out the timestamps. Something like this:
const createCheckin = ({
timestamp = Date.now(), // default is now
startStation = 'foo',
endStation = 'bar'
} = {}) => ({timestamp, startStation, endStation});
const checkins = [
createCheckin(),
createCheckin({ startStation: 'baz' }),
createCheckin({ timestamp: Date.now() + 100 }), // offset of 100
];
const filterCheckins = R.filter(({ startStation }) => startStation === 'foo');
const mapTimestamps = R.map(({ timestamp }) => timestamp);
const transducer = R.compose(
filterCheckins,
mapTimestamps,
);
const average = R.converge(R.divide, [R.sum, R.length]);
R.transduce(transducer, average, 0, checkins);
// Should return something like Date.now() + 50, giving the 100 offset at the top.
Of course average as it stands above can't work because the transform function works like a reduce.
I found out that I can do it in a step after the transducer.
const timestamps = R.transduce(transducer, R.flip(R.append), [], checkins);
average(timestamps);
However, I think there must be a way to do this with the iterator function (second argument of the transducer). How could you achieve this? Or maybe average has to be part of the transducer (using compose)?
As a first step, you can create a simple type to allow averages to be combined. This requires keeping a running tally of the sum and number of items being averaged.
const Avg = (sum, count) => ({ sum, count })
// creates a new `Avg` from a given value, initilised with a count of 1
Avg.of = n => Avg(n, 1)
// takes two `Avg` types and combines them together
Avg.append = (avg1, avg2) =>
Avg(avg1.sum + avg2.sum, avg1.count + avg2.count)
With this, we can turn our attention to creating the transformer that will combine the average values.
First, a simple helper function that allow values to be converted to our Avg type and also wraps a reduce function to default to the first value it receives rather than requiring an initial value to be provided (a nice initial value doesn't exist for averages, so we'll just use the first of the values instead)
const mapReduce1 = (map, reduce) =>
(acc, n) => acc == null ? map(n) : reduce(acc, map(n))
The transformer then just needs to combine the Avg values and then pull resulting average out of the result. n.b. The result needs to guard for null values in the case where the transformer is run over an empty list.
const avgXf = {
'##transducer/step': mapReduce1(Avg.of, Avg.append),
'##transducer/result': result =>
result == null ? null : result.sum / result.count
}
You can then pass this as the accumulator function to transduce, which should produce the resulting average value.
transduce(transducer, avgXf, null, checkins)
I'm afraid this strikes me as quite confused.
I think of transducers as a way of combining the steps of a composed function on sequences of values so that you can iterate the sequence only once.
average makes no sense here. To take an average you need the whole collection.
So you can transduce the filtering and mapping of the values. But you will absolutely need to then do the averaging separately. Note that filter then map is a common enough pattern that there are plenty of filterMap functions around. Ramda doesn't have one, but this would do fine:
const filterMap = (f, m) => (xs) =>
xs .flatMap (x => f (x) ? [m (x)] : [])
which would then be used like this:
filterMap (
propEq ('startStation', 'foo'),
prop ('timestamp')
) (checkins)
But for more complex sequences of transformations, transducers can certainly fit the bill.
I would also suggest that when you can, you should use lift instead of converge. It's a more standard FP function, and works on a more abstract data type. Here const average = lift (divide) (sum, length) would work fine.

Functional composition in JavaScript

I know this is quite possible since my Haskell friends seem to be able to do this kind of thing in their sleep, but I can't wrap my head around more complicated functional composition in JS.
Say, for example, you have these three functions:
const round = v => Math.round(v);
const clamp = v => v < 1.3 ? 1.3 : v;
const getScore = (iteration, factor) =>
iteration < 2 ? 1 :
iteration === 2 ? 6 :
(getScore(iteration - 1, factor) * factor);
In this case, say iteration should be an integer, so we would want to apply round() to that argument. And imagine that factor must be at least 1.3, so we would want to apply clamp() to that argument.
If we break getScore into two functions, this seems easier to compose:
const getScore = iteration => factor =>
iteration < 2 ? 1 :
iteration === 2 ? 6 :
(getScore(iteration - 1)(factor) * factor);
The code to do this probably looks something like this:
const getRoundedClampedScore = compose(round, clamp, getScore);
But what does the compose function look like? And how is getRoundedClampedScore invoked? Or is this horribly wrong?
The compose function should probably take the core function to be composed first, using rest parameters to put the other functions into an array, and then return a function that calls the ith function in the array with the ith argument:
const round = v => Math.round(v);
const clamp = v => v < 1.3 ? 1.3 : v;
const getScore = iteration => factor =>
iteration < 2 ? 1 :
iteration === 2 ? 6 :
(getScore(iteration - 1)(factor) * factor);
const compose = (fn, ...transformArgsFns) => (...args) => {
const newArgs = transformArgsFns.map((tranformArgFn, i) => tranformArgFn(args[i]));
return fn(...newArgs);
}
const getRoundedClampedScore = compose(getScore, round, clamp);
console.log(getRoundedClampedScore(1)(5))
console.log(getRoundedClampedScore(3.3)(5))
console.log(getRoundedClampedScore(3.3)(1))
Haskell programmers can often simplify expressions similar to how you'd simplify mathematical expressions. I will show you how to do so in this answer. First, let's look at the building blocks of your expression:
round :: Number -> Number
clamp :: Number -> Number
getScore :: Number -> Number -> Number
By composing these three functions we want to create the following function:
getRoundedClampedScore :: Number -> Number -> Number
getRoundedClampedScore iteration factor = getScore (round iteration) (clamp factor)
We can simplify this expression as follows:
getRoundedClampedScore iteration factor = getScore (round iteration) (clamp factor)
getRoundedClampedScore iteration = getScore (round iteration) . clamp
getRoundedClampedScore iteration = (getScore . round) iteration . clamp
getRoundedClampedScore iteration = (. clamp) ((getScore . round) iteration)
getRoundedClampedScore = (. clamp) . (getScore . round)
getRoundedClampedScore = (. clamp) . getScore . round
If you want to convert this directly into JavaScript then you could do so using reverse function composition:
const pipe = f => g => x => g(f(x));
const compose2 = (f, g, h) => pipe(g)(pipe(f)(pipe(h)));
const getRoundedClampedScore = compose2(getScore, round, clamp);
// You'd call it as follows:
getRoundedClampedScore(iteration)(factor);
That being said, the best solution would be to simply define it in pointful form:
const compose2 = (f, g, h) => x => y => f(g(x))(h(y));
const getRoundedClampedScore = compose2(getScore, round, clamp);
Pointfree style is often useful but sometimes pointless.
I think part of the trouble you're having is that compose isn't actually the function you're looking for, but rather something else. compose feeds a value through a series of functions, whereas you're looking to pre-process a series of arguments, and then feed those processed arguments into a final function.
Ramda has a utility function that's perfect for this, called converge. What converge does is produce a function that applies a series of functions to a series of arguments on a 1-to-1 correspondence, and then feeds all of those transformed arguments into another function. In your case, using it would look like this:
var saferGetScore = R.converge(getScore, [round, clamp]);
If you don't want to get involved in a whole 3rd party library just to use this converge function, you can easily define your with a single line of code. It looks a lot like what CaptainPerformance is using in their answer, but with one fewer ... (and you definitely shouldn't name it compose, because that's an entirely different concept):
const converge = (f, fs) => (...args) => f(...args.map((a, i) => fs[i](a)));
const saferGetScore = converge(getScore, [round, clamp]);
const score = saferGetScore(2.5, 0.3);

What is the effect of explicitly decalred 'let' on a for-loop variable with recursion?

So I have a sample code for a function the goes through an array and all of it's sub-arrays by recursion, and counts the number of matching found strings.
sample array:
const array = [
'something',
'something',
[
'something',
'something'
],
'something',
[
'something',
[
'something',
'something'
],
'something',
[
[
'something',
'something',
[
'anything'
]
],
'something',
],
[
'something',
[
'something',
'something',
'something',
[
'anything',
'something',
[
'something',
'anything'
]
]
]
]
],
'anything'
];
My function can go through this array and count the number of "something"'s in it.
This code works well:
let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
for(let i=0;i<array.length;i++){
const item = array[i];
if((typeof item) === 'string'){
(item === stringMatch) ? somethingsFound++ : undefined;
} else if ((typeof item) === 'object'){
searchArrays(item, stringMatch);
}
}
}
searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);
console output:
>Found 18 somethings
However
Here is the part I don't understand and need some explanation about. If i remove the let declaration on the forloop variable i and just implicitly declare it i=0;<array.length;i++ then my function goes into infinite recursion. I checked it by putting a console.log("running search) statement and saw that.
What does the let do exactly in this situation? I have tried reading about it but couldn't quite understand what's going on, and how exactly the recursion and forloop interact.
here's the failing block of code just in case, all that differs is that let declartion:
let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
for(i=0;i<array.length;i++){
const item = array[i];
if((typeof item) === 'string'){
(item === stringMatch) ? somethingsFound++ : undefined;
} else if ((typeof item) === 'object'){
searchArrays(item, stringMatch);
}
}
}
searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);
Thanks! CodeAt30
...and just implicitly declare it i=0;
That's not a declaration, it's just creating an implicit global*. Since it's a global, it's shared by all of the calls to searchArrays, so your outer calls are ending prematurely (because i has been incremented by your inner calls).
Example:
function withDeclaration(recurse) {
for (let i = 0; i < 3; ++i) {
console.log((recurse ? "Outer " : "Inner ") + i);
if (recurse) {
withDeclaration(false);
}
}
}
function withoutDeclaration(recurse) {
for (i = 0; i < 3; ++i) {
console.log((recurse ? "Outer " : "Inner ") + i);
if (recurse) {
withoutDeclaration(false);
}
}
}
console.log("With declaration:");
withDeclaration(true);
console.log("Without declaration:");
withoutDeclaration(true);
.as-console-wrapper {
max-height: 100% !important;
}
The moral of the story: Never rely on implicit globals. Declare your variables, in the innermost scope in which you need them. Use "use strict" to make implicit global creation an error.
* (that's a post on my anemic little blog)
When you use the "implicitly declared" variable, you will only have one such variable, independent of where you are in the recursion tree. It will be one global variable.
That will effectively destroy the logic of your algorithm, as the deepest recursion level will move the value of i beyond the array length, and then when you backtrack the loop at the previous recursion level will suddenly jump to that value of i, probably skipping several valid array entries it should have dealt with.
Always declare variables.
TJ and Trincot do a good job of fixing your program – I'm going to try to fix your thinking...
recursion is a functional heritage
Recursion is a concept that comes from functional style. Mixing it with imperative style is a source of much pain and confusion for new programmers.
To design a recursive function, we identify the base and inductive case(s)
base case – the first value of the input is Empty - if the input is empty, there are obviously no matches, therefore return 0
inductive case 1 – first is not empty, but it is an Array – recur on first plus recur on the rest of the values
inductive case 2 - first is not empty and not an array, therefore it is a plain value – if first matches match, return 1 for the match plus the result of recurring on the rest of the values
inductive case 3 - first is not empty, not an array, nor does it match match – recur on the rest of values
As a result of this implementation, all pain and suffering are removed from the program. We do not concern ourselves with local state variables, variable reassignment, array iterators, incrementing iterators, or other side effects like for
For brevity's sake, I replaced 'something' and 'anything' in your data with 'A' and 'B' respectively.
const Empty =
Symbol ()
const searchArrays = (match, [ first = Empty, ...rest ]) =>
{
/* no value */
if (first === Empty)
return 0
/* value is NOT empty */
else if (Array.isArray (first))
return searchArrays (match, first) + searchArrays (match, rest)
/* value is NOT array */
else if (first === match)
return 1 + searchArrays (match, rest)
/* value is NOT match */
else
return searchArrays (match, rest)
}
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
with functional style
Or encode searchArrays as a pure functional expression – this program is the same but exchanges the imperative-style if/else if/else and return statement syntaxes for ternary expressions
const Empty =
Symbol ()
const searchArrays = (match, [ first = Empty, ...rest ]) =>
first === Empty
? 0
: Array.isArray (first)
? searchArrays (match, first) + searchArrays (match, rest)
: first === match
? 1 + searchArrays (match, rest)
: searchArrays (match, rest)
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
without magic
Above, we use a rest parameter to destructure the input array. If this is confusing to you, it will help to see it in a simplified example. Note Empty is used so that our function can identify when to stop.
const Empty =
Symbol ()
const sum = ([ first = Empty, ...rest]) =>
first === Empty
? 0
: first + sum (rest)
console.log (sum ([ 1, 2, 3, 4 ])) // 10
console.log (sum ([])) // 0
This is a high-level feature included in newer versions of JavaScript, but we don't have to use it if it makes us uncomfortable. Below, we rewrite sum without the fanciful destructuring syntaxes
const isEmpty = (xs = []) =>
xs.length === 0
const first = (xs = []) =>
xs [0]
const rest = (xs = []) =>
xs.slice (1)
const sum = (values = []) =>
isEmpty (values)
? 0
: first (values) + sum (rest (values))
console.log (sum ([ 1, 2, 3, 4 ])) // 10
console.log (sum ([])) // 0
We can take our isEmpty, first, and rest functions and reimplement searchArrays now – notice the similarities; changes in bold
const searchArrays = (match, values = []) =>
isEmpty (values)
? 0
: Array.isArray (first (values))
? searchArrays (match, first (values)) + searchArrays (match, rest (values))
: first (values) === match
? 1 + searchArrays (match, rest (values))
: searchArrays (match, rest (values))
Expand the code snippet to see that it works the same
const isEmpty = (xs = []) =>
xs.length === 0
const first = (xs = []) =>
xs [0]
const rest = (xs = []) =>
xs.slice (1)
const searchArrays = (match, values = []) =>
isEmpty (values)
? 0
: Array.isArray (first (values))
? searchArrays (match, first (values)) + searchArrays (match, rest (values))
: first (values) === match
? 1 + searchArrays (match, rest (values))
: searchArrays (match, rest (values))
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
with powerful abstraction
As programmers, "traverse a data structure and do an operation on each element" is a common thing we need to do. Identifying these patterns and abstracting them into generic, reusable functions is at the core of higher-level thinking, which unlocks the ability to write higher-level programs like this
const searchArrays = (match, values = []) =>
deepFold ( (count, x) => x === match ? count + 1 : count
, 0
, values
)
This skill does not come automatically but there are techniques that can help you achieve higher-level thinking. In this answer I aim to give the reader a little insight. If this sort of thing interests you, I encourage you to take a look ^_^
recursion caution
JavaScript does not yet support tail call elimination which means extra precaution needs to be taken when writing recursive functions. For a code example that follows your program closely, see this answer.

JavaScript: Refer to remaining Array in the reduce method (fold)?

I want to test wether an Array consists only of unique elements and my solution is the following:
function uniqueElements(a) {
var r = true;
while (a) {
var [el, a] = [a.slice(0,1), a.slice(1)];
r &= !a.includes(el);
};
return !!r;
}
This method works. However, since I am adopting a more functional style, and because folds are awesome, I would like to implement a function which looks somewhat like this:
function uniqueElements(a) {
var isUnique = (acc, el) => acc &= !remainingArray.includes(el);
return a.reduce(isUnique, true);
}
I can't figure out how to get to that remainingArray variable. Does anybody know how to get it? Is this even possible in JS, and if not, how could that function be expressed through a fold?
Remember not to get stuck in a pattern of thinking. Folds are great but in JavaScript there's no way to stop folding early if our result can be computed before traversing the entire array
In other words, what is the answer of the following? true or false?
uniqueElements ( [ 1 , 1 , 2 , 3 , ... thousands more items ] )
// => true or false ?
We can determine the answer to be false immediately after processing the second 1. There's no need to keeping folding in 2, or 3, or the rest of the array as they will not affect the false outcome
A possible solution is a simple recursive procedure
const isUnique = ([ x, ... xs ], set = new Set ()) =>
x === undefined
? true
: set.has (x)
? false // we already found a non-unique, stop recurring
: isUnique (xs, set.add (x))
console.log (isUnique ([]))
// true
console.log (isUnique ([ 1, 2, 3 ]))
// true
console.log (isUnique ([ 1, 1, 2, 3 ]))
// false
Or a stack-safe solution that still maintains a pure functional interface – if I had to guess, this is probably 10x faster than the above program and doesn't expose private API
const isUnique = xs =>
{
const set = new Set ()
for (const x of xs)
if (set.has (x))
return false
else
set.add (x)
return true
}
console.log (isUnique ([]))
// true
console.log (isUnique ([ 1, 2, 3 ]))
// true
console.log (isUnique ([ 1, 1, 2, 3 ]))
// false
Or make up your own solution – either way, just don't get cornered into thinking folds need to be used wherever your touch a traversable data structure.
And in a more general sense, you need to practice imagining what the process of your function looks like. I recommend that you play compiler/evaluator with a pencil and paper while you're first getting the hang of it. Eventually you will be able to envision simple processes in your head; and then more complex ones with practice over time – I say this because you probably wouldn't have reached for a fold to complete this task if you could see how silly it looks to continue folding after the result can be returned
On that note, that is why I used Set to check for uniques as opposed to .includes. Sets can do binary search whereas array searches are linear – looking for your item in an array one-by-one just seems silly once you can see what that process would look like for a significantly large input. Only when you're envisioning process can you see how alternative data structures like Set can dramatically lower the time complexity of your function
You could slice the array:
function isUnique(xs) {
return xs.reduce((acc, x, i) => {
if (xs.slice(i + 1).includes(x)) {
return false;
}
return acc;
}, true);
}
Although, as mentioned in the comments, you could also use a hash for better performance if you have strings or numbers in your array.

Categories