هل هناك مكافئ هاسكل لفئات OOP المجردة ، باستخدام أنواع البيانات الجبرية أو تعدد الأشكال؟

StackOverflow https://stackoverflow.com/questions/4029455

سؤال

في Haskell ، هل من الممكن كتابة وظيفة بتوقيع يمكن أن يقبل نوعين مختلفين (على الرغم من مشابه) ، ويعمل بشكل مختلف اعتمادًا على النوع الذي يتم تمريره؟

مثال قد يجعل سؤالي أكثر وضوحًا. إذا كان لدي وظيفة اسمها myFunction, ، ونوعين يدعى MyTypeA و MyTypeB, ، هل يمكنني تحديد myFunction بحيث لا يمكن أن تقبل سوى البيانات من النوع MyTypeA أو MyTypeB كمعلمة لها الأولى؟

type MyTypeA = (Int, Int, Char, Char)
type MyTypeB = ([Int], [Char])

myFunction :: MyTypeA_or_MyTypeB -> Char
myFunction constrainedToTypeA = something
myFunction constrainedToTypeB = somethingElse

في لغة OOP ، يمكنك كتابة ما أحاول تحقيقه مثل:

public abstract class ConstrainedType {
}

public class MyTypeA extends ConstrainedType {
    ...various members...
}

public class MyTypeB extends ConstrainedType {
    ...various members...
}

...

public Char myFunction(ConstrainedType a) {
    if (a TypeOf MyTypeA) {
        return doStuffA();
    }
    else if (a TypeOf MyTypeB) {
        return doStuffB();
    }
}

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

هل كانت مفيدة؟

المحلول

نعم ، أنت محق ، فأنت تبحث عن أنواع البيانات الجبرية. هناك برنامج تعليمي رائع عليهم في تعلم لك هاسكل.

بالنسبة للسجل ، فإن مفهوم فئة مجردة من OOP لديه بالفعل ثلاث ترجمات مختلفة إلى Haskell ، و ADTS هي مجرد واحدة. فيما يلي نظرة عامة سريعة على التقنيات.

أنواع البيانات الجبرية

تقوم أنواع البيانات الجبرية بتشفير نمط فئة مجردة تعرف فئاتها الفرعية ، وحيث تحقق الوظائف من مثال معين هو الكائن هو عضو من خلال الصب.

abstract class IntBox { }

class Empty : IntBox { }

class Full : IntBox {
    int inside;
    Full(int inside) { this.inside = inside; }
}

int Get(IntBox a) {
    if (a is Empty) { return 0; }
    if (a is Full)  { return ((Full)a).inside; }
    error("IntBox not of expected type");
}

ترجم إلى:

data IntBox = Empty | Full Int

get :: IntBox -> Int
get Empty = 0
get (Full x) = x

سجل الوظائف

هذا النمط لا يسمح بالضرب ، لذا فإن Get الوظيفة أعلاه لن تكون قابلة للتعبير في هذا النمط. إذن هذا شيء مختلف تمامًا.

abstract class Animal { 
    abstract string CatchPhrase();
    virtual void Speak() { print(CatchPhrase()); }
}

class Cat : Animal {
    override string CatchPhrase() { return "Meow"; }
}

class Dog : Animal {
    override string CatchPhrase() { return "Woof"; }
    override void Speak() { print("Rowwrlrw"); }
}

ترجمتها في Haskell لا تعين أنواع الأنواع. Animal هو النوع الوحيد ، و Dog و Cat يتم سحقها في وظائف مُنشئها:

data Animal = Animal {
    catchPhrase :: String,
    speak       :: IO ()
}

protoAnimal :: Animal
protoAnimal = Animal {
    speak = putStrLn (catchPhrase protoAnimal)
}

cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }

dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }

هناك بعض التباديل المختلفة لهذا المفهوم الأساسي. الثابت هو أن النوع التجريدي هو نوع سجل حيث تكون الأساليب هي حقول السجل.

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

typeclasses

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

سأقوم بتشفير مثال الحيوان مرة أخرى:

class Animal a where
    catchPhrase :: a -> String
    speak       :: a -> IO ()

    speak a = putStrLn (catchPhrase a)

data Cat = Cat 
instance Animal Cat where
    catchPhrase Cat = "Meow"

data Dog = Dog
instance Animal Dog where
    catchPhrase Dog = "Woof"
    speak Dog = putStrLn "Rowwrlrw"

