بيانات عالية

نعم ، نعم ، لم تحلم وسمعت بشكل صحيح - إنه من نوع عالي. النوع هو مصطلح نظرية النوع الذي يعني بشكل أساسي نوعًا من النوع [البيانات].

لكن أولاً ، بعض الكلمات.

نُشرت عدة مقالات في حبري ، وصفت بالتفصيل طريقة التحقق من صحة البيانات باللغات الوظيفية.

هذه المقالة هي سنتي الخمسة في هذا الضجيج. سننظر في التحقق من صحة البيانات في هاسكل.

نوع التحقق


تم اعتبار مثال لتقنية التحقق باستخدام التحقق من النوع:

type EmailContactInfo = String type PostalContactInfo = String data ContactInfo = EmailOnly EmailContactInfo | PostOnly PostalContactInfo | EmailAndPost (EmailContactInfo, PostalContactInfo) data Person = Person { pName :: String, , pContactInfo :: ContactInfo, } 

باستخدام هذه الطريقة ، من المستحيل ببساطة إنشاء بيانات غير صحيحة. ومع ذلك ، على الرغم من حقيقة أن مثل هذا التحقق سهل جدًا في الإنشاء والقراءة ، فإن استخدامه يفرض عليك كتابة الكثير من الروتين وإجراء الكثير من التغييرات على الشفرة. وهذا يعني أن استخدام مثل هذه الطريقة يقتصر فقط على البيانات الهامة حقًا.

التحقق من البيانات عالية




في هذه المقالة ، سنلقي نظرة على طريقة تحقق مختلفة - باستخدام بيانات عالية الجودة.

لنفترض أن لدينا نوع بيانات:

 data Person = Person { pName :: String , pAge :: Int } 

ولن نتحقق من صحة البيانات إلا إذا كانت جميع حقول السجل صالحة.
نظرًا لأن Haskell متفوقة في الوظائف على معظم اللغات الوظيفية ، فيمكنها بسهولة التخلص من معظم الإجراءات الروتينية.

من الممكن هنا ، وبالتالي يتم استخدام هذه الطريقة على نطاق واسع بين مؤلفي المكتبات في هاسكل.

لأغراض المناقشة ، دعنا نتخيل أننا نريد أن يقوم المستخدم بملء المعلومات الشخصية من خلال نموذج ويب أو أي شيء آخر. وبعبارة أخرى ، من الممكن أن يفسدوا ملء جزء من المعلومات ، وليس بالضرورة إبطال بقية بنية البيانات. إذا قاموا بملء الهيكل بأكمله بنجاح ، نود الحصول على فرشاة شخص مليئة.

تتمثل إحدى طرق وضع النماذج في استخدام نوع بيانات ثانٍ:

 data MaybePerson = MaybePerson  { mpName :: Maybe String  , mpAge :: Maybe Int  } 

حيث أذكر أنه تم استخدام النوع الاختياري:

 -- already in Prelude data Maybe a = Nothing | Just a 

من هنا ، تكون وظيفة التحقق من الصحة بسيطة للغاية:

 validate :: MaybePerson -> Maybe Person validate (MaybePerson name age) =  Person <$> name <*> age 

مزيد من التفاصيل حول الدالات (<$>) و (<*>)
الوظيفة (<$>) هي مجرد مرادف infix لـ fmap Functor

 -- already in Prelude fmap :: Functor f => (a -> b) -> fa -> fb (<$>) :: Functor f => (a -> b) -> fa -> fb (<$>) = fmap 

AND (<*>) هي وظيفة لتطبيق Functional Functor

 -- already in Prelude (<*>) :: Applicative f => f (a -> b) -> fa -> fb 

