OOP اگه خوب نوشته بشه با توجه به اصول SOLID و separation of concerns بسیار خوب میتونه abstraction ایجاد کنه در FP هم میشه کده بد نوشت فرقی که وجود داره اینکه در FP بخاطره یک سری قانون ها که خوده زبان اعمال میکنه کد با ساختار پیچیده سخت تر نوشته میشه
در FP
composition اول از همه بجای inheritance کار میکنه و به جای ساختن سلسه شی و پنهان کردن داده پشته شی برنامه به جابجا کردن داده از function به function انجام میشه
شما شاید بگی inheritance در oop زیاد بد نیست و اگه نباشه چطور FP abstraction بوجود میاره
با یک مثال شاید این فرق بهتر معلوم شه فرض کنید میخواهیم یک برنامه برای log کردن بنویسیم
راهه کلاسیک OOP ساختن interface و ارث بردن
trait Loggable {
def logMessage:String
}
object Logger {
def log(value: Loggable):String = {
println("Logging:" value.logMessage)
}
}
حالا هر class که میخواهیم log کنیم باید از این trait استفاده کنن و راجبه Logging بدونن این کار tight coupling بوجود میاره و کد به سختی عوض میشه بعد از این
case class User(name: String, age: Int) extends Loggable {
override def logMessage = println(s"name: $name age: $age")
}
و به علاوه شاید یک کلاس برای یک کتاب خانه باشه که ما اصلا بهش دسترسی نداریم چطوری ان وقت لاگ میکنیم ؟
میتونیم مثال بالا رو کمی بهتر کنیم و Loggable برای هر تایپ بصورت کلی بنویسیم و function log عوض کنیم که Logger خاص هر تایپ بگیره
trait Loggable[A] {
def logMessage(a: A):String
}
object Logger {
def log[A](value: A, loggable: Loggable[A]):String = {
println("Logging:" loggable.logMessage(a))
}
}
این بهتره والی هنوز مشکل کلاس هایی که برای کتابخانه های دیگه هستن حل نمیکنه برای اینکار از
یک خاصیت سکالا که هسکل هم شبیهش را انجام میده استفاده میکنیم با اضافه کردن کلمه implicit برای loggable
complier سعی میکنه یک Loggable برای تایپ که بهش داده شده پیدا کنه و اگه پیدا نکنه compile نمیشه
trait Loggable[A] {
def logMessage(a: A):String
}
object Logger {
def log[A](value: A)(implicit loggable: Loggable[A]):String = {
println("Logging:" loggable.logMessage(a))
}
}
نوشتن Loggable برای یک تایپ
implicit val intLoggable: Loggable[Int] = new Loggable[Int] {
def logMessage(a: Int):String = a.toString
}
این کار به typeclass پترن معروفه که به قدرت dynamic typing از نظره کلی بودن ولی کاملا type safe هستش و در compile time خطا آشکار میشه اگه یک تایپ با interface درست کار نکنه تا زمانی که هر کتابخانه برای interface ما یک instance داشته باشه یا اینکه خودمون بنویسیم همه چیز کار میکنه