Monad

scala

#1

در خیلی از برنامه ها نیاز به پردازش زنجیری هست و پردازش یک داده وابسته به داده قبله
در مثال زیر 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 خوب


معادل UML برای زبانهای فانکشنال : Category theory
تا چه زمانی می توان از شرط های تو در تو استفاده کرد مانند case در الکسیر
بحث دیزاین پترن زبان های فانکشنال
#2

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


#3

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

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


#4

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

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


#5

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


#6

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

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


#7

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

من این کتاب خونه رو دیدم البته فکر کنم بیشتر یک زبانی رو با ماکرو درست کردند اینطور که فهمیدم البته . ولی یک چیزی رو خوب درک نکردم خیلی از مثال هایی که زد رو من دیدم با 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 نیست ؟ اگر وقت داشته باشید یک تحلیل کوچیکی روش داشته باشید بسیار استفاده می کنیم. خیلی نظرمو جذب خودش کرده چند باری هم مطالعه کردم ولی نتونستم تحلیل داشته باشم روش به صورت ۱۰۰ درصدی


#8

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


#9

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


#10

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

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


#11

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