<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Ghasem Shirdel</title>
        <link>https://virgool.io/feed/@ghasemdev</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-16 20:41:55</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1147886/avatar/DykR4K.jpeg?height=120&amp;width=120</url>
            <title>Ghasem Shirdel</title>
            <link>https://virgool.io/@ghasemdev</link>
        </image>

                    <item>
                <title>Ktor Client Pipelines – Deep Dive</title>
                <link>https://virgool.io/@ghasemdev/ktor-client-pipelines-%E2%80%93-deep-dive-eihqkuuhcja3</link>
                <description>Interceptor من کجاست 🧐؟خب، احتمالا اولین چیزی که بعد از مهاجرت از Retrofit و OkHttp باهاش مواجه می‌شین اینه که چطوری می‌تونین Interceptorهای شخصی‌سازی‌شده‌ی خودتون رو بیارین و استفاده کنین. بعضی از این Interceptorها این قابلیت رو دارن که هنوز هم داخل همون ساختار قبلی کار کنن و فقط به OkHttp اضافه بشن، و در نهایت کنار پلاگین‌های Ktor هم قرار بگیرن.fun provideKtorClient(): HttpClient {
    return HttpClient(OkHttp) {
        engine {
            preconfigured = OkHttpClient.Builder()
                .addInterceptor(SimpleHeaderInterceptor())
                .build()
        }
        ...
    }
}

class SimpleHeaderInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()

        val newRequest = originalRequest.newBuilder()
            .addHeader(&quot;X-Simple-Interceptor&quot;, &quot;HelloFromOkHttp&quot;)
            .build()

        return chain.proceed(newRequest)
    }
}اما برای بعضی از Interceptorها این روش ممکنه جواب نده، و دلیلش هم ساختار متفاوت Ktor هست. پس بیاین قبلش یه نگاه بندازیم ببینیم پشت‌صحنه چه اتفاقی می‌افته 😁توی Ktor ما یه سری Pipeline داریم که هر کدوم چندتا Phase دارن. کدهای Interceptorهای ما روی این فازها و با یه ترتیب مشخص اجرا می‌شن که جلوتر با هر کدومشون آشنا می‌شیم.Pipeline اصن چی هست؟توی Ktor Client هیچ کاری یهویی اتفاق نمی‌افته و هر request/response یه مسیر مشخص رو دنبال می‌کنه که شبیه یه سری لوله (Pipeline) هستن. هر کدوم از این لوله‌ها یه سری نقاط دارن که با یه اسم خاص مشخص شدن و بهشون می‌گیم Phase.وقتی توی Ktor می نویسیمclient.get&lt;MyDto&gt;(&quot;/api&quot;)این Pipeline ها پشت زمینه اجرا میشن┌────────────────────┐
│ HttpRequestPipeline│
└─────────┬──────────┘
          ↓
┌────────────────────┐
│  HttpSendPipeline  │
└─────────┬──────────┘
          ↓
    🌐 NETWORK 🌐
          ↓
┌────────────────────┐
│HttpResponsePipeline│
└─────────┬──────────┘
          ↓
