테스트할 값이 없는데 어떻게 조건을 만족시킬 수 있을까요?
원문 : https://humanwhocodes.com/blog/2023/09/javascript-wtf-why-does-every-return-true-for-empty-array/
자바스크립트 언어의 코어는 방대하기 때문에 특정 부분의 동작을 오해하기 쉽습니다. 저는 최근에 every()
메서드를 사용하는 코드를 리팩토링하면서 실제로 그 안의 상세한 로직을 이해하지 못하고 있다는 사실을 깨달았습니다. 제 생각으로는, every()
가 true
를 반환하기 위해서는 콜백 함수가 반드시 호출되어 true
를 반환해야 한다고 가정했지만 실제로는 그렇지 않았습니다. 콜백 함수와 관계없이 every()
는 빈 배열에 대해 true
를 반환했습니다. 왜냐하면 콜백 함수가 호출되지 않기 때문입니다. 다음 예제를 살펴보겠습니다.
function isNumber(value) {
return typeof value === "number";
}
[1].every(isNumber); // true
["1"].every(isNumber); // false
[1, 2, 3].every(isNumber); // true
[1, "2", 3].every(isNumber); // false
[].every(isNumber); // true
이 예제의 각 케이스에서 every()
호출 시 배열의 각 아이템이 숫자인지를 확인합니다. 초반 4개의 케이스는 every()
가 예상한 결과를 도출하기 때문에 직관적입니다. 이제 아래의 예제를 고려해 보세요.
[].every(() => true); // true
[].every(() => false); // true
좀 더 놀랍지 않나요? true
나 false
를 반환하는 콜백의 결과가 동일합니다. 이런 결과가 발생할 수 있는 유일한 이유는 콜백이 호출되지 않고 every()
의 기본값이 true
라고 가정할 때입니다. 그런데 콜백 함수를 실행할 값이 없을 때도 왜 every()
는 빈 배열에 대해 true
를 반환할까요?
그 이유를 이해하기 위해서는 이 메서드를 설명한 명세서를 자세히 들여다볼 필요가 있습니다.
every()
구현하기ECMA-262는 대략 아래와 같은 자바스크립트 코드로 해석되는 Array.prototype.every()
알고리즘을 정의합니다.
Array.prototype.every = function(callbackfn, thisArg) {
const O = this;
const len = O.length;
if (typeof callbackfn !== "function") {
throw new TypeError("Callback isn't callable");
}
let k = 0;
while (k < len) {
const Pk = String(k);
const kPresent = O.hasOwnProperty(Pk);
if (kPresent) {
const kValue = O[Pk];
const testResult = Boolean(callbackfn.call(thisArg, kValue, k, O));
if (testResult === false) {
return false;
}
}
k = k + 1;
}
return true;
};
위의 코드로부터 every()
는 결과를 true
로 가정하고 배열 내의 어떤 아이템이 콜백 함수에서 false
를 반환할 때만 false
를 반환하는 것을 알 수 있습니다. 만약 배열 내에 아이템이 없다면, 콜백 함수를 실행할 기회가 없고 따라서 false
를 반환할 방법도 없는 것입니다.
이제 질문은, 왜 every()
가 이런 식으로 동작할까요?
MDN 페이지에서는 every()
가 빈 배열에 대해 true
를 반환하는 이유에 대한 답을 제공합니다.
every
는 수학에서 "전체" 양화사처럼 동작합니다. 특히, 빈 배열의 경우 true를 반환합니다. (이는 공집합의 모든 원소가 주어진 어떠한 조건이든 모두 만족하는 공허참입니다.)
공허참은 만약 전제(antecedent)라 불리는 주어진 조건을 만족시킬 수 없는 경우 (즉, 주어진 조건이 참이 아닐 때) 어떤 것이 참임을 의미하는 수학적 개념입니다. 이를 자바스크립트 용어로 해석해보면, 콜백을 호출할 방법이 없기 때문에 공집합에 대해 every()
는 true
를 반환합니다. 콜백은 테스트할 조건을 나타내고, 만약 배열에 값이 없어서 실행되지 못한다면, every()
는 반드시 true
를 반환합니다.
"전체" 양화사는 데이터 집합에 대해 추론할 수 있게 해주는 보편 양화(universal quantification)라는 큰 수학적 주제의 일부입니다. 특히 형식화 배열(typed array)에서 수학적 계산을 수행하는데 있어 자바스크립트 배열의 중요성을 고려할 때, 이러한 연산을 내부적으로 지원하는 것은 합리적입니다. 그리고 every()
가 유일한 경우는 아닙니다.
역자주: 양화사(quantifier)란 술어 논리에서 특정 술어를 만족하는 대상이 주어진 논의 영역에 얼마나 존재하는지를 알려주는 의미를 갖습니다. 크게 존재 양화사(existential quantifier)와 보편 양화사(universal quantifier) 두 종류로 나뉩니다.
자바스크립트 some()
메서드는 존재 양화(existential quantification)의 "존재" 양화사를 구현한 것입니다 (영어 표현으로 "존재"는 "there exists", 또는 가끔 "exists", "for some"으로 나타냅니다). "존재" 양화사는 어떠한 빈 집합에 대해서도 결과가 거짓임을 나타냅니다. 따라서 some()
메서드는 빈 집합에 대해 false
를 반환하고 콜백 역시 실행하지 않습니다. 아래에 예제가 있습니다.
function isNumber(value) {
return typeof value === "number";
}
[1].some(isNumber); // true
["1"].some(isNumber); // false
[1, 2, 3].some(isNumber); // true
[1, "2", 3].some(isNumber); // true
[].some(isNumber); // false
[].some(() => true); // false
[].some(() => false); // false
컬렉션이나 이터러블에 대해 양화사 메서드를 구현한 프로그래밍 언어는 자바스크립트뿐만이 아닙니다.
all()
함수가 "전체"를 구현하고, any()
함수가 "존재"를 구현했습니다.Iterator:all()
메서드가 "전체"를 구현하고, any()
함수가 "존재"를 구현했습니다.따라서 자바스크립트도 every()
와 some()
에 대해 마찬가지입니다.
every()
의 의미every()
의 동작이 직관적이지 않다고 생각하는지 여부는 논쟁의 여지가 있습니다. 하지만 이런 생각과 관계없이 에러를 방지하기 위해서는 every()
의 "전체" 특성을 염두에 두어야 합니다. 요약하자면, 만약 every()
또는 비어있을 수 있는 배열을 사용하려면, 미리 명시적인 검사를 추가해야 합니다. 예를 들어, 만약 숫자 배열에 의존하는 연산이 있고 빈 배열에 대해서 연산이 실패해야 한다면, every()
를 사용하기 전에 배열이 비었는지를 확인해야 합니다.
function doSomethingWithNumbers(numbers) {
// 먼저 길이를 확인합니다
if (numbers.length === 0) {
throw new TypeError("Numbers array is empty; this method requires at least one number.");
}
// 그리고서 every()로 확인합니다
if (numbers.every(isNumber)) {
operationRequiringNonEmptyArray(numbers);
}
}
다시 강조하면, 이는 비어있을 때 연산에 사용하면 안 되는 배열이 있을 때만 중요합니다. 그렇지 않은 경우라면 추가적인 확인이 없어도 됩니다.
처음에는 빈 배열에 대한 every()
의 동작에 놀랐지만, 연산의 더 넓은 맥락을 이해하고 이러한 기능이 다른 언어에도 확장된 것을 확인하면서 납득할 수 있었습니다. 만약 여러분도 이러한 동작 때문에 혼란스럽다면, every()
를 마주할 때 생각하는 방법을 바꿔보는 걸 제안드립니다. every()
를 "배열의 모든 아이템이 조건을 만족하는가?"로 읽는 대신, "배열에 조건을 만족하지 않는 아이템이 있는가?"로 읽어보십시오. 이러한 사고의 전환은 앞으로 자바스크립트 코드의 오류를 방지하는 데 도움이 될 것입니다.
마스토돈과 트위터를 통해 더 많은 정보를 제공해주신 Dr. Axel Rauschmayer, Bart Louwers, Naman, Ronny Haase, Alexey Raspopov, Ivan, 그리고 David Thomas께 감사의 인사를 전합니다.
very imformative. https://www.iteducationcentre.com/python-course-in-amravati.php
잘 읽었습니다.