2012-12-22 32 views
13

में पढ़ें उदाहरण बनाना मैं एक डेटा प्रकारहास्केल

data Time = Time {hour :: Int, 
        minute :: Int 
       } 

जिसके लिए मैं

instance Show Time where 
    show (Time hour minute) = (if hour > 10 
          then (show hour) 
          else ("0" ++ show hour)) 
          ++ ":" ++ 
          (if minute > 10 
          then (show minute) 
          else ("0" ++ show minute)) 

जो 07:09 के प्रारूप में कई बार बाहर प्रिंट होने के रूप में शो के उदाहरण परिभाषित किया है।

instance Read Time where 
    readsPrec _ input = 
    let hourPart = takeWhile (/= ':') 
     minutePart = tail . dropWhile (/= ':') 
    in (\str -> [(newTime 
        (read (hourPart str) :: Int) 
        (read (minutePart str) :: Int), "")]) input 
:

अब, Show और Read के बीच समरूपता नहीं होनी चाहिए, ताकि पढ़ने (लेकिन सही मायने में (मुझे लगता है कि) समझ) this और this, और documentation पढ़ने के बाद, मैं निम्नलिखित कोड के साथ आए हैं

यह काम करता है, लेकिन "" भाग यह गलत लगता है। तो मेरा प्रश्न समाप्त हो रहा है:

क्या कोई मुझे लागू करने का सही तरीका बता सकता है "07:09"newTime 7 9 में और/या मुझे दिखाएं?

उत्तर

16

मैं isDigit का उपयोग करूंगा और समय की अपनी परिभाषा रखूंगा।

import Data.Char (isDigit) 

data Time = Time {hour :: Int, 
        minute :: Int 
       } 

आप थे, लेकिन newTime को परिभाषित नहीं किया, इसलिए मैं एक अपने आप तो मेरे कोड को संकलित करता लिखा है!

newTime :: Int -> Int -> Time 
newTime h m | between 0 23 h && between 0 59 m = Time h m 
      | otherwise = error "newTime: hours must be in range 0-23 and minutes 0-59" 
    where between low high val = low <= val && val <= high 

सबसे पहले, अपने शो उदाहरण एक छोटे से गलत है क्योंकि show $ Time 10 10 देता "010:010"

instance Show Time where 
    show (Time hour minute) = (if hour > 9  -- oops 
          then (show hour) 
          else ("0" ++ show hour)) 
          ++ ":" ++ 
          (if minute > 9  -- oops 
          then (show minute) 
          else ("0" ++ show minute)) 

चलो readsPrec पर एक नजर है है:

*Main> :i readsPrec 
class Read a where 
    readsPrec :: Int -> ReadS a 
    ... 
    -- Defined in GHC.Read 
*Main> :i ReadS 
type ReadS a = String -> [(a, String)] 
    -- Defined in Text.ParserCombinators.ReadP 

एक पार्सर है कि - यह होना चाहिए केवलकी बजाय बेजोड़ शेष स्ट्रिंग को वापस करें, तो तुम सही हो "" गलत है:

*Main> read "03:22" :: Time 
03:22 
*Main> read "[23:34,23:12,03:22]" :: [Time] 
*** Exception: Prelude.read: no parse 

यह पार्स नहीं कर सकता है क्योंकि आप पहले पढ़ने में ,23:12,03:22] फेंक दिया।

आइए refactor कि थोड़ा इनपुट खाने के लिए के रूप में हम साथ जाने:

instance Read Time where 
    readsPrec _ input = 
    let (hours,rest1) = span isDigit input 
     hour = read hours :: Int 
     (c:rest2) = rest1 
     (mins,rest3) = splitAt 2 rest2 
     minute = read mins :: Int 
     in 
     if c==':' && all isDigit mins && length mins == 2 then -- it looks valid 
     [(newTime hour minute,rest3)] 
     else []      -- don't give any parse if it was invalid 

उदाहरण

Main> read "[23:34,23:12,03:22]" :: [Time] 
[23:34,23:12,03:22] 
*Main> read "34:76" :: Time 
*** Exception: Prelude.read: no parse 

