기능성 JavaScript 코드를 어떻게 _read_ 할 수 있습니까?


9

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

답변:


18

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

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명명 된 함수 와 같은 공통 기능을 가져와 중첩을 피할 수 있습니다. 람다는 문맥 상 명확한 목적을 가진 매우 짧은 기능을 위해 예약되어 있습니다.

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


확실히 위반이 없습니다! 나는 여전히 함수형 프로그래밍에 대해 가지를 알고 있다고 생각하지만, 내가 얼마나 많이 알고 있는지 에 대한 질문에서 내 주장은 어쩌면 조금 과장되었다. 나는 정말 상대 초보자입니다. 그래서이 특별한 시도가 어떻게 간결하고 명확하지만 여전히 기능적인 방식으로 다시 쓰여질 수 있는지 보는 것은 금처럼 보입니다 ... 감사합니다. 나는 당신의 재 작성을 신중하게 공부할 것입니다.
Andrew Willems

1
체인이 길거나 메소드를 중첩하면 불필요한 중간 변수가 제거된다고 들었습니다. 대조적으로, 귀하의 대답은 내 체인 / 중첩을 잘 명명 된 중간 변수를 사용하여 중간 독립형 문으로 나눕니다. 이 경우 코드를 더 잘 읽을 수 있지만 얼마나 일반적인지 궁금합니다. 긴 메소드 체인 및 / 또는 딥 네 스팅은 종종 또는 항상 안티 패턴을 피하거나 상당한 이점을 가져올 때가 있다고 말하는가? 그리고 그 질문에 대한 대답은 기능적 코딩과 명령 적 코딩에 대해 다른가?
Andrew Willems

3
중간 변수를 제거하면 명확성이 추가 될 수있는 상황이 있습니다. 예를 들어, FP에서는 배열에 대한 색인을 거의 원하지 않습니다. 또한 때때로 중간 결과에 대한 좋은 이름이 없습니다. 그러나 내 경험상 대부분의 사람들은 다른 방식으로 너무 잘못하는 경향이 있습니다.
Karl Bielefeldt

6

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

함수 유형을 지정하는 것이 정말 중요합니다. 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) { ... }것이 관용적 인 함수를 정의하는 데 사용하고 있습니다 . 나는 그것들이 의미 상 약간 다르다는 것을 알고 있지만, 당신이 그 차이점에 의존하지 않는 한 가능하면 더 일반적인 변형을 사용하는 것이 좋습니다.


5
유형 +1! 언어에 언어가 없다고해서 그 언어에 대해 생각할 필요는 없습니다 . ECMAScript의 여러 문서 시스템에는 기능 유형을 기록하기위한 유형 언어가 있습니다. 여러 ECMAScript IDE에는 유형 언어도 있으며 일반적으로 주요 문서 시스템의 유형 언어를 이해하며 해당 유형 주석을 사용하여 기초 유형 검사 및 휴리스틱 힌트를 수행 할 수도 있습니다 .
Jörg W Mittag

유형 정의, 의미있는 이름, 관용구를 사용하여 ... 감사합니다! 가능한 많은 의견들 중 몇 가지에 불과합니다. 필자는 특정 부분을 카레 기능으로 작성하려고하지는 않았습니다. 글을 쓰는 동안 코드를 리팩터링하면서 이런 방식으로 발전했습니다. 이제는 이것이 어떻게 필요하지 않았는지 알 수 있으며 단일 함수에 대해 두 함수의 매개 변수를 두 개의 매개 변수로 병합하는 것만으로도 더 의미가있을뿐 아니라 그 짧은 비트를 적어도 더 읽기 쉽게 만듭니다.
Andrew Willems

@ JörgWMittag, 유형의 중요성에 대한 귀하의 의견과 귀하가 작성한 다른 답변에 대한 링크에 감사드립니다. WebStorm을 사용하고 있으며 다른 답변을 읽는 방법에 따라 WebStorm은 jsdoc와 같은 주석을 해석하는 방법을 알고 있습니다. jsdoc과 WebStorm은 명령형 코드뿐만 아니라 기능에 주석을 달기 위해 함께 사용할 수 있다고 생각합니다.하지만 실제로 알고 싶다면 더 깊이 탐구해야합니다. 나는 WebStorm을 알고 있고 거기에서 협력 할 수 있다는 것을 알고 jsdoc과 함께 놀았습니다. 그 기능을 더 많이 사용하기를 기대합니다.
Andrew Willems

@Jules, 그냥 내가 위에서 내 댓글에 언급 된 카레하는 기능을 명확히 : 당신이 묵시적으로의 각 인스턴스 obj => key => ...를 단순화 할 수 (obj, key) => ...나중에 있기 때문에 getX(obj)(key)또한 단순화 할 수 있습니다 get(obj, key). 대조적으로, 다른 코드화 된 함수 (getX, filter = vals => vals) => (objA, objB) => ...는 적어도 코드의 나머지 문맥에서 쉽게 단순화 될 수 없다.
앤드류 빌렘 스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.