أسلوب ترميز Haskell الجيد لكتلة التحكم if/else؟

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

  •  02-07-2019
  •  | 
  •  

سؤال

أنا أتعلم هاسكل على أمل أن يساعدني ذلك في الاقتراب من البرمجة الوظيفية.في السابق، كنت أستخدم غالبًا لغات ذات بناء جملة شبيه بلغة C، مثل C وJava وD.

لدي سؤال صغير حول أسلوب الترميز لـ if/else كتلة التحكم المستخدمة من قبل البرنامج التعليمي على ويكي الكتب.يبدو الرمز كما يلي:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   if (read guess) < num
     then do putStrLn "Too low!"
             doGuessing num
     else if (read guess) > num
            then do putStrLn "Too high!"
                    doGuessing num
            else do putStrLn "You Win!"

هذا يجعلني في حيرة من أمري، لأن أسلوب الترميز هذا ينتهك تمامًا النمط الموصى به في اللغات المشابهة للغة C، حيث يجب أن نضع مسافة بادئة if, else if, ، و else في نفس العمود.

أعلم أنه لا يعمل في هاسكل، لأنه سيكون خطأ تحليلي إذا قمت بوضع مسافة بادئة else في نفس العمود كما if.

ولكن ماذا عن النمط التالي؟أعتقد أن الأمر أوضح بكثير من المذكور أعلاه.ولكن بما أن ما ورد أعلاه يتم استخدامه بواسطة Wikibooks وYet Another Haskell Tutorial، والذي تم وضع علامة "أفضل برنامج تعليمي متاح عبر الإنترنت" على موقع Haskell الرسمي، فأنا لست متأكدًا مما إذا كان أسلوب الترميز هذا هو العرف في برامج Haskell.

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    if (read guess) < num then
        do 
            putStrLn "Too low!"
            doGuessing num
        else if (read guess) > num then do 
            putStrLn "Too high!"
            doGuessing num
        else do 
            putStrLn "You Win!"

لذا، أشعر بالفضول لمعرفة نمط الترميز الذي يتم استخدامه في كثير من الأحيان، أم أن هناك نمط ترميز آخر لهذا الجزء من التعليمات البرمجية؟

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

المحلول

أسلوب هاسكل عملي وليس ضروريًا!بدلاً من "افعل هذا ثم ذاك"، فكر في الجمع بين الوظائف والوصف ماذا برنامجك سوف يفعل، وليس كيف.

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

main = untilM (isCorrect 42) (read `liftM` getLine)

يستخدم هذا أداة الدمج التي تقوم بتشغيل الإجراء بشكل متكرر (getLine تسحب خط الإدخال و read يحول تلك السلسلة إلى عدد صحيح في هذه الحالة) ويتحقق من نتيجتها:

untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
  x <- a
  done <- p x
  if done
    then return ()
    else untilM p a

المسند (مطبق جزئيا في main) يتحقق من التخمين مقابل القيمة الصحيحة ويستجيب وفقًا لذلك:

isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
  case compare num guess of
    EQ -> putStrLn "You Win!"  >> return True
    LT -> putStrLn "Too high!" >> return False
    GT -> putStrLn "Too low!"  >> return False

الإجراء الذي سيتم تنفيذه حتى يخمن اللاعب بشكل صحيح هو

read `liftM` getLine

لماذا لا تبقي الأمر بسيطًا وتؤلف الوظيفتين فقط؟

*Main> :type read . getLine

<interactive>:1:7:
    Couldn't match expected type `a -> String'
           against inferred type `IO String'
    In the second argument of `(.)', namely `getLine'
    In the expression: read . getLine

نوع من getLine يكون IO String, ، لكن read يريد نقيا String.

الوظيفة liftM من Control.Monad يأخذ وظيفة خالصة و"يرفعها" إلى موناد.يخبرنا نوع التعبير كثيرًا عما يفعله:

*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a

إنه إجراء إدخال/إخراج يعيد لنا قيمة تم تحويلها بها عند التشغيل read, ، ان Int في حالتنا هذه.أذكر ذلك readLine هو إجراء الإدخال/الإخراج الذي ينتج String القيم، حتى تتمكن من التفكير liftM كما يسمح لنا بتقديم الطلب read "داخل" IO مناد.

لعبة عينة:

1
Too low!
100
Too high!
42
You Win!

نصائح أخرى

يمكنك استخدام "الحالة" - البناء:

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    case (read guess) of
        g | g < num -> do 
            putStrLn "Too low!"
            doGuessing num
        g | g > num -> do 
            putStrLn "Too high!"
            doGuessing num
        otherwise -> do 
            putStrLn "You Win!"

هناك تحسين بسيط في بيان حالة ماتياست (سأقوم بالتحرير، لكني أفتقر إلى الكارما) وهو استخدام وظيفة المقارنة، التي تُرجع إحدى القيم الثلاث، LT، أو GT، أو EQ:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   case (read guess) `compare` num of
     LT -> do putStrLn "Too low!"
              doGuessing num
     GT -> do putStrLn "Too high!"
              doGuessing num
     EQ -> putStrLn "You Win!"

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

سؤال إضافي لصحيفة هاسكل:ما هو نوع التخمين؟

الطريقة التي يفسر بها هاسكل if ... then ... else داخل أ do block يتماشى إلى حد كبير مع بناء جملة هاسكل بالكامل.

لكن الكثير من الناس يفضلون بناء جملة مختلفًا قليلاً، إذا سمحوا بذلك then و else لتظهر على نفس مستوى المسافة البادئة المقابلة if.لذلك، تأتي GHC مع ملحق لغة اختياري يسمى DoAndIfThenElse, ، الذي يسمح ببناء الجملة هذا.

ال DoAndIfThenElse تم تحويل الامتداد إلى جزء من اللغة الأساسية في المراجعة الأخيرة لمواصفات هاسكل، هاسكل 2010.

لاحظ أن حقيقة أنه يتعين عليك وضع مسافة بادئة لـ "ثم" و"آخر" داخل كتلة "do" تعتبر خطأً من قبل الكثيرين.من المحتمل أن يتم إصلاحه في Haskell' (Haskell Prime)، الإصدار التالي من مواصفات Haskell.

يمكنك أيضًا استخدام التجميع الواضح مع الأقواس المتعرجة.راجع قسم التخطيط http://www.haskell.org/tutorial/patterns.html

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

أستخدم أسلوب ترميز مثل مثالك من Wikibooks.من المؤكد أنها لا تتبع إرشادات لغة C، لكن لغة Haskell ليست لغة C، وهي قابلة للقراءة إلى حد ما، خاصة بمجرد أن تعتاد عليها.كما أنها منقوشة وفقًا لأسلوب الخوارزميات المستخدمة في العديد من الكتب المدرسية، مثل كورمين.

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

النمط الذي تعرضه أبسط بكثير وأقل تطلبًا من المحرر، وأعتقد أنه يجب عليك الالتزام به.التناقض الوحيد الذي يمكنني رؤيته هو أنك تضع الإجراء الأول في السطر الخاص به بينما تضع المهام الأخرى بعدthen/else.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top