۲۴ - ۸ترانسفورمرِ Identity یا IdentityT

همونطور که تایپِ ‏‎Identity‎‏ کمک می‌کرد اساسِ ‏‎Functor‎‏، ‏‎Applicative‎‏، و ‏‎Monad‎‏ رو نشون بدیم، تایپِ ‏‎IdentityT‎‏ هم کمک به درکِ موند ترانسفورمرها می‌کنه. استفاده از این تایپی که هیچ کارِ بخصوصی انجام نمیده، اجازه میده به تایپ‌ها و مفاهیمِ مهمِ موند ترانسفورمرها تمرکز کنیم. چیزهایی که اینجا می‌بینیم برای بقیه‌ی ترانسفورمرها هم صادق‌اند، اما تایپ‌هایی مثلِ ‏‎Maybe‎‏ و لیست، حالتهای دیگه‌ای (حالتِ شکست، و لیستِ خالی) هم دارند که یه ذره پیچیده‌شون می‌کنن.

اول تایپِ ‏‎Identity‎‏ که تا اینجا دیدین رو با این ‏‎IdentityT‎‏ ِ جدید مقایسه می‌کنیم:

-- می‌تونه یه a قدیمی. تایپ Identity
-- تایپِ با ساختار باشه، اما لازم
-- هم نمی‌تونه چیزی Identity نیست و
-- .ازش بدونه
newtype Identity a =
  Identity {runIdentity :: a }
  deriving (Eq, Show)

-- ،تنها نقشی که موند ترانسفورمر همانی
-- بازی می‌کنه identity monad transformer یا
-- .تعیینِ وجودِ ساختار اضافه‌ست
newtype IdentityT f a =
  IdentityT { runIdentityT :: f a }
  deriving (Eq, Show)

چیزی که اینجا تغییر کرد، اضافه شدنِ یه آرگومانِ تایپیِ دیگه‌ست.

حالا نمونه ِ ‏‎Functor‎‏ برای ‏‎Identity‎‏ و ‏‎IdentityT‎‏:

instance Functor Identity where
  fmap f (Identity a) = Identity (f a)

instance (Functor m)
      => Functor (IdentityT m) where
  fmap f (IdentityT fa) = IdentityT (fmap f fa)

این نمونه ِ ‏‎IdentityT‎‏ به نمونه ِ ‏‎Functor‎‏ ِ نوع‌داده ِ ‏‎One‎‏ که بالاتر دیدیم شباهت داره. اون آرگومانِ ‏‎fa‎‏، مقدارِ داخلِ ‏‎IdentityT‎‏ هست که یه ساختار (غیرقابل دسترس) دورش پوشونده شده. تنها چیزی که از اون ساختار ِ اضافه دورِ ‏‎a‎‏ می‌دونیم اینه که یه ‏‎Functor‎‏ ِه.

برای هردوشون نمونه ِ ‏‎Applicative‎‏ هم می‌خوایم:

instance Applicative Identity where
  pure = Identity

  (Identity f) <*> (Identity f) =
    Identity (f a)

instance (Applicative m)
      => Applicative (IdentityT m) where
  pure x = IdentityT (pure x)

  (IdentityT fab) <*> (IdentityT fa) =
    IdentityT (fab <*> fa)

باز هم نمونه ِ ‏‎Identity‎‏ باید آشنا باشه. در نمونه ِ ‏‎IdentityT‎‏، متغیرِ ‏‎fab‎‏، آرگومانِ اول برای ‏‎(<*>)‎‏ با تایپِ ‏‎f (a -> b)‎‏ هست و به نمونه ِ ‏‎Applicative‎‏ برای ‏‎m‎‏ اتکا داره. پس این نمونه، اعمال ِ اپلیکتیوی در حضورِ ساختار ِ ‏‎IdentityT‎‏ رو تعریف کرده.

می‌رسیم به نمونه‌های ‏‎Monad‎‏:

instance Monad Identity where
  return = pure

  (Identity a) >>= f = f a

instance (Monad m)
      => Monad (IdentityT m) where
  return = pure

  (IdentityT ma) >>= f =
    IdentityT $ ma >>= runIdentity . f

