আমি হাসকেলে আসলেই দক্ষ নই, সুতরাং এটি খুব সহজ প্রশ্ন হতে পারে।
র্যাঙ্ক 2 টিপগুলি কোন ভাষার সীমাবদ্ধতা সমাধান করে? হাস্কেলের মধ্যে ফাংশনগুলি ইতিমধ্যে বহুবিধ যুক্তিগুলি সমর্থন করে না?
আমি হাসকেলে আসলেই দক্ষ নই, সুতরাং এটি খুব সহজ প্রশ্ন হতে পারে।
র্যাঙ্ক 2 টিপগুলি কোন ভাষার সীমাবদ্ধতা সমাধান করে? হাস্কেলের মধ্যে ফাংশনগুলি ইতিমধ্যে বহুবিধ যুক্তিগুলি সমর্থন করে না?
উত্তর:
হাস্কেলের মধ্যে কি ফাংশনগুলি ইতিমধ্যে বহুবৈচিত্র্য যুক্তি সমর্থন করে না?
তারা করে তবে কেবল 1 পদমর্যাদার This
উদাহরণস্বরূপ নীচের ফাংশনটি এই এক্সটেনশনটি ছাড়া টাইপ করা যাবে না কারণ g
এর সংজ্ঞাতে বিভিন্ন যুক্তি ধরণের সাথে ব্যবহৃত হয় f
:
f g = g 1 + g "lala"
মনে রাখবেন যে কোনও ফাংশনের যুক্তি হিসাবে পলিমারফিক ফাংশনটি পাস করা পুরোপুরি সম্ভব। সুতরাং মত map id ["a","b","c"]
কিছু পুরোপুরি আইনী। তবে ফাংশনটি কেবল এটি মনোমরফিক হিসাবে ব্যবহার করতে পারে। উদাহরণস্বরূপ map
ব্যবহার করেid
যেমন এটি টাইপ ছিল String -> String
। এবং অবশ্যই আপনি প্রদত্ত প্রকারের পরিবর্তে একটি সাধারণ মনোমরফিক ফাংশনও পাস করতে পারেন id
। র্যাঙ্ক 2 টাইপ ব্যতীত কোনও ফাংশনের পক্ষে তার আর্গুমেন্টটি পলিমারফিক ফাংশন হওয়া আবশ্যক এবং এইভাবে পলিমারফিক ফাংশন হিসাবে এটি ব্যবহার করার কোনও উপায় নেই।
f' g x y = g x + g y
। এটির অনুমানযুক্ত র্যাঙ্ক -1 প্রকার forall a r. Num r => (a -> r) -> a -> a -> r
। যেহেতু forall a
ফাংশন তীরগুলির বাইরে রয়েছে, কলকারীকে প্রথমে কোনও প্রকার বাছাই করতে হবে a
; যদি তারা বাছাই করে Int
, আমরা পাই f' :: forall r. Num r => (Int -> r) -> Int -> Int -> r
, এবং এখন আমরা g
যুক্তিটি স্থির করেছি যাতে এটি নিতে পারে Int
তবে না String
। আমরা যদি সক্ষম করে RankNTypes
থাকি তবে আমরা f'
প্রকারের সাথে মন্তব্য করতে পারি forall b c r. Num r => (forall a. a -> r) -> b -> c -> r
। এটি ব্যবহার করতে পারবেন না, যদিও — কী হবে g
?
আপনি সিস্টেম এফ অধ্যয়ন না করা হলে উচ্চ-স্তরের পলিমারফিজম বোঝা শক্ত সরাসরি , কারণ হাস্কেল সরলতার স্বার্থে আপনার কাছ থেকে এর বিশদগুলি গোপন করার জন্য তৈরি করা হয়েছে।
তবে মূলত, মোটামুটি ধারণাটি হ'ল পলিমারফিক ধরণের ধরণের প্রকৃতপক্ষে a -> b
হ্যাসকেলে তারা ফর্মটি রাখে না; বাস্তবে, এগুলি দেখতে সর্বদা সুস্পষ্ট কোয়ান্টিফায়ার সহ:
id :: ∀a.a → a
id = Λt.λx:t.x
যদি আপনি "∀" চিহ্নটি না জানেন তবে এটি "সবার জন্য" হিসাবে পড়া হয়; ∀x.dog(x)
মানে "সমস্ত এক্স এর জন্য, এক্স একটি কুকুর" " "Λ" হ'ল মূলধন ল্যাম্বদা, প্রকারের পরামিতিগুলিকে বিমূর্ত করার জন্য ব্যবহৃত হয়; দ্বিতীয় লাইনটি যা বলে তা হ'ল আইডি একটি ফাংশন যা একটি প্রকার নেয়t
এবং তারপরে সেই ধরণের দ্বারা প্যারামিট্রাইজ করা একটি ফাংশন দেয়।
আপনি দেখতে পাচ্ছেন, সিস্টেম এফ-এ আপনি ঠিক এখনই id
কোনও মানটির মতো কোনও ফাংশন প্রয়োগ করতে পারবেন না ; প্রথমে আপনাকে কোনও মানটিতে প্রয়োগ করা একটি λ-ফাংশন পেতে আপনাকে কোনও ধরণের Λ- ফাংশন প্রয়োগ করতে হবে। উদাহরণস্বরূপ:
(Λt.λx:t.x) Int 5 = (λx:Int.x) 5
= 5
স্ট্যান্ডার্ড হাস্কেল (যেমন, হাস্কেল 98 এবং 2010) এই ধরণের কোয়ান্টিফায়ার, মূলধন ল্যাম্বডাস এবং টাইপ অ্যাপ্লিকেশন না রেখে আপনার জন্য এটি সহজ করে তোলে, তবে জিএইচসি যখন সংকলনের জন্য প্রোগ্রামটি বিশ্লেষণ করে তখন সেই দৃশ্যের পিছনে রাখে। (আমার বিশ্বাস, রানটাইম ওভারহেড ছাড়াই এটি সমস্ত সংকলন-কালীন স্টাফ।)
তবে হাস্কেলের এটির স্বয়ংক্রিয়ভাবে পরিচালনার অর্থ এটি ধরে নেওয়া হয় যে "∀" কোনও ফাংশন ("→") টাইপের বাম-হাতের শাখায় কখনই উপস্থিত হয় না। Rank2Types
এবং RankNTypes
এই বিধিনিষেধগুলি বন্ধ করে দেয় এবং যেখানে সন্নিবেশ করানো হবে তার জন্য আপনাকে হাস্কেলের ডিফল্ট নিয়মগুলিকে ওভাররাইড করার অনুমতি দেয়forall
।
তুমি কেন এটা করতে চাও? কারণ পূর্ণ, সীমাহীন সিস্টেম এফ হেলা শক্তিশালী, এবং এটি প্রচুর শীতল স্টাফ করতে পারে। উদাহরণস্বরূপ, প্রকারের আড়ালকরণ এবং মড্যুলারিটি উচ্চ-স্তরের প্রকারগুলি ব্যবহার করে প্রয়োগ করা যেতে পারে। উদাহরণস্বরূপ নিম্নলিখিত র্যাঙ্ক -1 ধরণের (দৃশ্য সেট করার জন্য) একটি সরল পুরানো ফাংশন ধরুন:
f :: ∀r.∀a.((a → r) → a → r) → r
ব্যবহার করতে f
, কলকারীকে প্রথমে কোন ধরণের জন্য ব্যবহার করতে হবে তা চয়ন করতে হবে r
এবং a
তারপরে ফলাফলের ধরণের যুক্তি সরবরাহ করতে হবে । সুতরাং আপনি বাছাই করতে পারেন r = Int
এবং a = String
:
f Int String :: ((String → Int) → String → Int) → Int
তবে এখন এটি নিম্নলিখিত উচ্চ-স্তরের প্রকারের সাথে তুলনা করুন:
f' :: ∀r.(∀a.(a → r) → a → r) → r
এই ধরণের একটি ফাংশন কীভাবে কাজ করে? ভাল, এটি ব্যবহার করার জন্য, প্রথমে আপনি কোন ধরণের জন্য ব্যবহার করবেন তা নির্দিষ্ট করে দিন r
। বলুন আমরা বাছাই Int
:
f' Int :: (∀a.(a → Int) → a → Int) → Int
কিন্তু এখন ∀a
হয় ভিতরে ফাংশন তীর, যাতে আপনি কি ধরনের জন্য ব্যবহার করতে নিতে পারে না a
; আপনার অবশ্যই f' Int
উপযুক্ত ধরণের একটি function- ফাংশনে আবেদন করতে হবে । এর অর্থ হল যে প্রয়োগকারী f'
কোন ধরণের a
কল করতে হবে তা কল করার জন্য নয়f'
। উচ্চ-স্তরের প্রকারগুলি ছাড়াই, বিপরীতে, কলার সর্বদা প্রকারগুলি চয়ন করে।
এটি কি জন্য দরকারী? ভাল, আসলে অনেক কিছুর জন্য, তবে একটি ধারণা হ'ল আপনি এটিকে অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিংয়ের মতো জিনিসগুলির মডেল করতে ব্যবহার করতে পারেন, যেখানে "অবজেক্টস" কিছু লুকানো ডেটা বান্ডিল করে এমন কিছু পদ্ধতি যা গোপন তথ্যগুলিতে কাজ করে। সুতরাং উদাহরণস্বরূপ, দুটি পদ্ধতির একটি অবজেক্ট — একটি যা প্রত্যাবর্তন করে Int
এবং অন্যটি যা প্রত্যাবর্তন করে String
, এই ধরণের মাধ্যমে প্রয়োগ করা যেতে পারে:
myObject :: ∀r.(∀a.(a → Int, a -> String) → a → r) → r
কিভাবে কাজ করে? অবজেক্টটি কোনও ফাংশন হিসাবে বাস্তবায়িত হয় যার মধ্যে লুকানো ধরণের কিছু অভ্যন্তরীণ ডেটা থাকে a
। বস্তুটি আসলে ব্যবহার করতে, তার ক্লায়েন্টরা একটি "কলব্যাক" ফাংশনে পাস করে যা বস্তু দুটি পদ্ধতির সাহায্যে কল করবে। উদাহরণ স্বরূপ:
myObject String (Λa. λ(length, name):(a → Int, a → String). λobjData:a. name objData)
এখানে আমরা মূলত অবজেক্টের দ্বিতীয় পদ্ধতিটি আহ্বান করছি, যার ধরণটি a → String
অজানা a
। ভাল, myObject
এর ক্লায়েন্টদের কাছে অজানা ; তবে এই ক্লায়েন্টরা স্বাক্ষর থেকে জানতে পারে যে তারা এটিতে দুটি ফাংশনের যে কোনও একটি প্রয়োগ করতে সক্ষম হবে এবং একটি Int
বা একটি পাবেString
।
আসল হাস্কেলের উদাহরণস্বরূপ, নীচে কোডটি আমি লিখেছিলাম যা আমি নিজে শিখিয়েছি RankNTypes
। এটি এমন একটি প্রকার প্রয়োগ করে ShowBox
যা কিছু শ্রেণিবদ্ধের সাথে তার Show
শ্রেণীর উদাহরণের সাথে একত্রে কিছু বান্ডিল করে nd নোট করুন যে নীচে অবস্থিত উদাহরণে, আমি একটি তালিকা তৈরি করেছি ShowBox
যার প্রথম উপাদানটি একটি সংখ্যা থেকে তৈরি হয়েছিল, এবং দ্বিতীয়টি স্ট্রিং থেকে। যেহেতু প্রকারগুলি উচ্চ-র্যাঙ্কের প্রকারগুলি ব্যবহার করে আড়াল করা থাকে তাই এটি প্রকারের চেকিং লঙ্ঘন করে না।
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ImpredicativeTypes #-}
type ShowBox = forall b. (forall a. Show a => a -> b) -> b
mkShowBox :: Show a => a -> ShowBox
mkShowBox x = \k -> k x
-- | This is the key function for using a 'ShowBox'. You pass in
-- a function @k@ that will be applied to the contents of the
-- ShowBox. But you don't pick the type of @k@'s argument--the
-- ShowBox does. However, it's restricted to picking a type that
-- implements @Show@, so you know that whatever type it picks, you
-- can use the 'show' function.
runShowBox :: forall b. (forall a. Show a => a -> b) -> ShowBox -> b
-- Expanded type:
--
-- runShowBox
-- :: forall b. (forall a. Show a => a -> b)
-- -> (forall b. (forall a. Show a => a -> b) -> b)
-- -> b
--
runShowBox k box = box k
example :: [ShowBox]
-- example :: [ShowBox] expands to this:
--
-- example :: [forall b. (forall a. Show a => a -> b) -> b]
--
-- Without the annotation the compiler infers the following, which
-- breaks in the definition of 'result' below:
--
-- example :: forall b. [(forall a. Show a => a -> b) -> b]
--
example = [mkShowBox 5, mkShowBox "foo"]
result :: [String]
result = map (runShowBox show) example
পিএস: ExistentialTypes
জিএইচসি কীভাবে আসে সে সম্পর্কে কারও মনে এই প্রশ্নটি পড়ে forall
, আমি বিশ্বাস করি কারণ এর কারণ হ'ল কারণ তিনি এই ধরণের কৌশলটি পর্দার আড়ালে ব্যবহার করছেন।
exists
কীওয়ার্ড থাকলে আপনি একটি অস্তিত্বের ধরণের সংজ্ঞা দিতে পারেন (উদাহরণস্বরূপ) data Any = Any (exists a. a)
, যেখানে Any :: (exists a. a) -> Any
। → প্রশ্ন ∀xP (x) এর → প্রশ্ন ≡ (∃xP (x) এর) ব্যবহার করে, আমরা যে Any
একটি টাইপ হতে পারে forall a. a -> Any
এবং যেখানে যে forall
শব্দ থেকে আসে। আমি বিশ্বাস করি যে জিএইচসি দ্বারা প্রয়োগ করা অস্তিত্বের ধরণগুলি কেবলমাত্র সাধারণ ডেটা টাইপ যা সমস্ত প্রয়োজনীয় টাইপক্লাসের অভিধানও বহন করে (দুঃখিত, আমি এর ব্যাক আপ করার কোনও রেফারেন্স খুঁজে পাইনি)।
data ApplyBox r = forall a. ApplyBox (a -> r) a
; যখন আপনি প্যাটার্নটির সাথে মেলে ApplyBox f x
, আপনি পাবেন f :: h -> r
এবং x :: h
একটি "লুকানো" সীমাবদ্ধ টাইপের জন্য h
। যদি আমি সঠিকভাবে বুঝতে পারি তবে টাইপক্লাস অভিধানের কেসটি এরকম কিছুতে অনুবাদ করা হয়: এরকম কিছুতে অনুবাদ করা data ShowBox = forall a. Show a => ShowBox a
হয় data ShowBox' = forall a. ShowBox' (ShowDict' a) a
; instance Show ShowBox' where show (ShowBox' dict val) = show' dict val
; show' :: ShowDict a -> a -> String
।
লুইস ক্যাসিলাসের উত্তর 2 ধরণের র্যাঙ্কটি কী তা বোঝায় সে সম্পর্কে প্রচুর দুর্দান্ত তথ্য দেয় তবে আমি কেবল একটি পয়েন্টে প্রসারিত করব যা তিনি আবরণ করেন নি। বহুমুখী হওয়ার জন্য একটি যুক্তি প্রয়োজন কেবল এটি একাধিক প্রকারের সাথে ব্যবহার করার অনুমতি দেয় না; এটি এই ফাংশনটি তার যুক্তি (গুলি) এর সাথে কী করতে পারে এবং কীভাবে এটির ফলাফল তৈরি করতে পারে তা সীমাবদ্ধ করে। অর্থাৎ এটি কলারকে কম স্বাচ্ছন্দ্য দেয় । আপনি এটি কেন করতে চান? আমি একটি সাধারণ উদাহরণ দিয়ে শুরু করব:
ধরুন আমাদের কাছে একটি ডেটা টাইপ রয়েছে
data Country = BigEnemy | MediumEnemy | PunyEnemy | TradePartner | Ally | BestAlly
এবং আমরা একটি ফাংশন লিখতে চাই
f g = launchMissilesAt $ g [BigEnemy, MediumEnemy, PunyEnemy]
এটি এমন একটি ফাংশন নেয় যা তার দেওয়া তালিকার অন্যতম উপাদান বেছে নেবে এবং IO
সেই লক্ষ্যে একটি অ্যাকশন লঞ্চ ক্ষেপণাস্ত্রগুলি ফিরিয়ে দেবে । আমরা f
একটি সহজ ধরণের দিতে পারে :
f :: ([Country] -> Country) -> IO ()
সমস্যাটি হ'ল আমরা দুর্ঘটনাক্রমে চালাতে পারি
f (\_ -> BestAlly)
এবং তারপরে আমরা বড় সমস্যায় পড়ে যাব! প্রদান f
একটি র্যাঙ্ক 1 বহুরুপী টাইপ
f :: ([a] -> a) -> IO ()
মোটেই সহায়তা করে না, কারণ a
আমরা কল করার সময় ধরণটি বেছে নিইf
এবং আমরা Country
আমাদের দূষিতটিকে \_ -> BestAlly
আবার এটিতে বিশেষজ্ঞ এবং ব্যবহার করি । সমাধানটি হ'ল একটি র্যাঙ্ক 2 ধরণের ব্যবহার করা:
f :: (forall a . [a] -> a) -> IO ()
এখন আমরা যে ফাংশনটি পাস \_ -> BestAlly
করব তাতে পলিমারফিক হওয়া প্রয়োজন, তাই চেক টাইপ করবেন না! প্রকৃতপক্ষে, যে উপাদানটিকে প্রদত্ত তালিকার মধ্যে নেই তার কোনও ফাংশন ফিরিয়ে দেওয়া হবে না (যদিও এমন কিছু ফাংশন যা অসীম লুপগুলিতে যায় বা ত্রুটি উত্পন্ন করে এবং তাই কখনও ফিরে আসে না) এটি করা হবে না।
উপরেরটি অবশ্যই স্বীকৃত, তবে এই কৌশলটির একটি প্রকরণটি ST
মনাদকে নিরাপদ করে তোলার মূল বিষয় ।
অন্যান্য উত্তরগুলি যেমন উচ্চতর-র্যাঙ্কের ধরণের থাকে তেমন বহিরাগত নয়। বিশ্বাস করুন বা না করুন, অনেকগুলি অবজেক্ট-ভিত্তিক ভাষা (জাভা এবং সি # সহ) তাদের বৈশিষ্ট্যযুক্ত করে। (অবশ্যই, সেই সম্প্রদায়ের মধ্যে কেউ তাদের ভয়ঙ্কর-শোনার নাম "উচ্চ-স্তরের প্রকারগুলি" দ্বারা চেনে না))
আমি যে উদাহরণটি দিতে যাচ্ছি তা হ'ল ভিজিটর প্যাটার্নের একটি পাঠ্যপুস্তক বাস্তবায়ন, যা আমি আমার প্রতিদিনের কাজে সব সময় ব্যবহার করি। এই উত্তরটি দর্শকের প্যাটার্নের পরিচিতি হিসাবে নয়; যে জ্ঞান অন্য কোথাও সহজেই উপলব্ধ ।
এই চর্চিত কাল্পনিক এইচআর অ্যাপ্লিকেশনটিতে আমরা এমন কর্মচারীদের উপর পরিচালনা করতে চাই যা পুরো সময়ের স্থায়ী কর্মী বা অস্থায়ী ঠিকাদার হতে পারে। আমার দর্শনার্থী প্যাটার্নের পছন্দসই বৈকল্পিক (এবং প্রকৃতপক্ষে এটি প্রাসঙ্গিক RankNTypes
) ভিজিটরের ফেরতের ধরণের প্যারামিটারাইজেস করে।
interface IEmployeeVisitor<T>
{
T Visit(PermanentEmployee e);
T Visit(Contractor c);
}
class XmlVisitor : IEmployeeVisitor<string> { /* ... */ }
class PaymentCalculator : IEmployeeVisitor<int> { /* ... */ }
মুল বক্তব্যটি হ'ল বিভিন্ন রিটার্নের ধরণের সংখ্যক দর্শক সমস্ত একই ডেটাতে পরিচালনা করতে পারে operate এর অর্থ IEmployee
কী T
হওয়া উচিত তা সম্পর্কে কোনও মতামত প্রকাশ করতে হবে না।
interface IEmployee
{
T Accept<T>(IEmployeeVisitor<T> v);
}
class PermanentEmployee : IEmployee
{
// ...
public T Accept<T>(IEmployeeVisitor<T> v)
{
return v.Visit(this);
}
}
class Contractor : IEmployee
{
// ...
public T Accept<T>(IEmployeeVisitor<T> v)
{
return v.Visit(this);
}
}
আমি ধরণের প্রতি আপনার দৃষ্টি আকর্ষণ করতে ইচ্ছুক। পর্যবেক্ষণ করুন যে IEmployeeVisitor
সর্বজনীনভাবে তার রিটার্নের প্রকারটি IEmployee
পরিমাণযুক্ত করে , যেখানে এটি তার Accept
পদ্ধতির অভ্যন্তরে পরিমাণযুক্ত করে - এটি উচ্চতর পদে বলে। সি # থেকে হাস্কেলের কাছে কঙ্কুলভাবে অনুবাদ করা:
data IEmployeeVisitor r = IEmployeeVisitor {
visitPermanent :: PermanentEmployee -> r,
visitContractor :: Contractor -> r
}
newtype IEmployee = IEmployee {
accept :: forall r. IEmployeeVisitor r -> r
}
তাই সেখানে যদি আপনি এটি আছে। আপনি জেনেরিক পদ্ধতিযুক্ত প্রকারগুলি লিখলে উচ্চ-স্তরের প্রকারগুলি সি # তে প্রদর্শিত হয়।
স্ট্যানফোর্ডে ব্রায়ান ও'সুলিভানের হাস্কেল কোর্সের স্লাইডগুলি আমাকে বুঝতে সহায়তা করেছে Rank2Types
।
অবজেক্ট ওরিয়েন্টেড ভাষাগুলির সাথে পরিচিতদের জন্য, একটি উচ্চ-স্তরের ফাংশন কেবল একটি জেনেরিক ফাংশন যা তার যুক্তি হিসাবে অন্য জেনেরিক ফাংশন হিসাবে প্রত্যাশা করে।
যেমন টাইপস্ক্রিপ্টে আপনি লিখতে পারেন:
type WithId<T> = T & { id: number }
type Identifier = <T>(obj: T) => WithId<T>
type Identify = <TObj>(obj: TObj, f: Identifier) => WithId<TObj>
জেনেরিক ফাংশন প্রকারটি কীভাবে জেনেরিক ফাংশনটির Identify
দাবি করে তা দেখুন Identifier
? এটি Identify
একটি উচ্চ-র্যাঙ্ক ফাংশন করে।
Accept
র্যাঙ্ক -১ পলিমর্ফিক টাইপ রয়েছে তবে এটি একটি পদ্ধতি IEmployee
যা নিজেই র্যাঙ্ক -২। যদি কেউ আমাকে একটি দেয় তবে IEmployee
আমি এটিকে খুলতে এবং Accept
যেকোন প্রকারে এর পদ্ধতিটি ব্যবহার করতে পারি ।
Visitee
ক্লাসের মাধ্যমে আপনার উদাহরণটিও র্যাঙ্ক -২ । একটি ফাংশন f :: Visitee e => T e
হ'ল (একবার ক্লাস স্টাফগুলি ডিজাইন করা হয়) মূলত f :: (forall r. e -> Visitor e r -> r) -> T e
। হাস্কেল 2010 আপনাকে এর মতো ক্লাস ব্যবহার করে সীমিত র্যাঙ্ক -2 পলিমারফিজম দিয়ে দূরে যেতে দেয়।
forall
আমার উদাহরণে ভাসা করতে পারবেন না । আমার হাতে কোনও রেফারেন্স নেই, তবে আপনি "আপনার টাইপ ক্লাসগুলি স্ক্র্যাপ করুন" এ ভালভাবে কিছু পেতে পারেন । উচ্চ-র্যাঙ্কের বহুবৈচিত্র্য প্রকৃতপক্ষে টাইপ-চেকিংয়ের সমস্যাগুলি পরিচয় করিয়ে দিতে পারে তবে শ্রেণি ব্যবস্থায় অন্তর্নিহিত সীমিত সাজাই ঠিক আছে।