카메라

이 글에서 저는 함수식 프로그래밍에서의 렌즈가 무엇인지, 어떻게 사용하는지, 가장 중요한 것은 자신의 렌즈를 어떻게 작성하여 실현하는지 보여드리고 싶습니다.
TL;박사
렌즈는 직접 조합할 수 있는 접근기다.그것들이 어떻게 일을 하는지, 그리고 어떻게 자신을 만드는지 계속 읽으세요.
Runkit에서 모든 예시와 두 번째 대체 구현을 포함하는 작은 노트북을 만들었습니다.따라서 언제든지 (본문을 읽기 전, 읽기 기간, 읽기 후) 이 문제들을 처리할 수 있다.참조: https://runkit.com/mister-what/lenses

소개


우리 문제의 묘사부터 시작합시다.다음과 같은 데이터 구조를 가지고 있다면, 위치와 직위에 따라 직원을 열거합니다.
const locations = {
  berlin: {
    employees: {
      staff: {
        list: [
          {
            name: "Wiley Moen",
            phone: "688-031-5608",
            id: "cdfa-f2ae"
          },
          {
            name: "Sydni Keebler",
            phone: "129-526-0289",
            id: "e0ec-e480"
          }
        ]
      },
      managers: {
        list: [
          {
            name: "Cecilia Wisoky",
            phone: "148-188-6725",
            id: "9ebf-5a73"
          }
        ]
      },
      students: {
        list: [
          {
            name: "Kirsten Denesik",
            phone: "938-634-9476",
            id: "c816-2234"
          }
        ]
      }
    }
  },
  paris: {
    employees: {
      staff: {
        list: [
          {
            name: "Lucius Herman",
            phone: "264-660-0107",
            id: "c2fc-55da"
          }
        ]
      },
      managers: {
        list: [
          {
            name: "Miss Rickie Smith",
            phone: "734-742-5829",
            id: "2095-69a7"
          }
        ]
      }
    }
  }
};
응용 프로그램의 다른 위치에서 이 구조의 데이터에 접근하면 대량의 중복을 초래하고 데이터 구조가 변경될 때 (어떤 이유로든) 발견하기 어려운 오류를 초래할 수 있다.
따라서 우리는 이 문제를 해결하는 또 다른 방법: 렌즈

렌즈


렌즈는 안전하고 변하지 않는 방식으로 데이터에 접근하고 조작하는 데 쓰인다.대상의 접근기 (getter와setter) 도 마찬가지입니다. 이상하지도 않고 특별한 점도 없습니다.렌즈가 정말 강한 이유는 직접 조합할 수 있기 때문이다.그게 무슨 뜻이에요?만약 당신이 생활 속에서 수학 수업을 한 적이 있다면, 함수는 서로 조합할 수 있다는 것을 알고 있다. 즉, 당신이 를 가지고 있다면, 당신은 f와 g의 조합을 로 정의할 수 있다. 을 제외하고는 다른 뜻이 없다.
그러면 우리는 어떻게 Javascript로 합성을 표현합니까?간단히 말하면
function compose(g, f) {
    return function(x) {
        return g(f(x));
    }
}

// or with fat-arrow functions:
const compose = (g, f) => x => g(f(x));
우리는 세 가지 (또는 더 많은 방식) 로 더 높은 구성 순서를 정의할 수 있다.
// recursive version
const compose = (...fns) => x =>
  fns.length
    ? compose(...fns.slice(0, -1))(
        fns[fns.length - 1](x)
      )
    : x;

// iterative version
const composeItr = (...fns) => x => {
  const functions = Array.from(
    fns
  ).reverse();
  /* `reverse` mutates the array,
    so we make a shallow copy of the functions array */
  let result = x;
  for (const f of functions) {
    result = f(result);
  }
  return result;
};

// with Array.prototype.reduce
const composeReduce = (...fns) => x =>
  fns.reduceRight(
    (result, f) => f(result),
    x
  );

// use it!
console.log(
  compose(
    x => `Hello ${x}`,
    x => `${x}!`
  )("World")
); // -> "Hello World!"

우리는 이제 함수를 어떻게 조합하는지 알게 되었다.조합 함수의 매개 변수와 반환 값이 같은 유형에 속할 때 함수 조합 효과가 가장 좋다는 것을 알 수 있습니다.
어떤 위치의 학생들을 위해 그룹의 getter를 정의합시다.
const studentsAtLocation = compose(
    (students = {}) => students.list || [],
    (employees = {}) => employees.students,
    (location = {}) => location.employees
  );

const locationWithName = locationName => (
  locations = {}
) => locations[locationName];

const getBerlinStudents = compose(
  studentsAtLocation,
  locationWithName("berlin")
);

const getParisStudents = compose(
  studentsAtLocation,
  locationWithName("paris")
);