نمونه ِ ‏‎Monad‎‏ ممکنه یه کم پیچیده باشه، ما هم خوردِش می‌کنیم. به خاطر داشته باشین که ‏‎Monad‎‏ جایی‌ه که واقعاً باید از اطلاعاتِ تایپِ معین ِ ‏‎IdentityT‎‏ استفاده کنیم تا بتونیم تایپ‌ها رو جور کنیم.

تشریحِ بایند

با نگاهی دقیق‌تر به نمونه‌ای که بالا نوشتیم شروع می‌کنیم:

instance (Monad m)
      => Monad (IdentityT m) where
    return = pure
    (IdentityT ma) >>=  f  =
--   [     1     ] [2] [3]
       IdentityT $ ma
--         [8]     [4]
       >>= runIdentity . f
--    [5]    [7]       [6]

۱.

اول با داده‌ساز ِ ‏‎IdentityT‎‏ روی مقدار با تایپِ ‏‎m a‎‏، از تایپِ ‏‎IdentityT m a‎‏ تطبیق الگو می‌کنیم (بازش می‌کنیم). این کار در واقع چنین تایپی داره: ‏‎IdentityT m a -> m a‎‏ و تایپِ ‏‎ma‎‏ هم ‏‎m a‎‏ هست. این حروفی که استفاده کردیم فقط برای یادآوریِ بهترن، به خودیِ خود مفهومی ندارن.

۲.

تایپِ بایندی که داریم تعریف می‌کنیم، از این قراره:

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b

این نمونه ایه که داریم تعریف می‌کنیم.

۳.

تابعی که از روی ‏‎IdentityT m a‎‏ بایند می‌کنیم، تایپ‌ش اینه:

(a -> IdentityT m b)

۴.

این ‏‎ma‎‏ همون مقداریه که از داده‌ساز ِ ‏‎IdentityT‎‏ درآوردیم و تایپ‌ش ‏‎m a‎‏ هست. این ‏‎m a‎‏ که از بافت ِ ‏‎IdentityT‎‏ ش خارج شده، آرگومانِ اول به بایند میشه.

۵.

این یه بایند ِ دیگه‌ست! بایند ِ اول، بایند ایه که داریم تعریف می‌کنیم؛ این بایند، خودِ اون تعریف‌ه. اینجا داریم از اون محدودیت ِ ‏‎Monad‎‏ ای که در شروعِ تعریفِ نمونه ِ ‏‎Monad‎‏ با ‏‎Monad m =>‎‏ درخواست کردیم استفاده می‌کنیم. تایپ‌ش اینطوریه:

(>>=) :: m a -> (a -> m b) -> m b

این تایپِ مرتبط با ‏‎m‎‏ ایه که در تایپِ ‏‎IdentityT m a‎‏ هست، نه تایپِ کلی برای کلاسِ ‏‎Monad‎‏. به کلام دیگه، حالا که دیگه ‏‎IdentityT‎‏ رو باز کردیم و به نوعی از سرِ راه بَرش داشتیم، این بایند، بایند ِ تایپِ ‏‎m‎‏ در تایپِ ‏‎IdentityT m‎‏ میشه. هنوز نمی‌دونیم که اون چه ‏‎Monad‎‏ ایه، نیازی هم نیست که بدونیم؛ از اونجایی که محدودیت تایپکلاسی ِ ‏‎Monad‎‏ روی اون متغیر هست، می‌دونیم که یه نمونه ِ ‏‎Monad‎‏ براش تعریف شده، و در نتیجه این بایند ِ دوم، بایند ایه که برای اون تایپ تعریف شده. تنها کاری که اینجا انجام میدیم اینه که تعریف کنیم چطور از اون بایند در حضورِ ساختار ِ اضافیِ ‏‎IdentityT‎‏ استفاده بشه.

۶.

این ‏‎f‎‏ میشه همون ‏‎f‎‏ ای که یکی از آرگومان‌های نمونه ِ ‏‎Monad‎‏ بود، و تایپ‌ش اینه:

(a -> IdentityT m b)

۷.