هذا يبدو لطيفا ، أليس كذلك؟ تأتي الصعوبة عندما تدرك أنه على الرغم من أن الأمر يبدو وكأنه لا يعمل حقًا مثل OO. قد ترغب في الحصول على قائمة بالحيوانات ، ولكن أفضل ما يمكنك فعله الآن هو Animal a => [a], ، قائمة بالحيوانات المتجانسة ، على سبيل المثال. قائمة القطط فقط أو الكلاب فقط. ثم تحتاج إلى جعل هذا النوع الغلاف:

{-# LANGUAGE ExistentialQuantification #-}

data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
    catchPhrase (AnyAnimal a) = catchPhrase a
    speak (AnyAnimal a) = speak a

وثم [AnyAnimal] هو ما تريده لقائمة الحيوانات الخاصة بك. ومع ذلك ، اتضح ذلك AnyAnimal يعرض بالضبط نفس المعلومات عن نفسها مثل Animal سجل في المثال الثاني ، لقد ذهبنا للتو بطريقة دورية. وبالتالي ، لماذا لا أعتبر أن typeclasses يكون ترميزًا جيدًا جدًا لـ OO.

وبالتالي يختتم إصدار هذا الأسبوع من طريقة الكثير من المعلومات!

نصائح أخرى

يبدو أنك قد ترغب في القراءة typeclasses.

النظر في هذا المثال باستخدام typeclasses.

نحدد C ++-مثل "فئة مجردة" MVC بناءً على ثلاثة أنواع (ملاحظة MultiParamTypeClasses): tState tAction tReaction من أجل تحديد وظيفة رئيسية tState -> tAction -> (tState, tReaction) (عندما يتم تطبيق إجراء على الدولة ، تحصل على حالة جديدة ورد فعل.

يحتوي typeclass على ثلاث وظائف "C ++ Abstract" ، وبعضها أكثر تعريفًا على "التجريدية". سيتم تعريف وظائف "التجريد" متى و instance MVC وهناك حاجة.

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, NoMonomorphismRestriction #-}


-- -------------------------------------------------------------------------------

class MVC tState tAction tReaction | tState -> tAction tReaction where
      changeState :: tState -> tAction -> tState       -- get a new state given the current state and an action ("abstract")
      whatReaction :: tState -> tReaction              -- get the reaction given a new state ("abstract")
      view :: (tState, tReaction) -> IO ()             -- show a state and reaction pair ("abstract")

      -- get a new state and a reaction given an state and an action (defined using previous functions)
      runModel :: tState -> tAction -> (tState, tReaction) 
      runModel s a = let
                                ns = (changeState s a) 
                                r = (whatReaction ns) 
                  in (ns, r)

      -- get a new state given the current state and an action, calling 'view' in the middle (defined using previous functions)
      run :: tState -> tAction -> IO tState
      run s a = do
                        let (s', r) = runModel s a
                        view (s', r)
                        return s'

      -- get a new state given the current state and a function 'getAction' that provides actions from "the user" (defined using previous functions)
      control :: tState -> IO (Maybe tAction) -> IO tState
      control s getAction = do
              ma <- getAction
              case ma of
                   Nothing -> return s
                   Just a -> do
                              ns <- run s a
                              control ns getAction


-- -------------------------------------------------------------------------------

-- concrete instance for MVC, where
-- tState=Int tAction=Char ('u' 'd') tReaction=Char ('z' 'p' 'n')
-- Define here the "abstract" functions
instance MVC Int Char Char where
         changeState i c 
                     | c == 'u' = i+1 -- up: add 1 to state
                     | c == 'd' = i-1 -- down: add -1 to state
                     | otherwise = i -- no change in state

         whatReaction i
                      | i == 0 = 'z' -- reaction is zero if state is 0
                      | i < 0 = 'n' -- reaction is negative if state < 0                     
                      | otherwise = 'p' -- reaction is positive if state > 0

         view (s, r) = do
                  putStrLn $ "view: state=" ++ (show s) ++ " reaction=" ++ (show r) ++ "\n"

--

-- define here the function "asking the user"
getAChar :: IO (Maybe Char) -- return (Just a char) or Nothing when 'x' (exit) is typed
getAChar = do
         putStrLn "?"
         str <- getLine
         putStrLn ""
         let c = str !! 0
         case c of
              'x' -> return Nothing
              _ -> return (Just c)


-- --------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------

-- call 'control' giving the initial state and the "input from the user" function 
finalState = control 0 getAChar :: IO Int

-- 

main = do
     s <- finalState
     print s
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top