┌────────────────────┐
│HttpReceivePipeline │
└────────────────────┘1️⃣ HttpRequestPipeline – اینجا درخواست ساخته میشهتوی این Pipeline هنوز خبری از شبکه نیست و درخواست‌ها تازه دارن شکل می‌گیرن. پلاگین‌هایی مثل ContentNegotiation و ContentEncoding توی این Pipeline دیتا کلاس‌های درخواست رو به JSON تبدیل می‌کنن و بعدش بسته به نیاز، zip هم می‌کنن.همچنین پلاگین‌هایی مثل HttpTimeout اینجا هستن که زمان طول کشیدن یک درخواست رو اندازه می‌گیرن تا اگه طول کشید، timeout بدن. پلاگینی مثل HttpRequestRetry هم تصمیم می‌گیره درخواست دوباره فرستاده بشه یا نه، و حتی Auth توکن‌های JWT رو به هدر درخواست اضافه می‌کنه.پس می‌تونیم نتیجه بگیریم که اگه قراره یه پلاگین درخواست‌ها رو دستکاری کنه (Body، Header) یا Chain رو دوباره صدا بزنه، این Pipeline دقیقاً برای همین کار طراحی شده.خب، حالا بریم سراغ فازهای این Pipeline 😎این فاز ها به ترتیب اجرا میشنBefore     → آمادهسازی پایه
State      → اضافه شدن رفتار و متادیتا
Transform  → تبدیل مدل به فرم قابل ارسال
Render     → ساخت bytes نهایی
Send       → تحویل به شبکه1️⃣ Before📍 قبل از اینکه request جدی شکل بگیرهمقداردهی های اولیه روی request انجام میشهتنظیمات پیش فرض کلاینت روی request اعمال میشهبعضی اعتبارسنجی های پایه قبل از ادامه ی مسیر انجام میشناگر از همین اول مشکلی باشه، request میتونه fail بشهخروجی این فاز:➡️ یک request خام ولی با تنظیمات پایه2️⃣ State📍 اضافه شدن state و رفتار به requestپلاگینها به request «رفتار» اضافه میکننهدرها، کوکی ها، retry policy، auth و… اینجا attach میشنrequest از حالت صرفاً دادهای خارج میشه و قابل ارسال میشهخروجی این فاز:➡️ request کامل شده از نظر متادیتا و state3️⃣ Transform📍 تبدیل logical body به body قابل ارسالbody از مدل منطقی (DTO / Object) تبدیل میشههنوز به bytes نرسیده ولی فرمت مشخص میشه (json، form، …)negotiation محتوا اینجا انجام میشهخروجی این فاز:➡️ body آمادهی render شدن4️⃣ Render📍 تبدیل نهایی body به داده ی قابل ارسالbody به شکل نهایی (bytes / stream) درمیادrequest از نظر ساختار کاملاً نهایی میشهبعد از این مرحله، body دیگه نباید تغییر کنهخروجی این فاز:➡️ request نهایی برای ارسال5️⃣ Send📍 لحظه ی تحویل request به enginerequest به HttpSendPipeline سپرده میشهtimeout، retry نهایی، validation یا تغییرات لحظه ی آخر اعمال میشهاز این نقطه به بعد وارد network میشیمخروجی این فاز:➡️ ارسال واقعی request به شبکه 🌐مثال ساده:client.requestPipeline.intercept(HttpRequestPipeline.Transform) {     
    println(&quot;Request body is still object: ${context.body}&quot;)     
    proceed() 
}2️⃣ HttpSendPipeline – اینجا request فرستاده میشهتوی این Pipeline خودش دیگه تغییر روی درخواست انجام نمی‌شه و در واقع آخرین فرصت قبل از رفتن به شبکه هست. ولی به جاش کارهایی مثل گرفتن لاگ‌های مورد نیاز، اعمال SSL Pinning روی Engineهای مربوطه (مثل OkHttp، CIO یا Darwin) یا حتی سناریوهای cache کردن انجام می‌شه تا درخواست‌هایی که لازم نیست دوباره فرستاده بشن، skip بشن.اینجا هم این فاز ها به ترتیب اجرا میشنBefore     → آخرین آمادهسازی
State      → تثبیت وضعیت ارسال
Monitoring → مشاهده و لاگ
Engine     → ارتباط واقعی با شبکه
Receive    → تحویل response1️⃣ Before📍 آخرین نقطه قبل از ورود به sendآخرین آماده سازی ها قبل از ارسال انجام میشهrequest هنوز قابل تغییر جزئیهاگر اینجا fail بشه، اصلاً وارد engine نمیشیمخروجی:➡️ request آماده ی تحویل به sender2️⃣ State📍 قفل شدن وضعیت request برای ارسالrequest وارد حالت «در حال ارسال» میشهstateهای لازم برای send attach میشنکوکی ها نهایی میشنخروجی:➡️ request با state پایدار برای ارسال3️⃣ Monitoring📍 مشاهده و ثبت فرآیند ارسالقبل و بعد از send، رویدادها ثبت میشنبرای لاگ، متریک یا دیباگروی رفتار request اثر نمیذاره، فقط observe میکنهخروجی:➡️ request بدون تغییر، ولی تحت مانیتورینگ4️⃣ Engine📍 ورود به لایه ی شبکه واقعیrequest تحویل engine میشهTLS / SSL / socket / connection اینجا اتفاق میافتهارتباط واقعی با سرور برقرار میشهخروجی:➡️ response خام از شبکه5️⃣ Receive📍 تحویل response به pipeline بعدیresponse گرفته شده از engine برگردونده میشهکنترل از send خارج میشهوارد HttpResponsePipeline میشیمخروجی:➡️ response خام برای پردازش بعدی3️⃣ HttpResponsePipeline – ریسپانس خاماینجا تازه ریسپانس خام از شبکه میاد و توی این Pipeline، ContentNegotiation ریسپانس رو به DTOها تبدیل می‌کنه. اگه از HttpCallValidator استفاده کرده باشیم، قبل از اینکه ریسپانس serialize بشه، اعتبارسنجی می‌شه و می‌شه بر اساس status code، خطای سرور پرتاب کرد.همچنین لاگ‌گیری روی ریسپانس‌ها توسط پلاگین Logging انجام می‌شه.HttpClient {
  HttpResponseValidator {
    validateResponse { response -&gt;
      if (!response.status.isSuccess()) {
        throw ServerException()
      }
    }
  }
}Receive       → مانیتورینگ 
Parse         → خواندن stream
Transform     → تبدیل به مدل
State         → تثبیت وضعیت
After         → پایان lifecycle1️⃣ Receive📍 ثبت و مشاهده ی responseرویداد دریافت response اعلام میشهبرای لاگ و مانیتورینگهیچ تغییری روی محتوا انجام نمیشهخروجی:➡️ response خام بدون تغییر2️⃣ Parse📍 خواندن response از حالت streamresponse از stream شبکه خارج میشهbody قابل خواندن میشههنوز تبدیل معنایی انجام نشدهخروجی:➡️ داده ی خام قابل پردازش (bytes / text)3️⃣ Transform📍 تبدیل body خام به مدل منطقیbody به مدل مورد انتظار تبدیل میشهnegotiation محتوا انجام میشهاینجا response معنی دار میشهخروجی:➡️ response با body تبدیل شده4️⃣ State📍 نهایی سازی وضعیت responsestateهای response ست میشنresponse آماده ی تحویل به caller میشهlifecycle call تکمیل میشهخروجی:➡️ response پایدار و نهایی5️⃣ After📍 پاکسازی و کارهای انتهاییcleanup منابعhook های بعد از اتمام callآخرین نقطه برای observe responseخروجی:➡️ پایان چرخه ی response4️⃣ HttpReceivePipeline – تبدیل به خروجی نهاییاین Pipeline، آخرین مرحله‌ست؛ جایی که ریسپانس پردازش شده تبدیل می‌شه به چیزی که caller واقعاً دریافت می‌کنه. بخشی از پلاگین HttpCache هم توی این Pipeline کار می‌کنه و تصمیم می‌گیره که باید از ریسپانس کش شده استفاده بشه یا نه.علاوه بر این، پلاگین‌هایی مثل ContentEncoding، Logging و HttpCookies هم یه سری عملیات خودشون رو توی این Pipeline انجام می‌دن.Before      → تغییرات بنیادین (قبل از state)
State       → اعمال state و encoding
After       → پایان دریافت1️⃣ Before📍 اولین برخورد منطقی با responseکارهایی که باید قبل از هر state یا cache انجام بشنresponse هنوز قابل تغییر محتواییهمناسب برای تغییرات بنیادین روی bodyخروجی:➡️ response قابل ادامه ی پردازش2️⃣ State📍 اعمال state نهایی روی responsehookهای مربوط به response اینجا اجرا میشنencoding نهایی اعمال میشهکوکی ها به روزرسانی میشنresponse برای مصرف آماده میشهخروجی:➡️ response با state کامل3️⃣ After📍 پایان چرخه ی دریافتcleanup نهاییپایان lifecycle request/responseتحویل به callerخروجی:➡️ response تحویلی به لایه ی بالاترکلاس های Phase و Pipelineهمون‌طور که تو بخش قبل یاد گرفتیم، ساختار داخلی پلاگین‌های Ktor به این شکله که یه‌سری Pipeline داریم که هر کدوم از چند تا فاز تشکیل شدن. فازهای Default اسم‌های مشخصی دارن و با استفاده از همین اسم‌ها می‌تونیم فازهای custom خودمون رو قبل یا بعد از اون‌ها اضافه کنیم. این کار باعث می‌شه پلاگینی که می‌سازیم کمترین تداخل رو با بقیه پلاگین‌ها داشته باشه. برای انجام این کار هم می‌تونیم از دو تا تابع insertPhaseAfter و insertPhaseBefore استفاده کنیم.class Pipeline&lt;TContext, TSubject&gt; {
    fun intercept(
        phase: PipelinePhase, 
        block: suspend PipelineContext&lt;TSubject, TContext&gt;.() -&gt; Unit,
    )
    fun insertPhaseAfter(ref: PipelinePhase, phase: PipelinePhase)
    fun insertPhaseBefore(ref: PipelinePhase, phase: PipelinePhase)
}

