Sunday, September 26, 2010

View patterns for validation

GHC's ViewPatterns extension has a lot of unexpected uses. One that I've found recently is input validation.

{-# LANGUAGE ViewPatterns #-}

import Control.Monad
import Text.Printf

months :: [String]
months = words "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"

Using this function:

range :: (Ord a) => a -> a -> a -> a
range lb ub x
| (x < lb) || (x > ub) = error "argument out of range"
| otherwise = x

we can put bounds on arguments:

month :: Int -> String
month (range 1 12 -> m) = months !! (m-1)

like so:

GHCi> month 3
"Mar"
GHCi> month 15
"*** Exception: argument out of range

This version provides better error messages:

-- V for 'verbose'
rangeV :: (Ord a, Show a) => String -> a -> a -> a -> a
rangeV fun lb ub x
| (x < lb) || (x > ub) = error (printf msg fun (show x) (show lb) (show ub))
| otherwise = x
where msg = "%s: argument %s is outside of range [%s,%s]"

like so:

monthV :: Int -> String
monthV (rangeV "monthV" 1 12 -> m) = months !! (m-1)

GHCi> monthV 3 "Mar" GHCi> monthV 15 "*** Exception: monthV: argument 15 is outside of range [1,12]

Or handle failure monadically:

-- Mp for MonadPlus
rangeMp :: (MonadPlus m, Ord a) => a -> a -> a -> m a
rangeMp lb ub x = guard ((x >= lb) && (x <= ub)) >> return x

like so:

monthMaybe :: Int -> Maybe String
monthMaybe (rangeMp 1 12 -> Just m) = Just (months !! (m-1))
monthMaybe _ = Nothing

GHCi> monthMaybe 3 Just "Mar" GHCi> monthMaybe 15 Nothing

7 comments:

  1. Yeah, that's nice. Personally I prefer the latter and would prefer it with Either so that you get actual information about why it failed.

    > {-# LANGUAGE ViewPatterns #-}

    Supposing we use the Control.Applicative.Error class.

    > import Control.Applicative.Error

    And we define a function that ensures X is true:

    > ensure :: String -> (a -> Bool) -> a -> Failing a
    > ensure err p a
    > | p a = Success a
    > | otherwise = Failure [err]

    We can then define inRange in terms of it for Failing:

    > inRange :: (Show a,Ord a) => a -> a -> a -> Failing a
    > inRange lb ub x = ensure err (\a -> a>=lb && a<=ub) x where
    > err = "Must be in range >=" ++ show lb ++ " and <= " ++ show ub

    And our `month' and `day' functions are defined like this:

    > months :: [String]
    > months = words "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"

    > month :: Int -> Failing String
    > month (inRange 1 12 -> Success m) = Success $ months !! (m-1)
    > month _ = Failure ["Month must be in range 1-12."]

    > days :: [String]
    > days = words "Monday Tuesday Wednesday Thursday Friday Saturday Sunday"

    > day :: Int -> Failing String
    > day (inRange 1 7 -> Success d) = Success $ days !! (d-1)
    > day _ = Failure ["Day must be in range 1-7."]

    The nice thing is we get this for free:

    λ> (,) <$> month 1 <*> day 3
    Success ("Jan","Wednesday")

    λ> (,) <$> month 1 <*> day 12
    Failure ["Day must be in range 1-7."]

    λ> (,) <$> month 24 <*> day 12
    Failure ["Month must be in range 1-12.","Day must be in range 1-7."]

    It kind of sucks to be building strings all the time, it'd be
    nicer to have proper types to represent these invariants, e.g.

    > data Prop a = OutOfRange a | Empty a | WrongSize a | InvalidFormat a | Ok a

    Or something like that. I suppose throwing exceptions is faster
    than creating intermediate data structures.

    ReplyDelete
  2. Nice idea! But I would like to see listed in your post the alternative without view patterns:

    month :: Int -> String
    month i | inRange 1 12 i = months !! (i-1)
    | otherwise = error "..."

    inRange :: Ord a => a -> a -> a -> Bool
    inRange lb ub x = x >= lb && x <= ub

    (the blog will probably clobber indentation)

    ReplyDelete
  3. Thanks for the suggestions Chris, and for mentioning Control.Applicative.Error. It definitely includes a few things I reimplemented in a recent project; I'll want to switch over.

    ReplyDelete
  4. This is one of the things I love about (GHC) Haskell: you can often simply create a clever new "pseudo-syntax" out of the blue!!

    This "range checking pattern" would be rigid syntax in most other languages. Here it's just something that emerges from the programming paradigm.

    ReplyDelete
  5. شركة عزل اسطح بالرياض تقدم افضل خدمات عزل الاسطح في كافة انحاء الرياض وذلك لحماية كافة المباني والمنشآت من التآكل سواء من خلال ارتفاع درجات الحرارة او تسريب المياه ، كما تقدم شركتنا افضل خدمات عزل الاسطح بأعلى جودة ممكنة ، وذلك لأن شركة عزل اسطح بالرياض تعتمد علي فريق من اكفأ العمال الماهرين وذوي خبرة سنوات طويلة في مجال عزل الاسطح .
    العزل الحراري مع شركة عزل اسطح بالرياض :-
    ولأن عملية العزل من احد العمليات الضرورية ولاسيما في عصرنا هذا نظراً لارتفاع درجات الحرارة كل عام عن سابقه بدرجات كبيرة ، فإنه ولا بد من عزل الاسطح ، ومن اهم اشكال العزل هو العزل الحراري ، وهو ما يفيد في الحفاظ علي درجة الحرارة الداخلية للمبني او المنشأة ثابتة بقدر المستطاع ، الامر الذي يحد من استخدام المكيفات بكثرة في فصل الصيف ، او المدافئ الكهربائية في فصل الشتاء ، وبالتالي يتم ترشيد الاستهلاك في فاتورة الكهرباء الي درجة مرضية ،كما يتم حماية افراد الاسرة من التعرض الي الامراض الناجمة من استخدام المكيفات الكهربائية .
    لذلك تقدم شركة عزل الاسطح بالرياض بتقديم خدمات العزل الحراري مستخدمة في ذلك افضل ما توصلت اليه التكنولوجية من معدات حديثة ومتطورة ، الامر الذي جعل شركة عزل اسطح بالرياض مصدر ثقة جميع العملاء القدامى والجدد .
    العزل المائي مع شركة عزل اسطح بالرياض :-
    كما يسر شركة عزل اسطح بالرياض ان تقدم خدمات العزل المائي للأسطح ، وذلك لحمايتها من تآكل مياه الامطار لها ، وايضا حمايتها من تسريب المياه ، الامر الذي قد يهدد بسلامة العقار ومن بداخله ، فيتم حماية الاسقف من خلال شركة عزل اسطح في بالرياض بواسطة العزل المائي ، من التآكل والتلف من قبل عوامل البيئة الخارجية كالرطوبة ومياه الامطار ، فتقدم شركة عزل اسطح بالرياض من خلال فريق متميز من الخبراء والفنيين اسقف ذات حماية عالية من الهشاشة والتآكل، وذلك بأفضل الاسعار بالرياض.
    تواصل معنا
    0500358741
    او زورو موقعنا
    http://elwosta.com/

    ReplyDelete


  6. شركة مكافحة حشرات بالدمام 0590352700 ديكوريند
    شركة مكافحة حشرات بالدمام

    مكافحة الحشرات من الأشياء الواجب فعلها علي اتم وجه حتي نتجنب الامراض و الأوبه التي تسببها الحشرات .

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

    و مما لا شك فيه أن أفضل شركة بالدمام هي شركة ديكوريند لمكافحة الحشرات .

    لما تقدمه الشركه من خدمات و طرق فعاله للقضاء علي الحشرات بجميع أنواعها .

    الشركه تعتمد علي العماله المدربه جيدا ، و المبيدات و الأجهزه الفعاله في القضاء علي مثل هذه الآفات الضاره.
    شركة مكافحة حشرات بالدمام
    شركة مكافحة حشرات بالدمام
    أنواع الحشرات :

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

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

    و تتنوع انواع الحشرات كالآتي :

    ذباب
    بعوض
    براغيث
    نمل
    صراصير
    و غيرها الكثير

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

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

    مما لاشك فيه أن أسعار شركة ديكوريند لا تقبل المنافسه اطلاقا ، لما تقدمه الشركه من خدمات جيده جدا في مقابل الأسعار التي تكون في متناول الجميع .

    و تسعي الشركه دائما إلي تقديم أرقي و أفضل الخدمات ، التي ترضي جميع عملائنا الكرام ، فلا تتردد في الاتصال بنا .

    اتصل الان : 0556043168 – 0590352700 شركة ديكوريند لمكافحة الحشرات بالدمام و جميع مناطق المملكة العربيه السعودية

    من خدماتنا :

    تنظيف فلل بالرياض

    تنظيف موكيت بالرياض

    تنظيف منازل بالرياض

    تنظيف شقق بالرياض

    تنظيف خزانات بالرياض

    تنظيف مجالس بالرياض

    ReplyDelete