기능성 JavaScript 코드를 어떻게 _read_ 할 수 있습니까? 부분을 이해

JavaScript에서 함수형 프로그래밍의 기본 개념 중 일부 / 많은 / 대부분을 배웠다고 생각합니다. 그러나 나는 기능 코드, 내가 작성한 코드조차도 읽는 데 어려움을 겪고 있으며 누군가 나를 도울 수있는 포인터, 팁, 모범 사례, 용어 등을 줄 수 있는지 궁금합니다.

아래 코드를 사용하십시오. 이 코드를 작성했습니다. 그것은 말 사이에, 두 개체 사이 %의 유사성을 할당하는 것을 목표로 {a:1, b:2, c:3, d:3}하고 {a:1, b:1, e:2, f:2, g:3, h:5}. Stack Overflow 에서이 질문에 대한 응답으로 코드를 생성했습니다 . 포스터가 어떤 종류의 유사성을 요구하는지 정확히 알지 못했기 때문에 네 가지 종류를 제공했습니다.

  • 두 번째에서 찾을 수있는 첫 번째 개체의 키 백분율
  • 두 번째에서 찾을 수있는 첫 번째 개체의 값 백분율 (중복 포함)
  • 중복이 허용되지 않고 두 번째에서 발견 될 수있는 첫 번째 객체의 값 백분율
  • 두 번째 오브젝트에서 찾을 수있는 첫 번째 오브젝트에서 {key : value} 쌍의 백분율

필연적으로 명령형 코드로 시작했지만 기능 프로그래밍에 적합한 문제라는 것을 빨리 깨달았습니다. 특히, 내가 비교하려고하는 기능 유형 (예 : 키 또는 값 등)을 정의한 위의 4 가지 전략 각각에 대해 함수 또는 3 개를 추출 할 수 있다면 코드의 나머지 부분을 반복 가능한 단위로 줄일 수 있습니다. 당신은 그것을 건조하게 유지합니다. 그래서 함수형 프로그래밍으로 전환했습니다. 나는 결과가 매우 자랑스럽고, 그것이 합리적으로 우아하다고 생각하며, 내가 한 일을 잘 이해하고 있다고 생각합니다.

그러나 코드를 직접 작성하고 구성하는 동안 코드의 모든 부분을 이해 했음에도 불구하고 지금 다시 살펴보면 특정 하프 라인을 읽는 방법과 읽는 방법에 대해 조금 당황합니다. 코드의 특정 반줄이 실제로 무엇을하고 있는지 “grok”. 스파게티의 엉망으로 빠르게 분해되는 다른 부분을 연결하기 위해 정신 화살표를 만드는 것을 발견했습니다.

그래서 누구든지 간결하고 내가 읽고있는 것에 대한 나의 이해에 기여하는 방식으로 좀 더 복잡한 코드를 어떻게 읽는지 말해 줄 수 있습니까? 가장 많이 얻는 부분은 행에 여러 개의 뚱뚱한 화살표가 있고 / 또는 행에 여러 개의 괄호가있는 부분 인 것 같습니다. 다시 말하지만, 핵심은 결국 논리를 알아낼 수는 있지만 기능적인 JavaScript 프로그래밍 라인을 빠르고 명확하고 직접 직접 수행하는 더 좋은 방법이 있다는 것입니다.

아래의 코드 줄이나 다른 예제를 자유롭게 사용하십시오. 그러나 나에게서 초기 제안을 원한다면 여기에 몇 가지가 있습니다. 합리적으로 간단한 것으로 시작하십시오. 코드의 끝 부분부터는 다음과 같이 매개 변수로 함수에 전달됩니다 obj => key => obj[key]. 어떻게 읽고 이해합니까? 더 긴 예는 시작 근처에서 하나의 전체 기능입니다 const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));. 마지막 map부분은 특히 나를 가져옵니다.

음,이 시점에서 내가있어 제발 하지 등 하스켈에 대한 참조 또는 상징적 추상적 표기 또는 태닝의 기본을 찾고 어떻게 하고 찾는 것은 내가 조용히 입 코드의 라인을보고 할 수있는 동안 것을 영어 문장이다. 당신이 구체적으로 정확하게 언급하는 참고 문헌이 있다면 훌륭하지만, 나는 기본적인 교과서를 읽어야한다고 말하는 답을 찾고 있지 않습니다. 나는 그것을했고 논리를 얻습니다 (적어도 상당량). 또한 철저한 답변이 필요하지는 않습니다 (그러한 시도는 환영 할지라도) : 하나의 특정 행을 읽는 우아한 방법을 제공하는 짧은 답변조차도 귀찮은 코드를 인정합니다.

나는이 질문의 일부가 다음과 같다고 가정합니다 : 심지어 기능 코드를 왼쪽에서 오른쪽으로, 위에서 아래로 선형으로 읽을 수 있습니까 ? 또는 사람은 거의 스파게티와 같은 코드 페이지에 배선 확실히이다의 정신 사진을 만들 강제 하지 선형? 그리고 그렇게 해야 한다면 여전히 코드를 읽어야하는데 어떻게 선형 텍스트를 취하고 스파게티를 연결합니까?

모든 팁을 주시면 감사하겠습니다.

const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };

// x or X is key or value or key/value pair

const getXs = (obj, getX) =>
  Object.keys(obj).map(key => getX(obj)(key));

const getPctSameXs = (getX, filter = vals => vals) =>
  (objA, objB) =>
    filter(getXs(objB, getX))
      .reduce(
        (numSame, x) =>
          getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
        0
      ) / Object.keys(objA).length * 100;