class PipelinePhase(val name: String)

abstract class PipelineContext&lt;TSubject, TContext&gt; {
    val subject: TSubject
    val context: TContext

    suspend fun proceed()
    suspend fun proceedWith(subject: TSubject)
}حالا توی مرحله‌ی بعدی می‌تونیم به این فازها intercept اضافه کنیم تا وقتی درخواست به اون مرحله رسید، این قطعه کد اجرا بشه. برای این‌که از یه فاز به فاز بعدی منتقل بشیم، باید داخل بدنه‌ی interceptor تابع proceed رو صدا بزنیم. اگه proceed صدا زده نشه، pipeline همون‌جا متوقف می‌شه و دیگه کدهای downstream اجرا نمی‌شن.نکته‌ی مهم: interceptorهایی که به یه فاز اضافه می‌شن، به ترتیبی که intercept شدن اجرا می‌شن. به همین خاطر ترتیب صدا زدن install(plugin)ها خیلی مهمه و می‌تونه روی نتیجه‌ی نهایی تأثیر بذاره.Pipeline
 ├── Phase 1
 │     ├── interceptor A
 │     ├── interceptor B
 │
 ├── Phase 2
 │     ├── interceptor C
 │
 └── Phase 3
       ├── interceptor Dدر ادامه، یه مثال واقعی از دو حالت استفاده از فازهای Custom و Default می‌بینیم.اینجا توی پلاگین DefaultRequest که base URL رو مشخص کردیم، در واقع داریم به RequestPipeline و فاز Before یه intercept اضافه می‌کنیم. این intercept برای همه‌ی درخواست‌ها اجرا می‌شه و وظیفه‌ش اینه که base URL رو به درخواست اضافه کنه. نکته‌ی مهمش هم اینه که این کار قبل از هر فاز دیگه و عملاً قبل از اجرای هر پایپ‌لاین انجام می‌شه.val client = HttpClient {
  defaultRequest {
    contentType(ContentType.Application.Json)
    url(&quot;https://example.com&quot;)
  }
}

// Internal code
public class DefaultRequest private constructor(private val block: DefaultRequestBuilder.() -&gt; Unit) {
    public companion object Plugin : HttpClientPlugin&lt;DefaultRequestBuilder, DefaultRequest&gt; {
        override val key: AttributeKey&lt;DefaultRequest&gt; = AttributeKey(&quot;DefaultRequest&quot;)

        override fun prepare(block: DefaultRequestBuilder.() -&gt; Unit): DefaultRequest = DefaultRequest(block)

        override fun install(plugin: DefaultRequest, scope: HttpClient) {
            scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
                // logic
            }
        }
    }
}توی این مثال، پلاگین HttpCallValidator یه کاری که می‌کنه اینه که توی یکی از بخش‌هاش یه فاز سفارشی با اسم BeforeReceive می‌سازه و اون رو قبل از HttpResponsePipeline.Receive اضافه می‌کنه. در واقع این فاز، اولین فازی هست که بعد از رسیدن response اجرا می‌شه. بعد از اینکه کل Pipeline از این فاز به بعد اجرا شد، اگه توی این مسیر خطایی اتفاق بیفته، callback مربوط به handleResponseExceptionWithRequest صدا زده می‌شه.HttpClient {
  HttpResponseValidator {
    handleResponseExceptionWithRequest { request, cause -&gt;
      if (cause is ResponseException) {
        val response = cause.response
        val status = response.status

        when (status.value) {
          401 -&gt; {
            throw AuthExpiredException(url = request.url.encodedPath, statusCode = status.value)
          }

          403 -&gt; {
            throw AccessDeniedException(message = &quot;Access denied to ${request.url.encodedPath}&quot;)
          }

          in 500..599 -&gt; {
            throw ServerException(code = status.value, endpoint = request.url.encodedPath)
          }

          else -&gt; {
            throw cause
          }
        }
      }

      throw cause
    }
  }
}

