পাঠক মনাদ এত জটিল এবং অকেজো বলে মনে হচ্ছে। জাভা বা সি ++ এর মতো অপরিহার্য ভাষায়, আমার যদি ভুল না হয় তবে পাঠক মোনাডের জন্য কোনও সমমানের ধারণা নেই।
আপনি কি আমাকে একটি সাধারণ উদাহরণ দিতে পারেন এবং এটি কিছুটা সাফ করতে পারেন?
পাঠক মনাদ এত জটিল এবং অকেজো বলে মনে হচ্ছে। জাভা বা সি ++ এর মতো অপরিহার্য ভাষায়, আমার যদি ভুল না হয় তবে পাঠক মোনাডের জন্য কোনও সমমানের ধারণা নেই।
আপনি কি আমাকে একটি সাধারণ উদাহরণ দিতে পারেন এবং এটি কিছুটা সাফ করতে পারেন?
উত্তর:
ভয় পাবেন না! পাঠক মোনাড আসলে এত জটিল নয় এবং আসল ব্যবহারযোগ্য ইউটিলিটি রয়েছে।
একটি মনাদ কাছে যাওয়ার দুটি উপায় আছে: আমরা জিজ্ঞাসা করতে পারি
প্রথম পদ্ধতির থেকে, পাঠক মনাদ কিছু বিমূর্ত প্রকারের
data Reader env a
যেমন যে
-- Reader is a monad
instance Monad (Reader env)
-- and we have a function to get its environment
ask :: Reader env env
-- finally, we can run a Reader
runReader :: Reader env a -> env -> a
তাহলে আমরা কীভাবে এটি ব্যবহার করব? ভাল, পাঠকের মনাদ একটি গণনার মাধ্যমে কনফিগারেশন তথ্য (অন্তর্ভুক্ত) পাস করার জন্য ভাল।
যে কোনও সময় আপনার গণনাতে একটি "ধ্রুবক" থাকে যা আপনার বিভিন্ন পয়েন্টে প্রয়োজন, তবে সত্যই আপনি বিভিন্ন মান সহ একই গণনা সম্পাদন করতে সক্ষম হতে চান তবে আপনার পাঠক মোনাড ব্যবহার করা উচিত।
ওও লোকে নির্ভরতা ইনজেকশন বলে তাই করতে পাঠক মনাদগুলিও ব্যবহৃত হয় । উদাহরণস্বরূপ, নেগাম্যাক্স অ্যালগরিদম দুটি প্লেয়ার গেমের পজিশনের মান গণনা করতে ঘন ঘন (অত্যন্ত অনুকূলিত আকারে) ব্যবহৃত হয়। অ্যালগরিদম নিজেও যদিও আপনি কোন গেমটি খেলছেন তা বিবেচনা করে না, কেবল "পরবর্তী" অবস্থানগুলি খেলায় কী তা নির্ধারণ করতে আপনার সক্ষম হওয়া প্রয়োজন এবং বর্তমান অবস্থানটি বিজয়ের অবস্থান কিনা তা আপনাকে জানাতে সক্ষম হওয়া প্রয়োজন।
import Control.Monad.Reader
data GameState = NotOver | FirstPlayerWin | SecondPlayerWin | Tie
data Game position
= Game {
getNext :: position -> [position],
getState :: position -> GameState
}
getNext' :: position -> Reader (Game position) [position]
getNext' position
= do game <- ask
return $ getNext game position
getState' :: position -> Reader (Game position) GameState
getState' position
= do game <- ask
return $ getState game position
negamax :: Double -> position -> Reader (Game position) Double
negamax color position
= do state <- getState' position
case state of
FirstPlayerWin -> return color
SecondPlayerWin -> return $ negate color
Tie -> return 0
NotOver -> do possible <- getNext' position
values <- mapM ((liftM negate) . negamax (negate color)) possible
return $ maximum values
এটি তখন যে কোনও সীমাবদ্ধ, নির্জনবাদী, দুটি প্লেয়ার গেম নিয়ে কাজ করবে।
সত্যিকারের নির্ভরতা ইনজেকশন নয় এমন জিনিসগুলির জন্যও এই প্যাটার্নটি কার্যকর। মনে করুন যে আপনি অর্থায়নে কাজ করেন, আপনি কোনও সম্পত্তির মূল্য নির্ধারণের জন্য কিছু জটিল যুক্তি ডিজাইন করতে পারেন (একটি ডেরাইভেটিভ বলে), যা সব ভাল এবং ভাল এবং আপনি কোনও দুর্গন্ধযুক্ত মনড ছাড়াই করতে পারেন। তবে, আপনি একাধিক মুদ্রার সাথে ডিল করতে আপনার প্রোগ্রামটি পরিবর্তন করেছেন ify আপনাকে ফ্লাইতে মুদ্রার মধ্যে রূপান্তর করতে সক্ষম হতে হবে। আপনার প্রথম প্রচেষ্টাটি শীর্ষ স্তরের ফাংশনটি সংজ্ঞায়িত করা
type CurrencyDict = Map CurrencyName Dollars
currencyDict :: CurrencyDict
স্পট দাম পেতে। তারপরে আপনি এই কোডটি আপনার কোডটিতে কল করতে পারেন .... তবে অপেক্ষা করুন! কাজ করবে না! মুদ্রার অভিধানটি অপরিবর্তনীয় এবং তাই কেবল আপনার প্রোগ্রামের জীবনের জন্যই হবে না, তবে এটি সংকলিত হওয়ার সময় থেকেই ! তো তুমি কি কর? ঠিক আছে, একটি বিকল্প হ'ল পাঠক মনাদ ব্যবহার করা:
computePrice :: Reader CurrencyDict Dollars
computePrice
= do currencyDict <- ask
--insert computation here
সম্ভবত সবচেয়ে ক্লাসিক ব্যবহারের কেস হ'ল দোভাষীকে প্রয়োগ করা। তবে, আমরা এটি দেখার আগে আমাদের আরও একটি কার্যকারিতা প্রবর্তন করতে হবে
local :: (env -> env) -> Reader env a -> Reader env a
ঠিক আছে, সুতরাং হাস্কেল এবং অন্যান্য কার্যকরী ভাষা ল্যাম্বডা ক্যালকুলাসের উপর ভিত্তি করে । ল্যাম্বদা ক্যালকুলাসের মতো একটি সিনট্যাক্স রয়েছে
data Term = Apply Term Term | Lambda String Term | Var Term deriving (Show)
এবং আমরা এই ভাষার জন্য একটি মূল্যায়নকারী লিখতে চাই। এটি করার জন্য, আমাদের এমন একটি পরিবেশের ট্র্যাক রাখা দরকার, যা শর্তগুলির সাথে যুক্ত বাইন্ডিংগুলির একটি তালিকা (আসলে এটি ক্লোজার হবে কারণ আমরা স্থির স্কোপিং করতে চাই)।
newtype Env = Env ([(String, Closure)])
type Closure = (Term, Env)
আমাদের হয়ে গেলে, আমাদের একটি মান (বা একটি ত্রুটি) বের করা উচিত:
data Value = Lam String Closure | Failure String
সুতরাং, আসুন দোভাষী লিখুন:
interp' :: Term -> Reader Env Value
--when we have a lambda term, we can just return it
interp' (Lambda nv t)
= do env <- ask
return $ Lam nv (t, env)
--when we run into a value, we look it up in the environment
interp' (Var v)
= do (Env env) <- ask
case lookup (show v) env of
-- if it is not in the environment we have a problem
Nothing -> return . Failure $ "unbound variable: " ++ (show v)
-- if it is in the environment, then we should interpret it
Just (term, env) -> local (const env) $ interp' term
--the complicated case is an application
interp' (Apply t1 t2)
= do v1 <- interp' t1
case v1 of
Failure s -> return (Failure s)
Lam nv clos -> local (\(Env ls) -> Env ((nv, clos) : ls)) $ interp' t2
--I guess not that complicated!
অবশেষে, আমরা একটি তুচ্ছ পরিবেশে এটি ব্যবহার করতে পারি:
interp :: Term -> Value
interp term = runReader (interp' term) (Env [])
এবং এটি হয়। ল্যাম্বডা ক্যালকুলাসের জন্য সম্পূর্ণ কার্যকারী দোভাষী inter
এ সম্পর্কে চিন্তা করার অন্য উপায়টি জিজ্ঞাসা করা: এটি কীভাবে বাস্তবায়ন করা হয়? উত্তরটি হ'ল পাঠক মোনাড প্রকৃতপক্ষে সমস্ত মনাদাদের মধ্যে অন্যতম সরল এবং মার্জিত।
newtype Reader env a = Reader {runReader :: env -> a}
পাঠক কার্যকারিতা জন্য একটি অভিনব নাম! আমরা ইতিমধ্যে সংজ্ঞায়িত করেছি runReader
তাই এপিআই এর অন্যান্য অংশগুলির কী হবে? ঠিক আছে, প্রত্যেকটি Monad
একটি Functor
:
instance Functor (Reader env) where
fmap f (Reader g) = Reader $ f . g
এখন, একটি monad পেতে:
instance Monad (Reader env) where
return x = Reader (\_ -> x)
(Reader f) >>= g = Reader $ \x -> runReader (g (f x)) x
যা এতটা ভীতিজনক নয়। ask
সত্যিই সহজ:
ask = Reader $ \x -> x
যদিও local
খুব খারাপ না:
local f (Reader g) = Reader $ \x -> runReader g (f x)
ঠিক আছে, তাই পাঠক মনাদ একটি ফাংশন। পাঠক কেন আদৌ আছে? ভাল প্রশ্ন. আসলে, আপনার দরকার নেই!
instance Functor ((->) env) where
fmap = (.)
instance Monad ((->) env) where
return = const
f >>= g = \x -> g (f x) x
এগুলি আরও সহজ। আরও কি, ask
ঠিক আছে id
এবং local
সক্রিয় ফাংশন ক্রম সঙ্গে ফাংশন রচনা!
Reader
monad টাইপ শ্রেণীর কিছু নির্দিষ্ট বাস্তবায়ন সঙ্গে একটি ফাংশন হয়? এটি আগে বললে আমার কিছুটা হতবাক হতে সহায়তা হত। প্রথমে আমি তা পাচ্ছিলাম না। অর্ধেক পথ ধরে আমি ভেবেছিলাম "ওহ, এটি আপনাকে এমন কিছু ফেরত দেওয়ার অনুমতি দেয় যা আপনি একবার হারিয়ে যাওয়া মান সরবরাহ করার পরে আপনাকে পছন্দসই ফলাফল দেয় will" আমি এটি দরকারী বলে মনে করি, তবে হঠাৎ বুঝতে পেরেছিলাম যে কোনও ফাংশন ঠিক এটি করে।
local
ফাংশনটির আরও কিছু ব্যাখ্যা দরকার ...
(Reader f) >>= g = (g (f x))
?
x
?
আমি মনে করি আপনি যেমন আশ্চর্য হয়ে গেছেন, যতক্ষণ না আমি নিজে আবিষ্কার করেছি যে রিডার মোনাডের রূপগুলি সর্বত্র রয়েছে । আমি এটি কীভাবে আবিষ্কার করলাম? কারণ আমি কোড লিখতে থাকি যা এতে ছোট পরিবর্তন হতে পারে।
উদাহরণস্বরূপ, এক পর্যায়ে আমি code তিহাসিক মূল্যবোধগুলি মোকাবেলা করার জন্য কিছু কোড লিখছিলাম ; সময়ের সাথে পরিবর্তিত মানগুলি। এর একটি খুব সাধারণ মডেল হ'ল সময় বিন্দু থেকে সময়টির সময়কালের মান পর্যন্ত:
import Control.Applicative
-- | A History with timeline type t and value type a.
newtype History t a = History { observe :: t -> a }
instance Functor (History t) where
-- Apply a function to the contents of a historical value
fmap f hist = History (f . observe hist)
instance Applicative (History t) where
-- A "pure" History is one that has the same value at all points in time
pure = History . const
-- This applies a function that changes over time to a value that also
-- changes, by observing both at the same point in time.
ff <*> fx = History $ \t -> (observe ff t) (observe fx t)
instance Monad (History t) where
return = pure
ma >>= f = History $ \t -> observe (f (observe ma t)) t
Applicative
উদাহরণ হিসেবে বলা যায় মানে আপনি আছে employees :: History Day [Person]
এবং customers :: History Day [Person]
আপনি এটা করতে পারেন:
-- | For any given day, the list of employees followed by the customers
employeesAndCustomers :: History Day [Person]
employeesAndCustomers = (++) <$> employees <*> customers
যেমন, Functor
এবং Applicative
ইতিহাসের সাথে কাজ করার জন্য আমাদের নিয়মিত, অ-.তিহাসিক ফাংশনগুলি মানিয়ে নিতে অনুমতি দিন।
মনড উদাহরণটি স্বজ্ঞাতভাবে ফাংশনটি বিবেচনা করে বোঝা যায় (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
। ধরণের a -> History t b
একটি ফাংশন এমন একটি ফাংশন যা মানগুলির a
ইতিহাসকে মানচিত্র করে b
; উদাহরণস্বরূপ, আপনি থাকতে পারে getSupervisor :: Person -> History Day Supervisor
, এবং getVP :: Supervisor -> History Day VP
। সুতরাং মোনাড উদাহরণটি History
এই জাতীয় ফাংশন রচনা সম্পর্কে; উদাহরণস্বরূপ, getSupervisor >=> getVP :: Person -> History Day VP
যে ফাংশনটি পায় তা হ'ল যে কোনওর জন্য তারা যা করেছে Person
তার ইতিহাস VP
।
ঠিক আছে, এই History
মোনাড আসলে ঠিক একই রকম Reader
। History t a
সত্যিই একই Reader t a
(যা হিসাবে একই t -> a
)।
আরেকটি উদাহরণ: আমি সম্প্রতি হাস্কেলের ওএলএপ ডিজাইনগুলি প্রোটোটাইপ করছি । এখানে একটি ধারণা হ'ল "হাইপারকিউব", যা মানগুলির একটি সেটগুলির ছেদ থেকে ম্যাপিং। এখানে আমরা আবার যাই:
newtype Hypercube intersection value = Hypercube { get :: intersection -> value }
হাইপারকিউবেসগুলির অপারেশনের একটি সাধারণ বিষয় হ'ল হাইপারকিউবের সংশ্লিষ্ট পয়েন্টগুলিতে মাল্টিপ্লেস স্কেলার ফাংশন প্রয়োগ করা। এটির Applicative
জন্য একটি উদাহরণ নির্ধারণ করে আমরা এটি পেতে পারি Hypercube
:
instance Functor (Hypercube intersection) where
fmap f cube = Hypercube (f . get cube)
instance Applicative (Hypercube intersection) where
-- A "pure" Hypercube is one that has the same value at all intersections
pure = Hypercube . const
-- Apply each function in the @ff@ hypercube to its corresponding point
-- in @fx@.
ff <*> fx = Hypercube $ \x -> (get ff x) (get fx x)
আমি কেবল History
উপরের কোডটি কপিপাস্ট করেছি এবং নাম পরিবর্তন করেছি। যেমন আপনি বলতে পারেন, Hypercube
ঠিক আছে Reader
।
যাচ্ছে তো যাচ্ছেই। উদাহরণস্বরূপ, Reader
আপনি যখন এই মডেলটি প্রয়োগ করেন তখন ভাষা অনুবাদকরাও এতে সিদ্ধ হয় :
Reader
ask
Reader
কার্যকরকরণের পরিবেশ।local
একটি ভাল উপমাটি হ'ল একটি এটিতে "ছিদ্র" সহ একটি Reader r a
প্রতিনিধিত্ব a
করে, যা a
আমরা কোন বিষয়ে কথা বলছি তা জানতে আপনাকে বাধা দেয় । গর্তগুলি পূরণ করার জন্য a
একবার সরবরাহ করার পরে আপনি কেবল আসলটি পেতে পারেন r
। এমন অনেকগুলি জিনিস রয়েছে। উপরের উদাহরণগুলিতে, "ইতিহাস" এমন একটি মান যা কোনও সময় নির্দিষ্ট না করা পর্যন্ত গণনা করা যায় না, একটি হাইপারকিউব এমন একটি মান যা কোনও ছেদচিহ্ন নির্দিষ্ট না করা পর্যন্ত গণনা করা যায় না এবং একটি ভাষা এক্সপ্রেশন এমন একটি মান যা যা করতে পারে আপনি ভেরিয়েবলের মান সরবরাহ না করা পর্যন্ত গণনা করা হবে না। এটি আপনাকে কেন Reader r a
একইরূপে একটি অন্তর্দৃষ্টি দেয় r -> a
কারণ এই জাতীয় ফাংশন স্বজ্ঞাতভাবে একটি a
অনুপস্থিত একটিও r
।
সুতরাং Functor
, Applicative
এবং Monad
উদাহরণগুলি Reader
হ'ল যে ক্ষেত্রে আপনি "একটি a
অনুপস্থিত একটি ", বাছাইয়ের যে কোনও কিছুর মডেলিং করছেন এমন ক্ষেত্রে খুব দরকারী সাধারণীকরণ r
এবং আপনাকে এই "অসম্পূর্ণ" অবজেক্টগুলিকে সম্পূর্ণরূপে আচরণ করার অনুমতি দেয়।
তবুও একই কথা অন্য উপায়: একটি Reader r a
এমন কিছু বিষয় যা হ্রাস হয় r
এবং উৎপন্ন a
, এবং Functor
, Applicative
এবং Monad
দৃষ্টান্ত সঙ্গে কাজ করার জন্য মৌলিক নিদর্শন হয় Reader
গুলি। Functor
= এমন একটি তৈরি করুন Reader
যা অন্যের আউটপুট পরিবর্তন করে Reader
; Applicative
= Reader
একই ইনপুটটিতে দুটি গুলি সংযুক্ত করুন এবং তাদের ফলাফলগুলি একত্রিত করুন; Monad
= এ এর একটি ফলাফল পরিদর্শন করুন Reader
এবং এটি অন্য নির্মাণে ব্যবহার করুন Reader
। local
এবং withReader
ফাংশন = একটি করা Reader
যে মডিফাই অন্য ইনপুট Reader
।
GeneralizedNewtypeDeriving
আহরণ করা এক্সটেনশন Functor
, Applicative
, Monad
তাদের অন্তর্নিহিত ধরনের উপর ভিত্তি করে newtypes জন্য, ইত্যাদি।
জাভা বা সি ++ এ আপনি যে কোনও সমস্যা ছাড়াই যে কোনও জায়গা থেকে কোনও পরিবর্তনশীল অ্যাক্সেস করতে পারেন। আপনার কোডটি বহু-থ্রেড হয়ে গেলে সমস্যাগুলি উপস্থিত হয়।
হাস্কেলে আপনার কাছে দুটি কার্য একটি ফাংশন থেকে অন্য ফাংশনে যাওয়ার জন্য দুটি উপায় রয়েছে:
fn1 -> fn2 -> fn3
, ফাংশন fn2
প্যারামিটার যা আপনি থেকে পাস প্রয়োজন নাও হতে পারে fn1
জন্য fn3
।পাঠক মনাদ ফাংশনগুলির মধ্যে ভাগ করতে চান এমন ডেটা কেবল সরিয়ে দেয়। ফাংশনগুলি সেই ডেটাটি পড়তে পারে তবে এটি পরিবর্তন করতে পারে না। রিডার মোনাদ এটাই করে। ঠিক আছে, প্রায় সব। এখানে বিভিন্ন ধরণের ক্রিয়াকলাপ রয়েছে local
তবে প্রথমবারের মতো আপনি asks
কেবলমাত্র আটকে থাকতে পারেন ।
do
নোট -ইন- নোটেশনে প্রচুর 'অপরিহার্য-শৈলী' কোড লেখার পক্ষে নিজেকে খুঁজে পাওয়া খুব সহজ , যা খাঁটি ফাংশনে রিফ্যাক্টর হওয়া ভাল better
where
ধারা দ্বারা একটিতে সংযুক্ত থাকে , তবে এটি ভেরিয়েবলগুলি পাস করার 3 য় উপায় হিসাবে গ্রহণ করা হবে?