مقاله فنی

LAMBDA و LET در Delphi: کلوژرهای فرمول HotXLS

HotXLS عبارت LAMBDA اکسل را به‌عنوان یک مقدار تابع درجه‌یک واقعی ارزیابی می‌کند. یک نام تعریف‌شده که متن RefersTo آن یک LAMBDA است می‌تواند با نام به‌صورت =MyFunc(5) فراخوانی شود، یک کلوژر متصل‌شده درون LET می‌تواند به‌صورت =LET(f, LAMBDA(x, x*2), f(21)) فراخوانی شود، و محیط لغوی ضبط‌شده در زمان تعریف همراه کلوژر سفر می‌کند. متن فرمول دقیقاً همان‌طور که هست به کتاب‌کار رفت‌وبرگشت می‌کند

این همان ویژگی است که یک موتور فرمول را از یک تجزیه‌گر فرمول جدا می‌کند. هر چیزی پیش از LAMBDA می‌توانست با پیمایش یک درخت از مقادیر ارزیابی شود. LAMBDA به یک پشته دامنه نیاز دارد، و به‌محض داشتن یک پشته دامنه، یک کل رده از منطق صفحه‌گسترده نوشته‌شده توسط کاربر، به‌جای فقط در اکسل، در برنامه Delphi شما هم شروع به کار می‌کند

چرا بیشتر موتورهای غیر اکسلی روی کلیدواژه LAMBDA متوقف می‌شوند؟

چون یک ارزیاب صفحه‌گسترده کلاسیک دقیقاً یک نوع مقدار دارد: یک عدد، یک رشته، یک بولی، یک خطا، یا یک ارجاع به سلول‌هایی که این‌ها را نگه می‌دارند. جایی برای گذاشتن یک تابع وجود ندارد. وقتی اکسل 365 عبارت LAMBDA را معرفی کرد، یک نوع مقدار اضافه کرد که نام‌های پارامتر، یک عبارت بدنه، و بایندینگ‌های قابل‌مشاهده در جایی که نوشته شده را حمل می‌کند. یک موتور بدون آن نوع می‌تواند LAMBDA(x, x*2) را تجزیه کند و متن را ذخیره کند، اما لحظه‌ای که یک سلول تلاش می‌کند آن را فراخوانی کند، چیزی برای فراخوانی وجود ندارد

HotXLS قطعه گمشده را به‌صورت یک مقدار کلوژر به‌همراه یک پشته دامنه زمان‌اجرا پیاده‌سازی می‌کند. فراخوانی یک کلوژر، محیط ضبط‌شده‌اش را push می‌کند، سپس مقادیر آرگومان را زیر نام‌های پارامتر push می‌کند، بدنه را ارزیابی می‌کند، و پشته را تا همان نشانه کوتاه می‌کند. آن ترتیب اهمیت دارد، و بخش بعدی توضیح می‌دهد چرا

سه راهی که یک 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 یک کلوژر را اعمال می‌کند، ابتدا محیط لغوی ضبط‌شده و سپس بایندینگ‌های آرگومان را push می‌کند، پس یک پارامتر به‌نام 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)) همان‌طور که اکسل آن را ارزیابی می‌کند ارزیابی می‌شود

آن کامل‌بودن بیشتر از آنچه به نظر می‌رسد اهمیت دارد. LET همان راهی است که کاربران برای اجتناب از محاسبه دوباره یک زیرعبارت پنج‌بار در یک فرمول از آن استفاده می‌کنند، پس کتاب‌کارهای واقعی آن را دقیقاً در همان شکل‌های عمیقاً تودرتویی استفاده می‌کنند که یک پیاده‌سازی ناقص در آن‌ها اشتباه می‌کند. اگر پیش‌تر با گسترش بایندینگ‌های LET پیش از ارزیابی، این شکاف‌ها را دور می‌زدید، آن دورزدن دیگر لازم نیست