// Internal code
// ... → BeforeReceive → Receive → Parse → Transform → ...
internal object ReceiveError : ClientHook&lt;suspend (HttpRequest, Throwable) -&gt; Throwable?&gt; {
  override fun install(client: HttpClient, handler: suspend (HttpRequest, Throwable) -&gt; Throwable?) {
    val BeforeReceive = PipelinePhase(&quot;BeforeReceive&quot;)
    client.responsePipeline.insertPhaseBefore(HttpResponsePipeline.Receive, BeforeReceive)
    client.responsePipeline.intercept(BeforeReceive) {
      try {
        proceed()
      } catch (cause: Throwable) {
        val error = handler(context.request, cause)
        if (error != null) throw error
      }
    }
  }
}🔩 ClientHook چیه و چرا اصلاً وجود داره؟توی مثال قبلی دیدیم که پلاگین HttpCallValidator از چیزی به اسم ClientHook ارث‌بری می‌کنه تا بتونه توسط پلاگین HttpResponseValidator روی ResponsePipeline اجرا بشه. در واقع پلاگین‌ها به‌صورت مستقیم از ساختار Pipeline خبر ندارن و فقط با مفهوم Hook کار می‌کنن. این کار عمداً انجام شده تا یه لایه‌ی انتزاعی بین پلاگین‌ها و ساختار داخلی Ktor وجود داشته باشه و از یه‌سری مشکل جلوگیری بشه، مثل این‌که:پلاگین به ساختار داخلی Ktor وابسته نشهبا تغییر pipeline یا phaseها، پلاگین نشکنهپلاگین‌ها با هم تداخل نداشته باشنیه API عمومی و پایدار در اختیارمون باشهبشه از Hookهای مشترک بین پلاگین‌های مختلف استفاده کردبه‌عنوان مثال، وقتی از SetupRequest استفاده می‌کنیم، عملاً یه همچین روندی پشت‌صحنه در جریانه.val CustomPlugin = createClientPlugin(&quot;CustomPlugin&quot;) {
  on(SetupRequest) {
    // Logic
  }
}