के लिए देता है यह, हालांकि, "03:45" अनुमति नहीं है और के रूप में यह व्याख्या "03:45"। मुझे यकीन नहीं है कि यह एक अच्छा विचार है, इसलिए शायद हम एक और परीक्षण length hours == 2 जोड़ सकते हैं।


मैं यह सब विभाजन और अवधि सामान बंद जा रहा हूँ अगर हम इसे इस तरह से कर रहे हैं, तो शायद मैं पसंद करेंगे:

instance Read Time where 
    readsPrec _ (h1:h2:':':m1:m2:therest) = 
    let hour = read [h1,h2] :: Int -- lazily doesn't get evaluated unless valid 
     minute = read [m1,m2] :: Int 
     in 
     if all isDigit [h1,h2,m1,m2] then -- it looks valid 
     [(newTime hour minute,therest)] 
     else []      -- don't give any parse if it was invalid 
    readsPrec _ _ = []    -- don't give any parse if it was invalid 

वास्तव में क्लीनर और मेरे लिए आसान लगता है कौन सा।

इस समय यह "3:45" अनुमति नहीं देता:

*Main> read "3:40" :: Time 
*** Exception: Prelude.read: no parse 
*Main> read "03:40" :: Time 
03:40 
*Main> read "[03:40,02:10]" :: [Time] 
[03:40,02:10] 
+1

शो में एक-एक-एक त्रुटि को इंगित करने के लिए धन्यवाद। यह नया कोड अधिक समझ में आता है, और यदि वास्तव में (जिसे मैं उल्लेख करना भूल गया) के लिए अधिक सही होता, तो "स्मार्ट कन्स्ट्रक्टर" 'newTime' पहले से ही वैधता जांच करता है। – Magnap

+0

@Magnap मैंने अनावश्यक चेक संपादित किए हैं और नए समय का उपयोग किया है। – AndrewC

+0

@Magnap मुझे अभी भी यह जांचना होगा कि 'Int' पर 'read' को कॉल करने से पहले मुझे अंक मिल गए हैं। – AndrewC

4

यदि readsPrec पर इनपुट एक स्ट्रिंग है जिसमें Time के वैध प्रतिनिधित्व के बाद कुछ अन्य वर्ण शामिल हैं, तो उन अन्य पात्रों को टुपल के दूसरे तत्व के रूप में वापस किया जाना चाहिए।

तो स्ट्रिंग 12:34 bla के लिए, परिणाम [(newTime 12 34, " bla")] होना चाहिए। आपके कार्यान्वयन से उस इनपुट के लिए एक त्रुटि होगी। इसका मतलब है कि read "[12:34]" :: [Time] की तरह कुछ विफल हो जाएगा क्योंकि यह (तर्क के रूप में कहेंगे Time '"12:34]" साथ रों readsPrec क्योंकि readList[ का उपभोग होता है, तो शेष तार के साथ readsPrec फोन, और उसके बाद जाँच लें कि शेष स्ट्रिंग readsPrec द्वारा दिया या तो ] है या अधिक तत्वों के बाद एक अल्पविराम)।

को ठीक अपने readsPrec आप afterColon की तरह कुछ करने के लिए minutePart नाम बदलने चाहिए और फिर विभाजित करने के लिए है कि वास्तविक मिनट भाग में और जो कुछ मिनट भाग के बाद आता है (उदाहरण के लिए takeWhile isDigit के साथ)। फिर मिनट के बाद आने वाली चीजें ट्यूपल के दूसरे तत्व के रूप में लौटा दी जानी चाहिए।

+0

धन्यवाद एक बहुत। चलो (पठनीयता के लिए ब्रैकेट) को बदलने के बाद '{hourPart = takeWhile (/ = ':') इनपुट; के बाद कोलोन = पूंछ। ड्रॉपवॉल्ड (/ = ':') $ इनपुट; मिनटपार्ट = ले लो जबकि समय के बाद संपादित करें कॉलन; बाकी = ड्रॉप जबकि समय के बाद छोड़ो कॉलन;} 'यह अद्भुत काम करता है। – Magnap