بازگشت مد کلاسیک در TS: خود مرجع از نوع تابع

این پست تمرکز بر قسمت پایانی پست است؟ چالش بازگشت
یک راه برای انجام بازگشت، ساخت تابعی است که خودش را دریافت کند.
برای انجام این کار، باید نوع تابع را اعلام کنید تا بتوانید آن را به عنوان مرجع ارسال کنید:
export type RecFunc = (n: number, f: RecFunc) => number;
export function fix_point(f: RecFunc): (x: number) => number {
return x => f(x, f)
}
و برای پیاده سازی تابعی مانند این (مثلاً تابع فیبوناچی):
import { RecFunc, fix_point } from './genericMemoization.js'
const fibPrimitive = (n: number, selfFib: RecFunc): number => n <= 0? 0: n == 1? 1: selfFib(n-1, selfFib) + selfFib(n-2, selfFib);
const fib = fix_point(fibPrimitive);
console.log(fib(12)); // 144
امکان گسترش آن به هر آرگومان ورودی و خروجی وجود دارد، من فقط در اینجا روی ورودی و خروجی با یک عدد ساده تمرکز کردم.
تابع پیتر آکرمن:
export type RecFunc2 = (m:number, n: number, f: RecFunc) => number;
export function fix_point2(f: RecFunc2): (x: number, y: number) => number {
return (x, y) => f(x, y, f)
}
import { RecFunc2, fix_point2 } from './genericMemoization.js'
const ackPrimitive = (m:number, n: number, selfAck: RecFunc2): number => {
if (m == 0) return n + 1;
if (n == 0) return selfAck(m-1, 1, selfAck);
return selfAck(m-1, selfAck(m, n-1, selfAck), selfAck);
}
const ack = fix_point2(ackPrimitive);
تزئین عملکرد
من می توانم درمان های ساده را به بازگشت اضافه کنم. به عنوان مثال، چاپ به console.log
هر بار که تابع با پارامترهای ارسال شده در سطح صحیح فراخوانی می شود. به عنوان مثال، سطح ورودی 0 است، بنابراین هر پاس یک سطح را افزایش می دهد.
بیایید آن را صدا کنیم param_logger
شیئی که آن را اداره می کند. من می توانم با آن به 2 روش تعامل داشته باشم:
- آنچه را که دریافت کردم ثبت کنم و سطح را چاپ کنم
- دیگری ایجاد کنید
param_logger
از سطح پایین تر
O param_logger
برای برآورده کردن این، شما باید دو عملکرد داشته باشید:
type ParamLogger = {
log: (param: number) => void,
deeper_logger: () => ParamLogger
}
استراتژی که هنوز به اصلاح آن اهمیتی نمی دهد deeper_logger
به سادگی این خواهد بود:
const lyingLogger: ParamLogger = {
log: (param: number) => console.log(`0: ${param}`),
deeper_logger: () => lyingLogger
}
خوب، اکنون برای حفظ سطح به نوعی پارامتر با سطح نیاز دارم. از آنجایی که من آن را واضح نمی دانم، این بدان معناست که باید آن را در محفظه ضبط کنم. بنابراین این بدان معنی است که من به یک تابع برای ایجاد لاگر نیاز دارم. و باید بازگشتی باشد!
const loggerByLevel = (level: number): ParamLogger => ({
log: (param: number) => console.log(`${level}: ${param}`),
deeper_logger: () => loggerByLevel(level + 1)
})
اما من نباید این عملکرد را افشا کنم، درست است؟ من فقط میتونم مال خودم رو افشا کنم baseLogger
. چگونه این کار را انجام دهم؟ از طریق IIFE (بلافاصله عبارت تابع را فراخوانی کرد):
const baseLogger: ParamLogger = (() => {
const loggerByLevel = (level: number): ParamLogger => ({
log: (param: number) => console.log(`${level}: ${param}`),
deeper_logger: () => loggerByLevel(level + 1)
})
return loggerByLevel(0)
})();
حال، برای ماندن در روح این پست، اجازه دهید تابع را به صورت بازگشتی به خودش منتقل کنیم؟
const baseLogger: ParamLogger = (() => {
type ParamLoggerCreator = (level: number, func: ParamLoggerCreator) => ParamLogger
const loggerByLevel: ParamLoggerCreator = (level: number, func: ParamLoggerCreator) => ({
log: (param: number) => console.log(`${level}: ${param}`),
deeper_logger: () => func(level + 1, func)
})
return loggerByLevel(0, loggerByLevel)
})();
حال چگونه با این دکوراسیون کنار بیاییم؟ یک تزیین ساده در مورد فیبوناچی برای چاپ ساده پارامتر:
fibPrimitive(12, (n, self) => {
console.log(n)
return fibPrimitive(n, self)
})
توجه داشته باشید که تابعی که به صورت بازگشتی به جلو منتقل می شود، تابع است self
، که در ابتدا فقط یک تماس است console.log
با عبور فراخوانی تابع اصلی self
. حالا باید عوض کنم self
.
من بر اساس پیاده سازی ساخته شده برای حفظ، شروع، در نهایت تغییر خواهد کرد self
:
function fix_point_log(f: RecFunc): (n: number) => number {
const logging = (n: number, self: RecFunc): number => {
baseLogger.log(n)
return f(n, logging)
}
return n => logging(n, logging)
}
هوم، تا اینجا خوب است، من می گویم. من می توانم نوعی شبیه سازی کنم step
همچنین:
function fix_point_log(f: RecFunc): (n: number) => number {
const logging = (n: number, self: RecFunc): number => {
baseLogger.log(n)
const step = (n: number, s: RecFunc): number => {
return self(n, s)
}
return f(n, step)
}
return n => logging(n, logging)
}
باشه، step
در اینجا یک تابع فلش است. این بدان معناست که اکنون می توانم ویژگی هایی را به آن اضافه کنم:
function fix_point_log(f: RecFunc): (n: number) => number {
const logging = (n: number, self: RecFunc): number => {
baseLogger.log(n)
const step = (n: number, s: RecFunc): number => {
return self(n, s)
}
step.log = baseLogger.deeper_logger()
return f(n, step)
}
return n => logging(n, logging)
}
چه نوع step
در آن زمان؟
type TipoDeStep = RecFunc & { log: ParamLogger }
// ou então equivalentemente...
type TipoDeStep = ((n: number, s: RecFunc) => number) & { log: ParamLogger }
// outra maneira...
type TipoDeStep = {
(n: number, s: RecFunc): number,
log: ParamLogger
}
این نماد سوم نماد “امضای تماس” است (به geeksforgeeks مراجعه کنید). شما حتی نمی توانید مستقیماً یک شی ایجاد کنید قابل فراخوانی از طریق یک تابع فلش، اما TS به شما اجازه می دهد مقدار را بلافاصله بعد از آن وارد کنید. به عنوان مثال، این معتبر است:
type BinOp = {
(a: number, b: number): number,
nome: string
}
const soma: BinOp = (a, b) => a + b
soma.nome = "+"
اما این باطل است:
type BinOp = {
(a: number, b: number): number,
nome: string
}
const soma: BinOp = (a, b) => a + b
// erro de TS
// Property 'nome' is missing in type '(a: number, b: number) => number' but required in type 'BinOp'.ts(2741)
توده! اکنون باید تشخیص دهم که چه زمانی self
پاس از نوع امضای تماس و با ویژگی است log
. ساده ترین راه بازی در نقش نگهبان است!
function isRecFuncEtlog(f: RecFunc): f is RecFunc & { log: ParamLogger } {
return (f as any)["log"] != null
}
با این در حال حاضر من می توانم رهگیری و استفاده از log
:
function fix_point_log(f: RecFunc): (n: number) => number {
const logging = (n: number, self: RecFunc): number => {
if (isRecFuncEtlog(self)) {
self.log.log(n)
return f(n, self)
}
baseLogger.log(n)
const step = (n: number, s: RecFunc): number => {
return self(n, s)
}
step.log = baseLogger.deeper_logger()
return f(n, step)
}
return n => logging(n, logging)
}
آماده! در حال حاضر تنها چیزی که از دست رفته است مرحله فراخوانی زمانی که تابعی با گزارش است. من از همان منطق استفاده خواهم کرد step
، اما اکنون به جای استفاده از baseLogger.deeper_logger()
من استفاده خواهم کرد .deeper_logger()
انجام دهید self.log
:
function fix_point_log(f: RecFunc): (n: number) => number {
const logging = (n: number, self: RecFunc): number => {
if (isRecFuncEtlog(self)) {
self.log.log(n)
const step = (n: number, s: RecFunc): number => {
return self(n, s)
}
step.log = self.log.deeper_logger()
return f(n, step)
}
baseLogger.log(n)
const step = (n: number, s: RecFunc): number => {
return self(n, s)
}
step.log = baseLogger.deeper_logger()
return f(n, step)
}
return n => logging(n, logging)
}