ولنوع اختياري ، هذه الوظائف لها التعريف التالي

 -- already in Prelude (<$>) :: (a -> b) -> Maybe a -> Maybe b _ <$> Nothing = Nothing f <$> (Just a) = Just (fa) (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b (Just f) <*> m = f <$> m Nothing <*> _ = Nothing 


يعمل التحقق من الصحة الخاص بنا ، ولكن من المزعج كتابة رمز روتيني إضافي يدويًا ، حيث يتم ذلك بشكل ميكانيكي بالكامل. علاوة على ذلك ، يعني تكرار هذه الجهود أننا سنحتاج إلى استخدام أدمغتنا في المستقبل للتأكد من أن جميع التعريفات الثلاثة لا تزال متزامنة. هل سيكون رائعا إذا كان المترجم يمكنه التعامل مع هذا؟

مفاجأة! يستطيع! عائلة طويلة ستساعدنا!

في هاسكل ، هناك شيء مثل جنس ، إنه نوع ، وأبسط وأدق تفسير هو أن الجنس هو نوع من النوع [البيانات]. النوع الأكثر استخدامًا هو * ، والذي يمكن تسميته بـ "نهائي"

 ghci> :k Int Int :: * ghci> :k String String :: * ghci> :k Maybe Int Maybe Int :: * ghci> :k Maybe String Maybe String :: * ghci> :k [Int] [Int] :: * 

وأي نوع من ربما ؟
 ghci> :k Maybe Maybe :: * -> * ghci> :k [] [] :: * -> * 

هذا مثال من نوع عالي.

لاحظ أنه يمكننا وصف كل من الشخص و ربما الأشخاص باستخدام البيانات الفردية عالية الجودة التالية :

 data Person' f = Person { pName :: f String , pAge :: f Int } 

هنا نقوم بتوصيف الشخص على شيء f (مع الجنس * -> * ) ، والذي يسمح لنا بالقيام بما يلي من أجل استخدام الأنواع الأصلية:

 type Person = Person' Identity type MaybePerson = Person' Maybe 

هنا نستخدم نوعًا بسيطًا من الهوية المجمع

 -- already in Prelude newtype Identity a = Identity { runIdentity :: a } 

على الرغم من أن هذا يعمل ، إلا أنه مزعج قليلاً في حالة الشخص ، حيث يتم الآن تغليف جميع بياناتنا داخل الهوية :

 ghci> :t pName @Identity pName :: Person -> Identity String ghci> :t runIdentity. pName runIdentity. pName :: Person -> String 

يمكننا القضاء على هذا الإزعاج بشكل تافه ، وبعد ذلك سنفحص سبب فائدة هذا التعريف للشخص . للتخلص من المعرّفات ، يمكننا استخدام مجموعة من الأنواع (وظيفة على مستوى النوع) تمحوها:

 {-# LANGUAGE TypeFamilies #-} -- "Higher-Kinded Data" type family HKD fa where HKD Identity a = a HKD fa = fa data Person' f = Person  { pName :: HKD f String  , pAge :: HKD f Int  } deriving (Generic) 

نحن بحاجة إلى اختتام عام للجزء الثاني من المقالة.

يعني استخدام عائلة من نوع HKD أن GHC يمحو تلقائيًا أي أغلفة للهوية في وجهات نظرنا:

 ghci> :t pName @Identity pName :: Person -> String ghci> :t pName @Maybe pName :: Person -> Maybe String 

وهي بالتحديد هذه النسخة من الشخص من النوع الأعلى التي يمكن استخدامها بأفضل طريقة كبديل عن بديل لنسختنا الأصلية.

السؤال الواضح هو ما اشتريناه لأنفسنا مع كل هذا العمل المنجز. دعنا نعود إلى صياغة التحقق لمساعدتنا في الإجابة على هذا السؤال.

يمكننا الآن إعادة كتابتها باستخدام أسلوبنا الجديد:

 validate :: Person' Maybe -> Maybe Person validate (Person name age) = Person <$> name <*> age 

لا تغيير مثير للاهتمام؟ لكن المؤامرة هي مدى الحاجة إلى التغيير. كما ترون ، فإن نوعنا ونمطنا فقط يتطابقان مع التنفيذ الأولي. ما هو أنيق هنا هو أننا دمجنا الآن شخصًا وربما شخصًا في نفس الرأي ، وبالتالي لم يعدا متصلين فقط بالمعنى الاسمي.

الوراثة ووظيفة التحقق الأعم


يجب كتابة الإصدار الحالي من وظيفة التحقق لكل نوع بيانات جديد ، على الرغم من أن الرمز روتيني تمامًا.

يمكننا كتابة إصدار التحقق الذي سيعمل مع أي نوع بيانات أعلى.

يمكن للمرء استخدام Template Haskell ، ولكنه يولد رمزًا ويستخدم فقط في الحالات القصوى. لن نفعل.

السر هو الاتصال بـ GHC.Generics . إذا لم تكن على دراية بالمكتبة ، فإنها توفر تشابهًا من نوع بيانات Haskell المنتظم إلى تمثيل عام يمكن التحكم فيه هيكليًا من قبل مبرمج ذكي (أي: نحن.) من خلال توفير رمز حتى نغير الأنواع الثابتة والمنتجات والمنتجات المشتركة ، يمكننا فرض كتابة GHC لنا رمز مستقلة عن النوع. هذه تقنية أنيقة للغاية ستدغدغ أصابع قدميك إذا لم تكن قد رأيتها من قبل.

في النهاية ، نريد الحصول على شيء مثل:

 validate :: _ => d Maybe -> Maybe (d Identity) 

من وجهة نظر Generics ، يمكن تقسيم أي نوع في الغالب إلى عدة تصميمات:

 -- undefined data, lifted version of Empty data V1 p -- Unit: used for constructors without arguments, lifted version of () data U1 p = U1 -- a container for ac, Constants, additional parameters and recursion of kind * newtype K1 icp = K1 { unK1 :: c } -- a wrapper, Meta-information (constructor names, etc.) newtype M1 itfp = M1 { unM1 :: fp } -- Sums: encode choice between constructors, lifted version of Either data (:+:) fgp = L1 (fp) | R1 (gp) -- Products: encode multiple arguments to constructors, lifted version of (,) data (:*:) fgp = (fp) :*: (gp) 

أي أنه قد تكون هناك هياكل غير مهيأة ، وهياكل خالية من الجدل ، وهياكل ثابتة ، ومعلوماتية (منشئي ، وما إلى ذلك). وأيضًا جمعيات الهياكل - إجمالي أو جمعيات من النوع OR-OR والرسوم المتحركة ، فهي أيضًا جمعيات أو سجلات قصيرة.

أولاً ، نحتاج إلى تحديد فئة ستكون العمود الفقري لتحولنا. من التجربة ، هذا هو دائمًا الجزء الأكثر صعوبة - أنواع هذه التحولات المعممة مجردة للغاية ، وفي رأيي ، من الصعب جدًا التفكير فيها. لنستخدم:

 {-# LANGUAGE MultiParamTypeClasses #-} class GValidate io where gvalidate :: ip -> Maybe (op) 

يمكنك استخدام القواعد "اللينة والبطيئة" للاستدلال حول كيفية ظهور نوع الفصل الدراسي ، ولكن بشكل عام ، ستحتاج إلى كل من إدخال ومعلمة إخراج. يجب أن يكون كلاهما من جنس * -> * ، ثم ينقل هذه الصفحة الوجودية ، لأسباب مظلمة وغير مقدسة غير معروفة للبشرية. ثم ، باستخدام قائمة تحقق صغيرة ، نذهب للمساعدة في لف رؤوسنا حول هذا المشهد الجهنمي الرهيب ، والذي سنتناوله لاحقًا بالتسلسل.

على أي حال ، فصولنا في أيدينا بالفعل ، والآن نحتاج فقط إلى كتابة أمثلة من فصولنا لأنواع مختلفة من GHC . يمكننا البدء بالحالة الأساسية ، والتي يجب أن نكون قادرين على التحقق منها ، ربما ربما k :

 {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeOperators #-} instance GValidate (K1 a (Maybe k)) (K1 ak) where -- gvalidate :: K1 a (Maybe k) -> Maybe (K1 ak) gvalidate (K1 k) = K1 <$> k {-# INLINE gvalidate #-} 

K1 هو "نوع ثابت" ، مما يعني أن هذا هو المكان الذي ينتهي فيه العودية الهيكلية. في المثال مع شخصنا ، سيكون هذا هو pName :: HKD f String .

في معظم الحالات ، عندما يكون لديك حالة أساسية ، فإن الباقي عبارة عن أمثلة محددة ميكانيكيًا لأنواع أخرى. ما لم تكن بحاجة إلى الوصول إلى البيانات الوصفية حول نوع المصدر في أي مكان ، فستكون هذه المثيلات دائمًا تقريبًا تماثلًا تافهًا.

يمكننا البدء ببنى مضاعفة - إذا كان لدينا GValidate io و GValidate i 'o' ، يجب أن نتمكن من تشغيلهما بالتوازي:

 instance (GValidate io, GValidate i' o') => GValidate (i :*: i') (o :*: o') where gvalidate (l :*: r) = (:*:) <$> gvalidate l <*> gvalidate r {-# INLINE gvalidate #-} 

إذا كانت K1 تشير مباشرة إلى محددات الشخص ، (: * :) تتوافق تقريبًا مع بنية الفاصلة ، التي نفصل بين حقولنا في السجل.

يمكننا تحديد مثيل GValidate مشابه للمنتجات المشتركة أو بنيات الملخص (يتم فصل القيم المقابلة | في تعريف البيانات):

 instance (GValidate io, GValidate i' o') => GValidate (i :+: i') (o :+: o') where gvalidate (L1 l) = L1 <$> gvalidate l gvalidate (R1 r) = R1 <$> gvalidate r {-# INLINE gvalidate #-} 

أيضًا ، نظرًا لأننا لا نهتم بإيجاد البيانات الوصفية ، يمكننا ببساطة تحديد GValidate io عبر مُنشئ البيانات الوصفية:

 instance GValidate io => GValidate (M1 _a _b i) (M1 _a' _b' o) where gvalidate (M1 x) = M1 <$> gvalidate x {-# INLINE gvalidate #-} 

الآن نحن مع هياكل غير مثيرة للاهتمام للحصول على وصف كامل. سنزودهم بالمثيلات التافهة التالية للأنواع غير السكنية ( V1 ) وللمصممين بدون أي معلمات ( U1 ):

 instance GValidate V1 V1 where gvalidate = undefined {-# INLINE gvalidate #-} instance GValidate U1 U1 where gvalidate U1 = Just U1 {-# INLINE gvalidate #-} 

يعد استخدام undefined أمرًا آمنًا هنا ، حيث لا يمكن استدعاؤه إلا بقيمة V1 . لحسن الحظ بالنسبة لنا ، V1 غير مأهول وغير مهيأ ، لذلك لا يمكن أن يحدث هذا أبدًا ، مما يعني أننا على صواب من الناحية الأخلاقية في استخدامنا غير المحدد .

بدون مزيد من اللغط ، الآن بعد أن أصبح لدينا هذه الآلية بأكملها ، يمكننا أخيرًا كتابة نسخة غير عامة من التحقق:

 {-# LANGUAGE FlexibleContexts #-} validate :: ( Generic (f Maybe) , Generic (f Identity) , GValidate (Rep (f Maybe)) (Rep (f Identity)) ) => f Maybe -> Maybe (f Identity) validate = fmap to . gvalidate . from 

في كل مرة يمكنك الحصول على ابتسامة عريضة عندما يكون التوقيع على الوظيفة أطول من التنفيذ الفعلي ؛ هذا يعني أننا استأجرنا مترجمًا لكتابة التعليمات البرمجية لنا. المهم هنا للتحقق أنه لا يذكر الشخص . ستعمل هذه الوظيفة مع أي نوع محدد على أنه بيانات عالية الجودة. فويلا!

الملخص


هذا كل شيء اليوم يا شباب. تعرّفنا على فكرة البيانات عالية الجودة ، وشاهدنا كيف أنها معادلة تمامًا لنوع البيانات المحدد بطريقة أكثر تقليدية ، كما ألقينا نظرة سريعة على الأشياء الممكنة في هذا النهج.

يتيح لك القيام بجميع أنواع الأشياء المدهشة ، مثل: إنشاء عدسات لأنواع البيانات العشوائية دون اللجوء إلى Template Haskell ؛ تسلسل حسب نوع البيانات ؛ وتتبع التبعيات تلقائيًا لاستخدام حقول التسجيل.

تطبيق سعيد للولادة عالية!

الأصل: بيانات عالية النوع

Source: https://habr.com/ru/post/ar429104/


All Articles