۲۸ - ۸خب حالا چطور 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‎‏ ِ غیرِامن ِ دیگه‌ای هم هستن، ولی خیلی کم پیش میاد که لازم بشن، کلاً بهتره صریح رو به ضمنی ترجیح بدین.