به دلیل اینکه ‏‎f‎‏ یه ‏‎IdentityT m b‎‏ برمی‌گردونه، ‏‎runIdentityT‎‏ هم لازم داریم، ولی تایپِ بایند ِ دوم (‏‎>>=‎‏ مرتبط با ‏‎Monad m =>‎‏) اینطوریه: ‏‎m a -> (a -> m b)‎‏. یعنی بدونِ ‏‎runIdentityT‎‏، نهایتاً تلاش می‌کنه ‏‎m (IdentityT m b)‎‏ رو متحد کنه، که شدنی نیست چون ‏‎m‎‏ و ‏‎IdentityT m‎‏ یک تایپ نیستن. با ‏‎runIdentityT‎‏ مقدار رو دَر میاریم و این کار تایپِ ‏‎IdentityT m b -> m b‎‏ داره. و اینجا ترکیب ِ ‏‎runIdentityT . f‎‏ تایپِ‌ش ‏‎a -> m b‎‏ هست. با استفاده از ‏‎undefined‎‏، خودتون می‌تونین در GHCi ببینین:

Prelude> :{
*Main| let f :: (a -> IdentityT m b)
*Main|     f = undefined
*Main| :}
Prelude> :t f
f :: (a -> IdentityT m b)
Prelude> :t runIdentityT
runIdentity :: IdentityT f a -> f a
Prelude> :t (runIdentityT . f)
(runIdentityT . f) :: a1 -> f a

قبوله، متغیرها اسم‌هاشون فرق دارن، اما واضحه که ‏‎a1 -> f a‎‏ و ‏‎a -> m a‎‏ یکسان‌اند.

۸.

برای ارضای بایند ِ بیرونی که برای ‏‎Monad‎‏ ِ تایپِ ‏‎IdentityT m‎‏ تعریف می‌کنیم، و انتظارِ جواب نهایی با تایپِ ‏‎IdentityT m b‎‏ داره، باید ‏‎m b‎‏ که حاصل از بیانیه‌ی ‏‎ma >>= runIdentityT . f‎‏ هست رو دوباره تو ‏‎IdentityT‎‏ بسته‌بندی کنیم. دقت کنین:

Prelude> :t IdentityT
IdentityT :: f a -> IdentityT f a
Prelude> :t runIdentityT
runIdentityT :: IdentityT f a -> f a

حالا یه بایندی داریم که میشه با ترکیب ِ ‏‎IdentityT‎‏ و یه ‏‎Monad‎‏ ِ دیگه ازش استفاده کرد – در این مثال با یه لیست:

Prelude> let sumR = return . (+1)
Prelude> IdentityT [1, 2, 3] >>= sumR
IdentityT {runIdentityT = [2,3,4]}

تعریفِ بایند، قدم به قدم

حالا برمی‌گردیم و اون تعریف رو مجدداً قدم به قدم دوره می‌کنیم. هدف اینه که هرچی ابهام در کارهایی که انجام دادیم هست رو برطرف کنیم تا بتونین برای هر موند ترانسفورمری که لازم دارین، خودتون نمونه‌ش رو بنویسین. این بار با توسعه ِ ‏‎InstanceSigs‎‏ می‌نویسیم تا بتونیم تایپ‌ها رو ببینیم:

