۲۸ - ۸خب حالا چطور MVar کنیم؟
قبلتر توی فصل، یه مثال با استفاده از تایپِ MVar
از زمانی که IO
جلوی اشتراکگذاری رو میگیره نشون دادیم. کُدِ قبلیمون بلوکه میشد چون این:
myData :: IO (MVar Int)
myData = newEmptyMVar
یه اجراییه ِ IO
که یه MVar
ِ خالی درست میکنه؛ یه ارجاعِ پایدار به یک MVar
ِ مشخص نیست. از دو راه میشه درستش کرد. یکی اینکه ارجاعِ پایدار رو به عنوان آرگومان پاس کنیم. کدِ زیر با موفقیت تا انتها اجرا میشه:
module WhatHappens where
import Control.Concurrent
main :: IO ()
main = do
mv <- myData
putMVar mv (0 :: Int)
zero <- takeMVar mv
print zero
از یه راهِ خبیثانهتر و غیرضروری هم میشه این کار رو کرد. از این فرصت استفاده میکنیم تا یه راهِ ناامنِ فعالکردنِ اشتراکگذاری برای یه اجراییه ِ IO
رو بررسی کنیم: unsafePerformIO
! کُدِ زیر هم تا انتها اجرا میشه:
module WhatHappens where
import Control.Concurrent
import System.IO.Unsafe
myData :: MVar Int
myData = unsafePerformIO newEmptyMVar
-- ^-------------^
-- IO م. اجرای ناامنِ
main :: IO ()
main = do
putMVar myData 0
zero <- takeMVar myData
print zero
تایپِ unsafePerformIO
اینه: IO a -> a
، که ظاهراً غیرممکنه و در کل ایدهی خوبی نیست. در کدِ واقعی باید از ارجاعهای MVar
به عنوانِ آرگومان، یا به واسطهی ReaderT
استفاده کنین، اما ترکیب ِ MVar
و unsafePerformIO
این فرصت رو به ما میده که خیلی رُک و واضح کاری که unsafePerformIO
در کُد انجام میده رو نشون بدیم. اینطوری میشه بجای اینکه هر بار یه MVar
ِ جدید ساخت، MVar
ِ خالی رو بصورتِ ضمنی و دفعاتِ نامحدود به اشتراک گذاشت.
وقتی لازم نیست، یا جایی که ممکنه شفافیت مرجع در کُدتون رو بِهم بزنه از unsafePerformIO
استفاده نکنین! اگه مطمئن نیستین... استفاده نکنین! تابعهای IO
ِ غیرِامن ِ دیگهای هم هستن، ولی خیلی کم پیش میاد که لازم بشن، کلاً بهتره صریح رو به ضمنی ترجیح بدین.