public interface ClientHook&lt;HookHandler&gt; {
  public fun install(client: HttpClient, handler: HookHandler)
}

public object SetupRequest : ClientHook&lt;suspend (HttpRequestBuilder) -&gt; Unit&gt;{
  override fun install(
    client: HttpClient,
    handler: suspend (HttpRequestBuilder) -&gt; Unit,
  ) {
    client.requestPipeline.intercept(HttpRequestPipeline.Before) {
      handler(context)
    }
  }
}🧱 ClientPluginBuilder چیه؟برای ساختن یه پلاگین Custom، وقتی میایم همچین کدی می‌نویسیم، در واقع پشت‌صحنه داریم از یه کلاسی استفاده می‌کنیم که وظیفه‌ش نگه‌داری اسم پلاگین، کانفیگ‌هایی که از بیرون مقداردهی می‌شن و همین‌طور Hookهاست.توی مثال پایین هم از تابع onRequest همین کلاس استفاده شده که پشت‌صحنه یه RequestHook به این پلاگین اضافه می‌کنه. بعد از اینکه پلاگین نصب شد، تابع install مربوط به هوک صدا زده می‌شه تا یه Interceptor به فاز State از RequestPipeline اضافه کنه.از بقیه توابع مهم این کلاس هم می‌شه به onResponse، transformRequestBody و transformResponseBody اشاره کرد.val CustomPlugin = createClientPlugin(&quot;CustomPlugin&quot;) {
  onRequest {
    // Logic
  }
}

