บทความเทคนิค

LAMBDA และ LET ใน Delphi: โคลเชอร์สูตรของ HotXLS

HotXLS ประมวลผล LAMBDA ของ Excel เป็นค่าฟังก์ชันชั้นหนึ่งอย่างแท้จริง ชื่อที่กำหนดไว้ซึ่งข้อความ RefersTo เป็น LAMBDA สามารถเรียกด้วยชื่อได้เป็น =MyFunc(5) โคลเชอร์ที่ผูกไว้ภายใน LET สามารถเรียกได้เป็น =LET(f, LAMBDA(x, x*2), f(21)) และสภาพแวดล้อมตามขอบเขตที่จับไว้ตอนนิยามจะติดไปกับโคลเชอร์ด้วย ข้อความสูตรจะเขียนกลับเข้าเวิร์กบุ๊กเหมือนเดิมทุกประการ

นี่คือฟีเจอร์ที่แยกเอนจินสูตรออกจากตัวแยกวิเคราะห์สูตรธรรมดา ทุกอย่างก่อน LAMBDA สามารถประมวลผลได้ด้วยการเดินผ่านต้นไม้ของค่าต่าง ๆ แต่ LAMBDA ต้องการสแตกขอบเขต และเมื่อคุณมีสแตกขอบเขตแล้ว ตรรกะสเปรดชีตที่ผู้ใช้เขียนขึ้นทั้งกลุ่มก็เริ่มทำงานได้ในแอปพลิเคชัน Delphi ของคุณ ไม่ใช่เฉพาะใน Excel เท่านั้น

เหตุใดเอนจินที่ไม่ใช่ Excel ส่วนใหญ่จึงหยุดอยู่แค่คำสั่ง LAMBDA

เพราะตัวประมวลผลสเปรดชีตแบบคลาสสิกมีค่าอยู่เพียงชนิดเดียวเท่านั้น คือตัวเลข สตริง บูลีน ข้อผิดพลาด หรือการอ้างอิงไปยังเซลล์ที่เก็บสิ่งเหล่านั้น ไม่มีที่ให้เก็บฟังก์ชันเลย เมื่อ Excel 365 นำ LAMBDA เข้ามา มันได้เพิ่มชนิดค่าที่พกชื่อพารามิเตอร์ นิพจน์เนื้อหา และการผูกค่าที่มองเห็นได้ ณ จุดที่มันถูกเขียนขึ้นมาด้วย เอนจินที่ไม่มีชนิดนี้สามารถแยกวิเคราะห์ LAMBDA(x, x*2) และเก็บข้อความไว้ได้ แต่ทันทีที่เซลล์พยายามเรียกใช้มัน ก็ไม่มีอะไรให้เรียกเลย

HotXLS สร้างส่วนที่ขาดหายไปนี้ขึ้นมาเป็นค่าโคลเชอร์บวกกับสแตกขอบเขตแบบรันไทม์ การเรียกโคลเชอร์จะพุชสภาพแวดล้อมที่จับไว้ก่อน แล้วพุชค่าอาร์กิวเมนต์ภายใต้ชื่อพารามิเตอร์ ประมวลผลเนื้อหา แล้วตัดสแตกกลับไปยังจุดที่มาร์กไว้ ลำดับนี้สำคัญมาก และหัวข้อถัดไปจะอธิบายว่าทำไม

สามวิธีที่ LAMBDA ถูกเรียกใช้

HotXLS แก้ปัญหาการเรียกชื่อฟังก์ชันที่ไม่รู้จักผ่านสามเส้นทาง ซึ่งลองตามลำดับ และการรู้ว่าเส้นทางไหนถูกทริกเกอร์จะอธิบายความประหลาดใจส่วนใหญ่ได้ อย่างแรกคือชื่อที่ผูกไว้ในขอบเขต LET หรือ LAMBDA ปัจจุบัน: ถ้า f เป็นการผูกค่าภายในที่เก็บโคลเชอร์อยู่ f(21) จะเรียกใช้มัน อย่างที่สองคือชื่อที่กำหนดไว้ในเวิร์กบุ๊กซึ่งข้อความสูตรขึ้นต้นด้วย LAMBDA: MyFunc(5) จะคอมไพล์เนื้อหาของชื่อนั้นแล้วเรียกใช้ อย่างที่สามคือตัวจัดการฟังก์ชันผู้ใช้แบบคลาสสิกที่ไม่เปลี่ยนแปลง สำหรับทุกอย่างที่สองเส้นทางแรกไม่รับผิดชอบ

