Kodokon kodokon.com

Functional pattern: currying, composition, immutability

จัดโครงสร้างโค้ดของคุณด้วย currying การประกอบฟังก์ชัน และข้อมูลที่ไม่เปลี่ยนแปลง เพื่อให้ได้ความคาดเดาได้และการทดสอบได้

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

JavaScript ปฏิบัติต่อฟังก์ชันในฐานะ ค่าชั้นหนึ่ง ซึ่งเปิดประตูสู่ functional pattern Currying เปลี่ยนฟังก์ชันที่มี n อาร์กิวเมนต์ให้เป็นสายโซ่ของฟังก์ชันที่รับอาร์กิวเมนต์เดียว: f(a, b, c) กลายเป็น f(a)(b)(c) อย่าสับสนกับ partial application ซึ่งตรึงอาร์กิวเมนต์บางตัวในขั้นตอนเดียว (fn.bind(null, a)) currying โดดเด่นในการสร้างฟังก์ชันเฉพาะทางจากฟังก์ชันทั่วไป

JAVASCRIPT
const curry = (fn) => {
  const arity = fn.length;
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...rest) => curried(...args, ...rest);
  };
};

const add3 = (a, b, c) => a + b + c;
const addCurried = curry(add3);

console.log(addCurried(1)(2)(3)); // 6
console.log(addCurried(1, 2)(3)); // 6
curry ทั่วไปที่อิงกับ arity ของฟังก์ชัน

ความละเอียดอ่อนในสเปก: fn.length นับเฉพาะพารามิเตอร์ที่อยู่ ก่อน พารามิเตอร์ default หรือ rest ตัวแรกเท่านั้น (a, b = 1) => {} มี length เป็น 1 ส่วน (...args) => {} เป็น 0 ดังนั้น curry ที่อิง arity จึงต้องได้รับฟังก์ชันที่มีพารามิเตอร์แบบเรียบง่าย Composition คือขั้นถัดไป: การประกอบฟังก์ชัน pure เล็ก ๆ เข้าเป็น pipeline ที่อ่านง่าย

JAVASCRIPT
const pipe = (...fns) => (input) =>
  fns.reduce((acc, fn) => fn(acc), input);

const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const dashes = (s) => s.replace(/\s+/g, "-");

const toSlug = pipe(trim, lower, dashes);
console.log(toSlug("  Hello World  "));
// "hello-world"
pipe ใช้ฟังก์ชันจากซ้ายไปขวา

pipe(f, g, h)(x) คำนวณ h(g(f(x))) คุณอ่านมันตามลำดับการทำงาน ตัวแปร compose ใช้จากขวาไปซ้าย เหมือนสัญกรณ์ทางคณิตศาสตร์ pipeline เหล่านี้จะมีคุณค่าก็ต่อเมื่อฟังก์ชันเป็น pure เท่านั้น - ไม่มี side effect ไม่มี dependency ที่ซ่อนอยู่ - ซึ่งเรียกร้อง ข้อมูลแบบ immutable: แทนที่จะ mutate อ็อบเจกต์ คุณสร้างเวอร์ชันใหม่ของมัน แล้วความเท่ากันของการอ้างอิงก็เพียงพอที่จะตรวจจับการเปลี่ยนแปลง (หัวใจของ reconciliation ใน React)

JAVASCRIPT
const state = Object.freeze({
  user: { name: "Ada" },
  tags: ["js"]
});

const next = {
  ...state,
  tags: [...state.tags, "fp"]
};

const deepCopy = structuredClone(state);
deepCopy.user.name = "Grace"; // independent copy
เวอร์ชันใหม่ผ่านการคัดลอกแทนการ mutate

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. อะไรคือความแตกต่างระหว่าง currying และ partial application?
    • ไม่มี พวกมันเป็นคำพ้องความหมายกัน
    • currying เปลี่ยน f(a, b, c) เป็น f(a)(b)(c) ส่วน partial application ตรึงอาร์กิวเมนต์บางตัวในการเรียกครั้งเดียว
    • partial application ใช้ได้กับ bind เท่านั้น ส่วน currying ใช้ได้กับ arrow function เท่านั้น
    • currying ใช้ได้กับฟังก์ชันที่มีสองอาร์กิวเมนต์เท่านั้น
  2. pipe(f, g, h)(x) เทียบเท่ากับอะไร?
    • f(g(h(x)))
    • h(g(f(x)))
    • g(f(h(x)))
    • f(x) + g(x) + h(x)
  3. หลังจาก const s = Object.freeze({ a: { b: 1 } }) การกำหนดค่า s.a.b = 2 ทำงานหรือไม่?
    • ไม่ freeze แช่แข็งทั้งต้นไม้ของอ็อบเจกต์
    • ไม่ TypeError จะถูกโยนแม้อยู่นอกโหมด strict
    • ใช่ เพราะ freeze เป็นแบบตื้น: อ็อบเจกต์ที่ซ้อนอยู่ s.a ไม่ได้ถูกแช่แข็ง
    • ใช่ แต่เฉพาะในโหมดที่ไม่ใช่ strict เท่านั้น