Monad

در خیلی از برنامه ها نیاز به پردازش زنجیری هست و پردازش یک داده وابسته به داده قبله
در مثال زیر comment های پروژه های کاربر میخواهیم برگردونیم و درصورت نبود کاربر یا نبود پروژه باید با خطا سریع خارج بشیم
تو روبی این یک راه برای این کار

class CommentFetcher
  def self.fetch(id)
    user = fetch_user
    if user(id)
      projects = projects_for(user)
      if projects
        return comments_for(projects)
      else
        return "no projects"
      end
    else
      return "no user"
    end
  end

  def self.fetch_user(id)
    //
  end

  def self.projects_for(user)
    //
  end

  def self.comments_for(projects)
    //
  end
end

همین کد در سکالا


object CommentFetcher {
  def fetch(id: Int): Option[Comments] = {
    val user = fetchUser(id)
    if user == None {
      None
    } else {
      val projects = fetchProjects(user) 
      if projects == None {
        None
      } else {
        val comments = fetchComments(projects)
        comments
      }
    }
  }
  
  
  def fetchUser(id: Int): Option[User] = ???
  
  def fetchComments(projects: List[Project]) = ???
}

درک هردو این کد ها بسیار سخت درک میشن و باید تمام حالت های شرطی نگاه کرد
در حالت ایده ال یک function باید باشه که در صورتی که یکی از این function ها در زنجیره خطا برگردونه
بدون فراخوانی باقی کد خطا رو برگردونیم
این function
flatMap یا andThen یا Bind نام داره و ساختاری که اینو تعریف میکنه Monad نامیده میشه
flatMap همونطور که رو اسمش هست اول function که میگیره رو map میکنه بعد نتیجه flat که یعنی نتیجه آخری بجای اینکه لیست لیست عدد ی باشه فقط لیست عدد هست و اینطوری میشه بصورت زنجیری از flatmap استفاده کرد

trait Monad[F[_]] {
  def flatMap[A, B](fa: F[A], f: A => F[B]): F[B]
}

چون option یک monad هست میشه مثال بالا رو به صورت زیر نوشت

fetchUser(1).flatMap(((x) => fetchProjects(x).map(((y) => fetchComments(y)))))

این کار انقدر رایجه که یک syntax sugar براش وجود داره و اون for comprehenshion هستش

def fetch(id: Int) = {
  for {
    user <- fetchUser(id)
    projects <- fetchProjects(user)
    comments <- fetchComments(projects)
  } yield comments
}

اگر هر کدام از این عبارت ها None برگردونن کله عبارت None برمیگردونه
List, Option, Either, Future

همه monad هستند Future مثلا برای کار async هست و اگر دو کار async به هم مربوط باشن و یکی خطا برگردونه خطا به عنوان نتیجه کل عبارت برمیگرده
یا با Either اگه یک عبارت Left برگردونه کل محاسبه متوقف میشه و خطا برمیگرده

monad ها مختص به سکالا و هسکل نیستند و ساختار ریاضی به حساب میان promise ها در جاواسکریپت و state در redux همه نسخه های دست پا شکسته monad ها هستند

اینم یک talk خوب

9 Likes

سام عزیز چنین چیزی در الکسیر هم داریم ؟

1 Likes

With تقریبا اینطوری کار میکنه در الیکسیر

ولی این کتابخانه این ساختار ها رو اصولی تر ساخته

3 Likes

درود سام عزیز . من چند وقتی هست که در الکسیر از fallback استفاده می کنم . و خیلی از کد هام داخل with که می ره چندین شرط رو اجرا کنه که مشکلی نیست ولی برخی از کد های بنده تک شرط هستند و چون از fallback استفاده می کنم باید with به کار ببرم چون روی case مثل اینکه جواب نمی ده

سوال من اینجاست که آیا تو سرعت کد من تاثیر داره ؟ چون هنوز وارد بحث بنچ مارک اینا نشدم در کل هنوز ابزاری برای تست ندارم چون در فکر بودم شاید این ماکرو بر اساس کاری که کاربرا می خواند یعنی شروط تو در تو ساخته شده باشه و نباید برای تک شرط استفاده بشه

2 Likes

هیچ تاثیری در سرعت نباید داشته باشه

2 Likes

@shahryarjb
Monad برای کار با خطا در الیکسیر

http://crowdhailer.me/2018-08-26/errors-are-not-exceptional/

4 Likes

درود سام عزیز ممنونم که معرفی کردید واقعا ازت سپاسگزارم.

من این کتاب خونه رو دیدم البته فکر کنم بیشتر یک زبانی رو با ماکرو درست کردند اینطور که فهمیدم البته . ولی یک چیزی رو خوب درک نکردم خیلی از مثال هایی که زد رو من دیدم با with می شه زد.

برای مثال:

  {:ok, user} = fetch_user(user_id)
  {:ok, first_name} = first_name(user)
  {:ok, second_name} = second_name(user)

چند خط بالا رو می شه به صورت with نوشت . ولی ایشون با این کتابخونه به صورت زیر نوشت

def fetch_full_name(user) do
  OK.for do
    first_name <- fetch_first_name(user)
    second_name <- fetch_second_name(user)
  after
    first_name <> " " <> second_name
  end
end

برای من اینجا سواله آیا این دقیقا خود with هست یا ایشون چون اینجا یک for درست کرده یک حلقه هست درحقیقت ؟ البته فکر نمی کنم حلقه باشه


و مثال بعدیشو برام سوال شده

@spec greet(integer) :: String.t()
def greet(user_id) do
  OK.try do
    user <- fetch_user(id)
    name <- full_name(user)
    after
      "Hello " ++ name
    rescue
      _ ->
        "And you are?"
  end
end

اینجا اومده از rescue استفاده کرده آیا این همون with با else نیست ؟ اگر وقت داشته باشید یک تحلیل کوچیکی روش داشته باشید بسیار استفاده می کنیم. خیلی نظرمو جذب خودش کرده چند باری هم مطالعه کردم ولی نتونستم تحلیل داشته باشم روش به صورت ۱۰۰ درصدی

1 Likes

فکر می‌کنم این کتابخانه فقط برای خطاست و با exception کاری نداره
شما همون with استفاده کنی بهتره

2 Likes

قبل از ورود به بنچ مارک, چرا فکر میکنی توی performance تغییری ایجاد کردی؟

1 Likes

درود توماج جان . چون

به این علت این چنین حرفی رو زدم . این حرفم بیشتر چیزی که فکر می کردمه . جایی چیزی ندیدم

1 Likes

راستش خیلی نگرفتم داستانو اما احتمالا اشکال از من باشه چون تاپیک رو‌ دنبال نکردم

1 Likes