class ClientPluginBuilder(...) {
  internal val hooks: MutableList&lt;HookHandler&lt;*&gt;&gt; = mutableListOf()

  public fun onRequest(
    block: suspend OnRequestContext.(request: HttpRequestBuilder,
    content: Any) -&gt; Unit,
  ) {
    on(RequestHook, block)
  }

  public fun &lt;HookHandler&gt; on(
    hook: ClientHook&lt;HookHandler&gt;,
    handler: HookHandler,
  ) {
    hooks.add(HookHandler(hook, handler))
  }
}

internal class HookHandler&lt;T&gt;(
  private val hook: ClientHook&lt;T&gt;,
  private val handler: T,
) {
  fun install(client: HttpClient) {
    hook.install(client, handler)
  }
}

internal object RequestHook : ClientHook&lt;suspend OnRequestContext.(request: HttpRequestBuilder, content: Any) -&gt; Unit&gt; {
  override fun install(
    client: HttpClient,
    handler: suspend OnRequestContext.(request: HttpRequestBuilder, content: Any) -&gt; Unit
  ) {
    client.requestPipeline.intercept(HttpRequestPipeline.State) {
      handler(OnRequestContext(), context, subject)
    }
  }
}به صورت کلی میشه نحوه اتصال پلاگین تا Pipeline به این شکل ترسیم کرد.Plugin DSL
    ↓
ClientPluginBuilder
    ↓
on(ClientHook)
    ↓
Hook.install()
    ↓