การผูกค่าภายในที่เก็บสิ่งอื่นที่ไม่ใช่โคลเชอร์นั้นเรียกใช้ไม่ได้ ถ้าคุณผูก f เข้ากับเลข 3 แล้วเขียน f(21) คุณจะได้ข้อผิดพลาดเรื่องค่า ไม่ใช่การพยายามคูณ นี่เข้มงวดกว่าที่ภาษาแบบไดนามิกจะเป็น และเป็นความตั้งใจ: การสะกดผิดที่เปลี่ยนการเรียกฟังก์ชันให้กลายเป็นการอ้างอิงโดยไม่ตั้งใจ คือคำตอบที่ผิดแบบเงียบ ๆ ซึ่งเป็นผลลัพธ์ที่แย่ที่สุดที่เอนจินสเปรดชีตจะสร้างขึ้นได้

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Model');

    // ฟังก์ชันที่มีชื่อและใช้ซ้ำได้ ขอบเขตระดับเวิร์กบุ๊ก
    Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');

    Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';

    // โคลเชอร์ที่ผูกและเรียกใช้ภายในสูตรเดียว
    Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';

    // LET แบบซ้อน: การผูกค่าทุกตัวมองเห็นได้จากตัวที่อยู่หลังมัน
    Sheet.Cells[4, 2].Formula :=
      'LET(base, 100, bump, LAMBDA(v, v+base), LET(step, bump(5), step*2))';

    Book.Recalculate;
    Book.SaveAs('lambda-model.xlsx');
  finally
    Book.Free;
  end;
end;

การบดบังทำงานอย่างไรเมื่อชื่อชนกัน

พารามิเตอร์ชนะเสมอ เมื่อ HotXLS เรียกใช้โคลเชอร์ มันจะพุชสภาพแวดล้อมตามขอบเขตที่จับไว้ก่อน แล้วพุชการผูกอาร์กิวเมนต์ทีหลัง ดังนั้นพารามิเตอร์ชื่อ rate จะบดบังการผูกค่าภายนอกที่ชื่อ rate เช่นกัน และยังบดบังการอ้างอิงคอลัมน์ที่สะกดเหมือนกันในสูตรโดยรอบด้วย ลำดับแบบนี้เองที่ทำให้ฟังก์ชันแบบมีชื่อปลอดภัยต่อการใช้ซ้ำ ผู้เรียกใช้ไม่สามารถเปลี่ยนความหมายของเนื้อหาโดยไม่ตั้งใจได้ด้วยการมีตัวแปรที่ชื่อคล้ายกันอยู่ในขอบเขต

จำนวนอาร์กิวเมนต์จะถูกตรวจสอบก่อนที่จะมีการประมวลผลใด ๆ การเรียกที่จำนวนอาร์กิวเมนต์ไม่ตรงกับจำนวนพารามิเตอร์ของโคลเชอร์จะคืนค่าข้อผิดพลาดทันที แทนที่จะประมวลผลอาร์กิวเมนต์บางตัวแล้วค่อยล้มเหลว ซึ่งทำให้การประมวลผลที่ปราศจากผลข้างเคียงยังคงปราศจากงานค้างจริง ๆ สแตกขอบเขตจะถูกตัดกลับไปยังจุดเริ่มต้นภายในบล็อก finally ดังนั้นข้อผิดพลาดภายในเนื้อหาจะไม่ทิ้งการผูกค่าเก่าไว้ให้มองเห็นได้จากสูตรถัดไป

var
  Book: TXLSXWorkbook;
  Name: TXLSXDefinedName;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('customer-model.xlsx') = 1 then
    begin
      // ตรวจสอบสิ่งที่ผู้ใช้เขียนไว้ก่อนที่จะเชื่อการคำนวณใหม่
      Name := Book.DefinedNames.FindByName('NetOf');
      if (Name <> nil) and
         (UpperCase(Copy(Name.Formula, 1, 6)) = 'LAMBDA') then
        Log('Named lambda found: ' + Name.Formula);

      Book.Recalculate;
      Log(VarToStr(Book.Sheets[1].Cells[2, 2].Value));
    end;
  finally
    Book.Free;
  end;
end;

LET ไม่ใช่การใช้งานแบบครึ่ง ๆ กลาง ๆ อีกต่อไป

HotXLS รุ่นก่อนหน้าใช้งาน LET เพียงพอสำหรับรับมือกับกรณีการผูกค่าเดียวที่พบทั่วไปเท่านั้น การใช้งานปัจจุบันสมบูรณ์แล้ว: การผูกค่าทุกตัวมองเห็นได้จากการผูกค่าทั้งหมดที่ตามมาและจากนิพจน์เนื้อหา และ LET แบบซ้อนกันประกอบกันได้ตามปกติ ดังนั้น LET(a, 1, b, a+1, LET(c, b*2, c)) จะประมวลผลได้แบบเดียวกับที่ Excel ประมวลผล

ความสมบูรณ์นี้สำคัญกว่าที่ฟังดู LET คือวิธีที่ผู้ใช้หลีกเลี่ยงการคำนวณนิพจน์ย่อยเดิมซ้ำห้าครั้งในสูตรเดียว ดังนั้นเวิร์กบุ๊กจริงจึงใช้มันในรูปแบบที่ซ้อนกันลึกพอดีกับที่การใช้งานแบบครึ่ง ๆ กลาง ๆ มักทำผิด ถ้าคุณเคยแก้ปัญหาช่องว่างนี้ด้วยการขยายการผูกค่าของ LET ก่อนประมวลผล ตอนนี้คุณเลิกทำแบบนั้นได้แล้ว