console.log(
  getBerlinStudents(locations)
); // [ { name: 'Kirsten Denesik', ... ]

console.log(
  getParisStudents(locations)
); // []
만약 네가 아직도 나와 함께 있다면, getter 함수는 상반된 순서로 제공된다는 것을 이미 알아차렸을 것이다.우리는 getter를 매개 변수로 하고 getter의 함수를 되돌려줌으로써 이 문제를 해결할 것입니다.이런 모드 (한 함수를 전달하고 한 함수를 되돌려주는 것) 는 기본적으로 getter/setter 쌍으로 구성할 수 있도록 합니다. 방법은 한 함수를 전달하는 것입니다. 이 함수는 하나의 값을 받아들이고 getter/setter 쌍을 되돌려줍니다.이게 어떤 모습인지 한번 봅시다.
const createComposableGetterSetter = (
  getter, // (1)
  // -- getter(targetData: TargetData): Value
  setter // (4)
  // -- setter(value: Value, targetData: TargetData) => TargetData
) => toGetterAndSetter => targetData => { // (2)
  const getterSetter = toGetterAndSetter(
    getter(targetData)
  ); // (3)
  /**
   * toGetterAndSetter is called with
   * "data" as argument
   * and returns a GetterSetter object:
   * @typedef {
   *  {
   *    get: function(): *,
   *    set: function(newData: *): GetterSetter
   *  }
   * } GetterSetter
   *
   */
  return getterSetter.set(
    setter(
      getterSetter.get(),
      targetData
    )
  ); // (5)
};
설령 이것이 "단지"쌍선 함수체일지라도 이곳에서 무슨 일이 일어났는지 이해하는 데 시간이 좀 걸리기 때문에 나는 점차적으로 설명할 것이다.
  • getter와setter 함수를 매개 변수로 호출createComposableGetterSetter한 후에 우리는actutalcomposableGetterSetter을 얻었다.
  • 우리composableGetterSettertoGetterAndSetter 함수를 얻을 것이다. 이 함수는 일부 데이터를 입력으로 하고 getset 방법으로 대상을 되돌려준다.우리는 함수를 되돌려줍니다. 이 함수는 목표 데이터가 유일한 매개 변수가 되기를 기대합니다.
  • 우리는 (2)의 목표 데이터 호출 (1) 을 사용하고 반환 값을 toGetterAndSetter 함수에 전달함으로써 GetterSetter 대상을 구성합니다.
  • 우리는 GetterSetter 대상set() 방법을 사용합니다. 반환 값은 setter(4), 반환 값은 구조된 GetterSetter 대상의 값(우리 호출getterSetter.get()으로 간단하게 이 값을 검색합니다)과 targetData(우리 기대setter는 targetData의 새로운 버전으로 반환되며, 초점 값은 getterSetter.get()의 반환 값으로 설정됩니다.
  • 우리는 (5) 중의 getterSetter.set(...) 에서 되돌아오는 값(또는 GetterSetter 대상)을 되돌려줍니다.
  • toGetter And Setter 회사


    현재 우리는 이미 createComposableGetterSetter 함수를 정의했다.우리는 여전히 toGetterAndSetter 함수를 정의해야 한다. 우리는 그것을 사용하여 목표로부터 데이터를 얻거나 목표에 데이터를 설정할 것이다.먼저 정의해 봅시다toSetAccessors:
    const toSetAccessors = data => ({
      get: () => data,
      set: newData => toSetAccessors(newData)
    });
    
    그래서 우리가 목표 대상에 데이터를 설정하고 싶다면 간단한 함수는 우리를 위해 대상을 구성하고 그것을 사용한다.새 데이터로 set 방법을 호출할 때마다 새 데이터를 저장하고 되돌려주는 새로운 실례를 만듭니다.
    다음은 toGetAccessors 기능입니다.
    const toGetAccessors = data => ({
      get: () => data,
      set() {
        return this;
      }
    });
    
    GetAccessor 객체는 데이터만 검색할 수 있어야 합니다.새 데이터를 설정하려고 시도할 때, 그것은 자신의 실례만 되돌려줍니다.이것은 창설 후 변경할 수 없습니다.

    ComposableGetterSetters 사용(렌즈)


    우리는 현재 세 개의 Composablegettersetter (렌즈라고도 함) 를 만들어서 그들이 어떻게 작동하는지, 그리고 그것들을 사용하여 값을 검색하거나 데이터를 변경하는 데 필요한 것이 무엇인지 알 것이다.

    렌즈 생성


    우리는'파리'부동산을 주목하는 장면,'직원'부동산을 주목하는 장면, 세 번째'학생'부동산을 주목하는 장면을 만들 것이다.
    우리는 getter에서 기본값 (이상을 피하기 위해) 을 사용하고 setter에서 대상 확장을 사용하여 불변성을 유지할 것입니다.
    const parisLens = createComposableGetterSetter(
      obj => (obj || {}).paris,
      (value, obj) => ({
        ...obj,
        paris: value
      })
    );
    
    const employeesLens = createComposableGetterSetter(
      obj => (obj || {}).employees,
      (value, obj) => ({
        ...obj,
        employees: value
      })
    );
    
    const studentsLens = createComposableGetterSetter(
      obj => (obj || {}).students,
      (value, obj) => ({
        ...obj,
        students: value
      })
    );
    
    우리는 여기에 약간의 중복이 있다는 것을 알아차렸기 때문에 그것을 재구성합시다.
    const lensProp = propName =>
      createComposableGetterSetter(
        obj => (obj || {})[propName],
        (value, obj) => ({
          ...obj,
          [propName]: value
        })
      );
    
    // we can now create lenses for props like this:
    
    const parisLens = lensProp("paris");
    
    const employeesLens = lensProp(
      "employees"
    );
    
    const studentsLens = lensProp(
      "students"
    );
    
    const listLens = lensProp("list"); // needed to get the list of students
    
    
    우리는 지금 우리의 렌즈를 합성하고 사용하기 시작할 수 있다.
    const parisStudentListLens = compose(
      parisLens,
      employeesLens,
      studentsLens,
      listLens
    );
    
    const parisStudentList = parisStudentListLens(
      toGetAccessors
    )(locations).get();
    
    console.log(parisStudentList);
    // -> undefined, since there is no list of students for paris defined.
    
    const locationsWithStudentListForParis = parisStudentListLens(
      _list => toSetAccessors([])
      // ignore current list and replace it with an empty array
    )(locations).get();
    
    console.log(
      locationsWithStudentListForParis
    );// -> { ..., paris: { employees:{ ..., students: { list: [] } } } }
    
    
    이것은 매우 지루할 것이기 때문에, 우리는 몇 가지 도움말 프로그램을 정의할 것이다.
    const view = (lens, targetData) =>
      lens(toGetAccessors)(
        targetData
      ).get();
    
    const over = (
      lens,
      overFn /* like the `mapf` callback in `Array.prototype.map(mapf)`.
        i.e.: You get a value and return a new value. */,
      targetData
    ) =>
      lens(data =>
        toSetAccessors(overFn(data))
      )(targetData).get();
    
    const set = (lens, value, targetData) =>
      over(
        lens,
        () =>
          value /* we use `over` with a `overFn` function, 
            that just returns the value argument */,
        targetData
      );
    
    우리의 조수를 사용해 보자.
    // using get, set, over:
    
    const locationsWithStudentListForParis = set(
      parisStudentListLens,
      [],
      locations
    );
    
    const locationsWithOneStudentInParis = over(
      parisStudentListLens,
      (list = []) => [
        ...list,
        { name: "You", setVia: "Lens" }
      ],
      locations
    );
    
    const locationsWithTwoStudentInParis = over(
      parisStudentListLens,
      (list = []) => [
        ...list,
        { name: "Me", setVia: "Lens" }
      ],
      locationsWithOneStudentInParis
    );
    
    // logging the results:
    
    console.log(
      view(parisStudentListLens, locations)
    ); // -> undefined
    
    console.log(
      view(
        parisStudentListLens,
        locationsWithStudentListForParis
      )
    ); // -> []
    
    console.log(
      view(
        parisStudentListLens,
        locationsWithTwoStudentInParis
      )
    ); // -> [ { name: 'You', setVia: 'Lens' }, { name: 'Me', setVia: 'Lens' } ]
    
    console.log(
      view(
        parisStudentListLens,
        locationsWithOneStudentInParis
      )
    ); // -> [ { name: 'Me', setVia: 'Lens' } ]
    
    console.log(
      locationsWithTwoStudentInParis
    ); // -> ...
    
    
    이런 방법은 깊이 박힌 불변의 데이터 구조를 구축하는 것을 쉽게 한다.더 간단하게 lensIndex(index: number)lensPath(path: Array<string|number>) 렌즈를 정의하여 보조 대상을 만들 수 있습니다.lensIndex 그리고 그룹 값에 주목합니다.lensPath 렌즈 lensProplensIndex 를 생성하고 미리 합성하여 깊이 있는 내포된 객체 속성과 패턴 인덱스에 초점을 맞춘 렌즈를 만듭니다.

    렌즈의 더 많은 응용 분야


    렌즈는 화폐, 온도, 단위(공제 단위에서 영국 단위로, 반대로), 사용자의 입력, 해석, 문자열화 JSON 등 여러 가지 값 사이에서 전환하기에 매우 적합하다.
    보기를 놓치지 마세요Runkit Notebook.만약 네가 나의 허튼소리를 이해하지 못한다면, 얼마든지 물어봐라!
    나는 어떤 질문에도 기꺼이 대답할 것이다.)

    좋은 웹페이지 즐겨찾기