Pipeline.intercept(Phase)🔍 Hookهای معروف Ktor دقیقاً کجا اجرا میشن؟جمع بندی نهاییتوی این مقاله سعی کردم چیزهایی که پشت‌صحنه‌ی Ktor اتفاق می‌افته رو توضیح بدم. برخلاف OkHttp که معماری نسبتاً ساده‌ای داره، Ktor از یه معماری مخصوص به خودش استفاده می‌کنه؛ معماری‌ای که در نگاه اول شاید خیلی ساده به نظر بیاد، اما وقتی وارد نوشتن پلاگین‌های Custom می‌شی و می‌بینی پلاگینی که نوشتی داره روی بقیه پلاگین‌ها اثر می‌ذاره، اون‌وقته که باید بری تا تهش رو دربیاری و بفهمی این کتابخونه دقیقاً قراره چه کارهایی انجام بده.توی این شکل سعی کردم همه‌ی پلاگین‌های Ktor، هوک‌هایی که ازشون استفاده می‌کنن و این‌که روی کدوم فاز اجرا می‌شن رو نشون بدم. هدفم این بوده که اگه خواستین پلاگین جدیدی بنویسین، بتونین جوری طراحیش کنین که با بقیه پلاگین‌ها تداخل نداشته باشه.پایان...</description>
                <category>Ghasem Shirdel</category>
                <author>Ghasem Shirdel</author>
                <pubDate>Mon, 05 Jan 2026 22:54:54 +0330</pubDate>
            </item>
                    <item>
                <title>برنامه نویسی تابع گرا (Either Monad)</title>
                <link>https://virgool.io/@ghasemdev/either-monad-kxoesyh6tcfi</link>
                <description>Written by Ghasem ShirdelMonad چیه ؟میشه گفت که موناد الگو طراحی برای تابع هاست که یه سری مشکلات حل میکنن مثلا مشکل Null بودن ، مدیریت State یا Error Handling که توی این مقاله می خوام در مورد Either صحبت کنم.Either Monadساختار Either برای مدیریت خطا ها استفاده میشه و حتی توی زبان برنامه نویسی مثل Golang جزیی از syntax اون محسوب میشه و دو مقدار value , err رو بر میگردونه.کلاس Either یک ظرف هست که دو مقدار Right , Left داخل خودش نگهداری میکنه.از Left برای نگهداری مقدار Failure و از Right برای نگهداری مقدار Success استفاده میشه.خوب حالا برای پیاده سازی اون ما چرخ از اول اختراع نمی کنیم و از کتابخونه Arrow توی کاتلین یا پکیج dartz در زبان دارت استفاده می کنیم. اما قبل اون بهتر هست یه مثال از زمانی ببینیم که باید با استفاده از بلوک try-catch بیایم و ارور هامون رو کنترل کنیم توی این مثال هدف ما parse کردن یه رشته و به دست آوردن معکوس اون هست.KotlinDartخوب همینطور که توی کد های بالا دیدیم برای مدیریت بهتر برنامه میایم و یه سری Exception به بیرون تابع پرتاب میکنیم و باید در ادامه این خطا ها رو مدیریت کنیم وگرنه برنامه کرش میکنه. در نهایت خروجی برنامه برای مقدار 2 عدد 0.5 و برای مقادیر غیر عددی یا 0 نمایش نوع خطا هست.حالا با استفاده از Either کد هامون Refactor میکنیم تا ببینیم نتیجه چه خواهد بود?.KotlinDartخوب در ابتدا اومدیم و یه سری کلاس برای تایپ ارور هایی که داشتیم اضافه کردیم تا داخل تابع هامون به عنوان مقادیر چپ برگردونیم. توی مرحله بعدی تابع های parse و reciprocal دیگه خطایی رو پرتاب نمیکنن و بلکه یه Either بر میگردونن که حاوی مقدار اصلی و مقدار ارور ماست. فرق دیگه ای که وجود داره به جای استفاده تو در توی توابع داخل magic اومدیم و از توابع map , flatmap استفاده کردیم. در آخر درون تابع main اومدیم و با استفاده از when یا تابع fold مقدایر Left , Right رو هندل کردیم.نتیجه گیریاستفاده از Either مزیت هایی به همراه داره که میشه به مدیریت ساده تر ارور ها اشاره کرد (حد اقلش اینکه ما به برنامه تا حد ممکن ارور اضافه نکردیم ?) برای یادگیری بیشتر هم میتونین به داکیومنت هایی وجود داره مراجعه کنین ?. https://medium.com/@albert.llousas/monads-explained-in-kotlin-4126ac0cb7f2 </description>
                <category>Ghasem Shirdel</category>
                <author>Ghasem Shirdel</author>
                <pubDate>Fri, 22 Jul 2022 21:14:07 +0430</pubDate>
            </item>
            </channel>
</rss>