จุลภาคหรืออัฒภาค: ตอนนี้ใช้ได้ทั้งคู่

ข้อความสูตรใน HotXLS ตอนนี้ยอมรับจุลภาคเป็นตัวคั่นอาร์กิวเมนต์ควบคู่ไปกับอัฒภาคแบบคลาสสิก นี่ไม่ใช่การตั้งค่าตามโลแคล แต่เป็นกฎการยอมรับในตัวแยกวิเคราะห์ มันสำคัญเพราะสูตรมาจากแหล่งที่คุณควบคุมไม่ได้: วางมาจากตั๋วซัพพอร์ต คัดลอกมาจากเอกสารประกอบ สร้างขึ้นโดยสคริปต์ที่เขียนไวยากรณ์แบบมาตรฐานของ Excel หรือนำเข้าจาก CSV ของสตริงสูตร

ผลในทางปฏิบัติคือทั้ง SUM(A1,A2) และ SUM(A1;A2) คอมไพล์ได้ทั้งคู่ การเขียนกลับจะรักษาตัวคั่นที่ต้นฉบับใช้ไว้ ดังนั้นเวิร์กบุ๊กที่คุณโหลดมาจะถูกเขียนกลับด้วยตัวคั่นดั้งเดิม แทนที่จะถูกทำให้เป็นมาตรฐานเดียวกันโดยที่ผู้ใช้ไม่รู้ตัว

อะไรเขียนกลับได้ และอะไรควรตรวจสอบ

ข้อความสูตรถูกเก็บไว้เหมือนเดิมทุกประการ ดังนั้น LAMBDA ในชื่อที่กำหนดไว้จะรอดผ่านรอบการโหลดและบันทึกได้ครบถ้วน และเปิดใน Excel ในฐานะฟังก์ชันเดียวกัน LAMBDA เปล่าที่เก็บไว้เป็นผลลัพธ์ของเซลล์ หมายถึงสูตรที่ประมวลผลได้เป็นโคลเชอร์แทนที่จะเป็นค่า จะคงพฤติกรรมข้ามไปโดยไม่มีค่าแบบเดิมไว้: ข้อความถูกเก็บรักษาไว้ แต่ไม่มีการสร้างผลลัพธ์ตัวเลขที่แคชไว้ปลอม ๆ ให้ นั่นคือผลลัพธ์ที่ตรงไปตรงมา เพราะไม่มีค่าสเกลาร์ให้แคชอยู่แล้ว

มีสองนิสัยที่ควรนำมาใช้ ให้แลมบ์ดาแบบมีชื่อมีขอบเขตระดับเวิร์กบุ๊ก เว้นแต่จะมีเหตุผลที่ไม่ทำเช่นนั้น เพราะฟังก์ชันที่มีขอบเขตระดับชีตซึ่งหายไปเมื่อชีตถูกคัดลอก จะสร้างข้อผิดพลาดเรื่องชื่อในจุดที่ห่างไกลจากสาเหตุ กฎเรื่องขอบเขตอธิบายไว้ใน ชื่อที่กำหนดไว้และสูตรข้ามชีต และเมื่อเวิร์กบุ๊กที่เต็มไปด้วยแลมบ์ดาแบบมีชื่อมีปลายทางเป็นรายงานที่ต้องเสถียร ควรพิจารณาตรึงผลลัพธ์ด้วย ConvertFormulasToValues เพื่อให้ผู้บริโภคปลายทางเห็นตัวเลขแทนฟังก์ชันที่พวกเขาอาจไม่รองรับ

สำหรับการคำนวณใหม่หนัก ๆ เนื้อหาของ LAMBDA เป็นเพียงนิพจน์ธรรมดาในกราฟการพึ่งพา และถูกจัดคิวเหมือนสูตรอื่น ๆ ทุกตัว ตามที่อธิบายไว้ใน การคำนวณใหม่แบบเพิ่มขึ้นและกราฟการพึ่งพา ถ้าโมเดลของคุณเรียกฟังก์ชันที่มีชื่อตัวเดียวข้ามหลายพันแถว ต้นทุนอยู่ที่เนื้อหาฟังก์ชัน ไม่ใช่กลไกการเรียกใช้ และคำแนะนำการปรับให้เหมาะสมแบบเดียวกันก็ใช้ได้กับสูตรที่ทำซ้ำใด ๆ

HotXLS เป็นคอมโพเนนต์สเปรดชีตแบบเนทีฟสำหรับ Delphi และ C++Builder ที่อ่านและเขียน XLS, XLSX และ ODS ได้โดยไม่ต้องใช้ Excel หรือระบบอัตโนมัติของ Office ใด ๆ เอนจินสูตร ชื่อที่กำหนดไว้ และ API การคำนวณใหม่มีเอกสารอธิบายไว้ที่ หน้าคอมโพเนนต์สเปรดชีตสำหรับ Delphi ของ HotXLS