{-# LANGUAGE InstanceSigs #-}

instance (Monad m)
      => Monad (IdentityT m) where
    return = pure

    (>>=) :: IdentityT m a
          -> (a -> IdentityT m b)
          -> IdentityT m b
    (IdentityT ma) >>= f =
      undefined

فعلاً مقدار نهایی رو ‏‎undefined‎‏ نگه می‌داریم تا بتونیم با استفاده از انقیادهای ‏‎let‎‏ و تناقض، تایپِ تعاریفی که امتحان می‌کنیم رو ببینیم. در واقع با تهی (‏‎undefined‎‏) چیزهایی که مجبور به تأمین‌شون هستیم رو داریم موکول می‌کنیم به وقتی که آمادگیِ بیشتری داریم. اول یه انقیاد ِ ‏‎let‎‏ بذاریم تا بارگذاری‌ش رو ببینیم، حتی اگه کار نکنه:

    (>>=) :: IdentityT m a
          -> (a -> IdentityT m b)
          -> IdentityT m b
    (IdentityT ma) >>= f =
      let aimb = ma >>= f
      in undefined

برای یادآوری از اسم ‏‎aimb‎‏ استفاده کردیم تا بدونیم از چه بخش‌هایی تشکیل شده.

خب یه خطا می‌گیریم:

Couldn't match type ‘m’ with ‘IdentityT m’

این خطای تایپ، کمک زیادی نمی‌کنه؛ تشخیص اشکال کار باهاش آسون نیست. یه کم دستکاری کنیم تا خطا ِ بهتری بگیریم.

اول یه کاری که می‌دونیم جواب میده رو انجام میدیم: از ‏‎fmap‎‏ استفاده می‌کنیم که تایپچک میشه (اما جوابِ ‏‎(>>=)‎‏ رو نمیده)، پس برای اینکه بتونیم از پیغامِ کامپایلر کمک بگیریم، باید کاری کنیم خطا بده. اینجا با اعلام ِ یه تایپِ تماماً پلی‌مورفیک برای ‏‎aimb‎‏ خطا رو اجبار می‌کنیم:

  (>>=) :: IdentityT m a
        -> (a -> IdentityT m b)
        -> IdentityT m b
  (IdentityT ma) >>= f =
    let aimb :: a
        aimb = fmap f ma 
    in undefined

تایپی که برای ‏‎aimb‎‏ اعلام کردیم غیرممکنه؛ گفتیم می‌تونه هر تایپی باشه، در صورتی که اینطور نیست. تهی تنها چیزیه که می‌تونه اون تایپ رو داشته باشه چون تهی جزءِ همه‌ی تایپ‌ها هست.

GHC خودش میگه ‏‎aimb‎‏ چیه:

Couldn' match expected type ‘a1’
  with actual type ‘m (IdentityT m b)’

با این تعریفی که کردیم، تایپِ ‏‎aimb‎‏ میشه ‏‎m (IdentityT m b)‎‏. حالا ایرادِ کار معلوم شد: بین دوتا ‏‎m‎‏ که می‌خوایم متحد کنیم یه لایه‌ی ‏‎IdentityT‎‏ مزاحم‌ه.

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b

تابعی که تو این تعریف از روی ‏‎ma‎‏ (حاصل از تطبیق الگو روی ‏‎IdentityT‎‏) لیفت کردیم اینه:

(a -> IdentityT m b)

یعنی تابع رو روی تایپِ

m a

نگاشت کردیم و تایپِ

m (IdentityT m b)

گرفتیم. تابعِ ‏‎(>>=)‎‏ بعد از لیفت کردنِ تابع، دوتا ساختار ِ یکسان رو تلفیق می‌کنه (یادتون باشه که بایند، ترکیب ِ ‏‎fmap‎‏ و ‏‎join‎‏ ِه). یعنی اگه تایپِ حاصل از نگاشتِ ‏‎f‎‏ روی ‏‎ma‎‏ میشد ‏‎m (m b)‎‏، اون موقع ‏‎join‎‏ کار می‌کرد. اما با این وضع، باید یه راهی پیدا کنیم تا اون دوتا ‏‎m‎‏ رو بیاریم کنارِ هم تا دیگه لایه‌ی ‏‎IdentityT‎‏ بین‌شون نباشه.

برای اینکه پیشرفت مرحله‌ای‌مون بهتر دیده بشه، بجای ‏‎(>>=)‎‏ از ‏‎fmap‎‏ و ‏‎join‎‏ استفاده می‌کنیم. چطور از اون ‏‎IdentityT‎‏ بین دوتا ساختار ِ ‏‎m‎‏ خلاص شیم؟ خب، میدونیم ‏‎m‎‏ یه ‏‎Monad‎‏ ِه، یعنی یه ‏‎Functor‎‏ هم هست. پس با استفاده از ‏‎runIdentityT‎‏ میشه اون ‏‎IdentityT‎‏ وسط دسته ِ تایپ‌ها رو حذف کرد:

-- m (IdentitytT m b) تغییر
-- m (m b) به

-- :توجه کنین که
runIdentityT :: IdentityT f  a  ->     f a
fmap runIdentityT :: Functor f
          => f (IdentityT f1 a) -> f (f1 a)

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b
(IdentityT ma) >>= f =
  let aimb :: a
      aimb = fmap runIdenitytT (fmap f ma)
  in undefined

خطای تایپی که از این کُد می‌گیریم امیدوارکننده‌ست:

Couldn't match expected type ‘a1’
  with actual type ‘m (m b)’

پس رسیدیم به تایپِ ‏‎m (m b)‎‏، دیگه می‌دونیم از اینجا چطور به چیزی که می‌خوایم برسیم. ‏‎a1‎‏ همون تایپی‌ه که برای ‏‎aimb‎‏ اعلام کردیم، اما میگه تایپِ واقعی مون اونی که اعلام کردیم نیست، بلکه ‏‎m (m b)‎‏ ِه. پس تایپِ واقعی رو کشف کردیم، حالا می‌تونیم درست‌ش کنیم.

برای تلفیق ِ ‏‎m‎‏‌های تودرتو، از ‏‎join‎‏ از ماژول ِ ‏‎Control.Monad‎‏ استفاده می‌کنیم:

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b
(IdentityT ma) >>= f =
  let aimb :: a
      aimb =
       join (fmap runIdenitytT (fmap f ma))
  in undefined

حالا اگه بارگذاری‌ش کنیم، کامپایلر تایپی رو نشون میده که می‌خوایم:

Couldn' match expected type ‘a1’
  with actual type ‘m b’

قبل از تمیزکاری هم میشه سریع فهمید این درسته:

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b
(IdentityT ma) >>= f =
  let aimb =
       join (fmap runIdenitytT (fmap f ma))
  in aimb

تایپِ ‏‎aimb‎‏ رو حذف کردیم، اون ‏‎in undefined‎‏ هم تغییر دادیم. دیگه می‌دونیم که تایپِ واقعی ِ ‏‎aimb‎‏ شده ‏‎m b‎‏، پس هنوز کار نمی‌کنه. چرا؟ اگه به خطای تایپ ِ جدیدمون نگاه کنیم:

Couldn't match type ‘m’ with ‘IdentityT m’

تایپِ نتیجه‌ی نهاییِ ‏‎(>>=)‎‏ که داریم تعریف می‌کنیم، باید ‏‎IdentityT m b‎‏ باشه، پس تایپِ ‏‎aimb‎‏ هنوز باهاش جور نشده. برای تایپچک باید ‏‎m b‎‏ رو ببریم زیرِ ‏‎IdentityT‎‏:

IdentityT :: f a -> IdentityT f a

instance (Monad m)
      => Monad (IdentityT m) where
    return = pure

    (>>=) :: IdentityT m a
          -> (a -> IdentityT m b)
          -> IdentityT m b
    (IdentityT ma) >>= f =
      let aimb =
           join (fmap runIdenitytT (fmap f ma))
      in IdentityT aimb

این دیگه کامپایل میشه. ‏‎m b‎‏ رو که گذاشتیم زیر تایپِ ‏‎IdentityT‎‏، کار می‌کنه.

تمیزکاری

حالا که رسیدیم به یه چیزی که کار می‌کنه، بریم سراغ تمیزکاری. می‌خوایم تعریفی که برای ‏‎(>>=)‎‏ نوشتیم رو ارتقا بدیم. معمولاً بجای اینکه سعی کنین یه دفعه همه‌ش رو بازنویسی کنین، اگه قدم‌به‌قدم پیش برین نتیجه‌ی بهتری می‌گیرین. این خط رو چطور میشه ارتقا بدیم؟

IdentityT $ 
join (fmap runIdenitytT (fmap f ma))

خوب یکی از قانون‌های ‏‎Functor‎‏ مربوط به دوبار ‏‎fmap‎‏ کردن بود:

-- Functor قانون
fmap (f . g) = fmap f . fmap g

درسته! پس میشه اون خطِ بالا رو با این کُد عوض کنیم و جواب تغییری نکنه:

IdentityT $ 
join (fmap (runIdenitytT . f) ma)

حالا یه کم مشکوک شد... داریم نتیجه‌ی حاصل از ‏‎fmap‎‏ کردنِ دوتا تابعی که ترکیب کردیم رو ‏‎join‎‏ می‌کنیم. ترکیب ِ ‏‎join‎‏ با ‏‎fmap‎‏ همون ‏‎(>>=)‎‏ نبود؟

x >>= f = join (fmap f x)

پس تعریفِ نمونه ِ ‏‎Monad‎‏ ِمون رو میشه به زیر تغییر بدیم:

instance (Monad m)
      => Monad (IdentityT m) where
    return = pure

    (>>=) :: IdentityT m a
          -> (a -> IdentityT m b)
          -> IdentityT m b
    (IdentityT ma) >>= f =
      IdentityT $ ma >>= runIdentityT . f

این کار می‌کنه! یه نوع‌ساز داریم (‏‎IdentityT‎‏) که یه موند به عنوانِ آرگومان می‌گیره و یه موند در جواب برمی‌گردونه.

این تعریف رو میشه به شکل‌های دیگه هم نوشت. برای مثال در کتابخونه ‏‎transformers‎‏ اینطوری نوشته شده:

m >>= k =
  IdentityT $ runIdentityT . k
  =<< runIdentityT m

یه کم وقت بذارین و خودتون تعادلِ بین این دوتا تعریف رو کامل درک کنین.

اساسِ موند ترانسفورمرها

شاید به نظر نرسه، اما موند ترانسفورمر ِ ‏‎IdentityT‎‏، اساسِ کلیِ ترانسفورمرها رو نشون میده. دلیلی که همه‌ی این کارها رو کردیم این بود که نمی‌تونستیم تضمین کنیم از ترکیبِ دوتا تایپ به یه نمونه ِ موند می‌رسیم. یعنی فهمیدیم که داشتن نمونه‌های ‏‎Functor‎‏، ‏‎Applicative‎‏، و ‏‎Monad‎‏ برای رسیدن به یه نمونه ِ ‏‎Monad‎‏ ِ جدید کافی نیست. خب کدوم بخش از کُدِ زیر تازه بود؟

(>>=) :: IdentityT m a
      -> (a -> IdentityT m b)
      -> IdentityT m b
(IdentityT ma) >>= f =
  IdentityT $ ma >>= runIdentityT . f

تطبیق الگو روی ‏‎IdentityT‎‏ که نبود؛ ‏‎Functor‎‏ برای اون کافیه:

-- این نه
(IdentityT ma) ...

قابلیتِ بایند ِ توابع روی مقدارِ ‏‎ma‎‏ با تایپِ ‏‎m a‎‏ هم نبود؛ اون رو از محدودیت ِ ‏‎Monad‎‏ برای ‏‎m‎‏ هم داشتیم:

-- این هم نه
... ma >>= ...

برای استفاده از ‏‎runIdentityT‎‏ باید ماهیتِ یکی از تایپ‌ها رو به صورتِ معیّن می‌دونستیم (این تابع در واقع ‏‎fmap‎‏ کردنِ یه فولد از ساختار ِ ‏‎IdentityT‎‏ ِه). بعد از اعمالِ ‏‎runIdentityT‎‏ هم تونستیم مقدارِ نهایی رو دوباره بذاریم داخلِ ‏‎IdentityT‎‏:

-- برای این کار باید ماهیت
-- ‎‏رو مشخصاً می‌دونستیم.‏‎ IdentityT 
IdentityT .. runIdentityT ...

همونطور که خاطرتون هست، تا قبل از استفاده از ‏‎runIdentityT‎‏ نمی‌تونستیم تایپ‌ها رو با هم جور کنیم، چون یه ‏‎IdentityT‎‏ بینِ دوتا ‏‎m‎‏ گیر کرده بود. ثابت میشه که فقط با ‏‎Functor‎‏، ‏‎Applicative‎‏، و ‏‎Monad‎‏ نمیشه اون مشکل رو حل کرد. این یه مثال از اینه که چرا نمیشه برای تایپِ ‏‎Compose‎‏ نمونه ِ ‏‎Monad‎‏ نوشت، با این حال میشه یه ترانسفورمر مثلِ ‏‎IdentityT‎‏ درست کرد و از اطلاعاتی که مختصِ تایپِ‌ش هست بهره برد و با هر تایپ دیگه‌ای که یه نمونه ِ ‏‎Monad‎‏ داره ترکیب‌ش کرد. در کل، برای اینکه بتونیم تایپ‌ها رو جور کنیم، باید یه راهی برای فولد و بازسازیِ ساختاری که اطلاعاتِ مشخص ازش داریم داشته باشیم.