const pctSameKeys       = getPctSameXs(obj => key => key);
const pctSameValsDups   = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps      = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));

console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys:                   ', pctSameKeys      (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups  (obj1, obj2));
console.log('% same values, no duplicates:  ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps     (obj1, obj2));

// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys:                    50
// % same values, incl duplicates: 125
// % same values, no duplicates:   75
// % same properties (k/v pairs):  25


답변

이 특정 예제는 읽기가 어렵 기 때문에 대부분 읽기가 어렵습니다. 인터넷에서 찾을 수있는 엄청나게 많은 비율의 샘플도 마찬가지입니다. 많은 사람들이 주말에만 함수형 프로그래밍을 가지고 놀며 실제로 생산 함수형 코드를 장기간 유지하는 것을 다루지 않아도됩니다. 나는 이것을 다음과 같이 더 쓸 것이다 :

function mapObj(obj, f) {
  return Object.keys(obj).map(key => f(obj, key));
}

function getPctSameXs(obj1, obj2, f) {
  const mapped1 = mapObj(obj1, f);
  const mapped2 = mapObj(obj2, f);
  const same = mapped1.filter(x => mapped2.indexOf(x) != -1);
  const percent = same.length / mapped1.length * 100;
  return percent;
}

const getValues = (obj, key) => obj[key];
const valuesWithDupsPercent = getPctSameXs(obj1, obj2, getValues);

어떤 이유로 많은 사람들이 기능적 코드가 큰 중첩 표현의 특정 미학적 “모양”을 가져야한다는 생각을 머릿속에 가지고 있습니다. 내 버전은 모든 세미콜론이있는 명령형 코드와 다소 비슷하지만 모든 것은 불변이므로 모든 변수를 대체하고 원하는 경우 하나의 큰 표현을 얻을 수 있습니다. 그것은 스파게티 버전과 마찬가지로 “기능적”이지만 가독성이 더 높습니다.

여기서 표현은 매우 작은 조각으로 나뉘어 도메인에 의미가있는 이름이 주어집니다. mapObj명명 된 함수 와 같은 공통 기능을 가져와 중첩을 피할 수 있습니다. 람다는 문맥 상 명확한 목적을 가진 매우 짧은 기능을 위해 예약되어 있습니다.

읽기 어려운 코드를 발견하면 더 쉬울 때까지 리팩토링하십시오. 약간의 연습이 필요하지만 그만한 가치가 있습니다. 기능적 코드는 명령형처럼 읽을 수 있습니다. 사실, 종종 더 간결하기 때문에 종종 더 그렇습니다.


답변

기능 자바 스크립트에 대해 이야기 대부분의 사람들은지도, 필터를 사용하고있을 수 있습니다 및 감소,하지만 – 나는이이 말을 자바 스크립트에서 고기능 일 (많이하지 한 코드 자체보다 높은 수준의 기능을 정의 인을, 하스켈에서는 그렇게했지만 경험의 일부는 변한다고 생각합니다. 내가 배운 것들에 대한 몇 가지 조언을 드리겠습니다.

함수 유형을 지정하는 것이 정말 중요합니다. Haskell은 함수의 유형을 지정하지 않아도되지만 정의에 유형을 포함하면 훨씬 쉽게 읽을 수 있습니다. Javascript는 동일한 방식으로 명시 적 타이핑을 지원하지 않지만 주석에 유형 정의를 포함시키지 않을 이유는 없습니다.

// getXs :: forall O, F . O -> (O -> String -> F) -> [F]
const getXs = (obj, getX) =>
    Object.keys(obj).map(key => getX(obj)(key));

이와 같은 유형 정의 작업을 약간 연습하면 함수의 의미가 훨씬 명확 해집니다.

명명법은 절차 적 프로그래밍보다 훨씬 중요합니다. 많은 기능적 프로그램은 관습에 무거운 매우 간결한 스타일로 작성됩니다 (예 : ‘xs’는 목록 / 배열이고 ‘x’는 항목에 매우 광범위하다) 는 관례는 해당 스타일을 이해하지 않는 한 쉽게 더 자세한 이름을 제안합니다. 사용한 특정 이름을 보면 “getX”는 불투명 한 것이므로 “getXs”도 그다지 도움이되지 않습니다. “applyToProperties”와 같은 “getXs”를 호출하고 “getX”는 “propertyMapper”일 것입니다. “getPctSameXs”는 “percentPropertiesSameWith”( “with”).

또 다른 중요한 것은 관용적 코드작성하는 것입니다 . a => b => some-expression-involving-a-and-b카레 함수를 생성하기 위해 구문 을 사용하고 있습니다. 이것은 흥미롭고 일부 상황에서는 유용 할 수 있지만 , 여기서는 커리 함수의 이점을 얻는 어떤 것도하지 않고 전통적인 다중 인수 함수를 대신 사용하는 것이 관용적 인 자바 스크립트가 될 것입니다. 그렇게하면 상황을 한눈에 더 쉽게 확인할 수 있습니다. 또한 대신 const name = lambda-expression사용하는 function name (args) { ... }것이 관용적 인 함수를 정의하는 데 사용하고 있습니다 . 나는 그것들이 의미 상 약간 다르다는 것을 알고 있지만, 당신이 그 차이점에 의존하지 않는 한 가능하면 더 일반적인 변형을 사용하는 것이 좋습니다.