کاما یا نقطه‌ویرگول: هردو، حالا

متن فرمول در HotXLS اکنون کاما را به‌همراه نقطه‌ویرگول کلاسیک به‌عنوان جداکننده آرگومان می‌پذیرد. این یک تنظیم locale نیست؛ یک قاعده پذیرش در تجزیه‌گر است. اهمیت دارد چون فرمول‌ها از جاهایی می‌رسند که شما کنترلی بر آن‌ها ندارید: چسبانده‌شده از یک تیکت پشتیبانی، کپی‌شده از مستندات، تولیدشده توسط یک اسکریپت که نحو متعارف اکسل را صادر کرده، وارد‌شده از یک CSV از رشته‌های فرمول

اثر عملی این است که هر دوی SUM(A1,A2) و SUM(A1;A2) کامپایل می‌شوند. رفت‌وبرگشت هرچه را که مبدأ استفاده کرده حفظ می‌کند، پس یک کتاب‌کاری که بارگذاری کرده‌اید با جداکننده‌های اصلی‌اش پس‌نوشته می‌شود، نه اینکه پشت سر کاربر یکسان‌سازی شود

چه چیزی رفت‌وبرگشت می‌کند، و چه چیزی را باید بررسی کرد

متن فرمول عیناً ذخیره می‌شود، پس یک LAMBDA در یک نام تعریف‌شده یک چرخه بارگذاری و ذخیره را دست‌نخورده پشت سر می‌گذارد و در اکسل به‌عنوان همان تابع باز می‌شود. یک LAMBDA خام که به‌عنوان نتیجه یک سلول ذخیره شده، یعنی فرمولی که به یک کلوژر ارزیابی می‌شود نه به یک مقدار، رفتار موجود skip-without-value را حفظ می‌کند: متن حفظ می‌شود، هیچ نتیجه عددی کش‌شده‌ای برای آن اختراع نمی‌شود. آن نتیجه صادقانه است، چون هیچ اسکالری برای کش کردن وجود ندارد

دو عادت ارزش پذیرفتن دارند. به لامبداهای نام‌دار دامنه کتاب‌کار بدهید مگر اینکه دلیلی برای نه‌دادن وجود داشته باشد، چون یک تابع با دامنه شیت که وقتی یک شیت کپی می‌شود ناپدید می‌شود، یک خطای نام را در جایی دور از علت تولید می‌کند؛ قواعد دامنه در نام‌های تعریف‌شده و فرمول‌های بین‌شیتی پوشش داده شده است. و وقتی یک کتاب‌کار پر از لامبداهای نام‌دار برای یک گزارش که باید پایدار باشد مقصد دارد، منجمدکردن نتایج با ConvertFormulasToValues را در نظر بگیرید تا مصرف‌کنندگان پایین‌دست به‌جای توابعی که ممکن است پشتیبانی نکنند، عددها را ببینند

برای بازمحاسبه سنگین، بدنه‌های LAMBDA عبارت‌های معمولی در گراف وابستگی هستند و مثل هر فرمول دیگری زمان‌بندی می‌شوند، که در بازمحاسبه افزایشی و گراف وابستگی توضیح داده شده است. اگر مدل شما یک تابع نام‌دار را در سراسر هزاران ردیف فراخوانی می‌کند، هزینه بدنه است نه ماشین‌آلات فراخوانی، و همان توصیه بهینه‌سازی برای هر فرمول تکرارشونده‌ای اعمال می‌شود

HotXLS یک کامپوننت بومی صفحه‌گسترده برای Delphi و C++Builder است که فایل‌های XLS، XLSX و ODS را بدون Excel یا هرگونه اتوماسیون Office می‌خواند و می‌نویسد. موتور فرمول، نام‌های تعریف‌شده و API بازمحاسبه در صفحه کامپوننت صفحه‌گسترده Delphi HotXLS مستند شده‌اند