2011-12-22 8 views
15

पर विचार करें:टाइपक्लास उदाहरणों के वर्गबद्ध विस्फोट से कैसे बचें?

{-# OPTIONS -fglasgow-exts #-} 

data Second = Second 
data Minute = Minute 
data Hour = Hour 

-- Look Ma', a phantom type! 
data Time a = Time Int 

instance Show (Time Second) where 
    show (Time t) = show t ++ "sec" 

instance Show (Time Minute) where 
    show (Time t) = show t ++ "min" 

instance Show (Time Hour) where 
    show (Time t) = show t ++ "hrs" 

sec :: Int -> Time Second 
sec t = Time t 

minute :: Int -> Time Minute 
minute t = Time t 

hour :: Int -> Time Hour 
hour t = Time t 

class TimeAdder a b c | a b -> c where 
    add :: Time a -> Time b -> Time c 

instance TimeAdder Second Second Second where 
    add (Time s1) (Time s2) = sec (s1 + s2) 

instance TimeAdder Second Minute Second where 
    add (Time s) (Time m) = sec (s + 60*m) 

instance TimeAdder Second Hour Second where 
    add (Time s) (Time h) = sec (s + 3600*h) 

instance TimeAdder Minute Second Second where 
    add (Time m) (Time s) = sec (60*m + s) 

instance TimeAdder Minute Minute Minute where 
    add (Time m1) (Time m2) = minute (m1 + m2) 

instance TimeAdder Minute Hour Minute where 
    add (Time m) (Time h) = minute (m + 60*h) 

instance TimeAdder Hour Second Second where 
    add (Time h) (Time s) = sec (3600*h + s) 

instance TimeAdder Hour Minute Minute where 
    add (Time h) (Time m) = minute (60*h + m) 

instance TimeAdder Hour Hour Hour where 
    add (Time h1) (Time h2) = hour (h1 + h2) 

add (minute 5) (hour 2) 
--125min 

हालांकि मैं काफी रोमांचित है कि इस तरह पागल सामान काम करता हूँ, मुझे आश्चर्य है कि कैसे TimeAdder उदाहरणों में से द्विघात विस्फोट से बचा जा सकता।

+1

घंटे, मिनट और सेकंड वास्तव में उस प्रकार सुरक्षा के इस प्रकार के लिए एक उम्मीदवार के अच्छा नहीं कर रहे हैं, के बाद से क्यों आप कभी भी है कि जैसे एक समारोह के लिए होता है केवल सेकंड में समय स्वीकार करता है? इस प्रकार की प्रकार-सुरक्षा के लिए एक बेहतर अभ्यास उदाहरण के लिए, भौतिक इकाइयां हो सकती हैं। आप 'टाइम',' मास ',' लम्बाई 'आदि को प्रेत प्रकार के रूप में कह सकते हैं और गति, ऊर्जा इत्यादि के लिए टाइप-सुरक्षित गणनाएं कर सकते हैं। इससे उदाहरणों की संख्या में भी मदद मिलेगी क्योंकि सभी प्रकारों में अंतर-परिवर्तनीय नहीं है आपका समय उदाहरण – shang

+0

@shang: यह सही है। मुझे यह उल्लेख करना चाहिए था कि टाइप क्लास और प्रेत प्रकारों पर बेहतर संभाल पाने के लिए यह सिर्फ एक खिलौना उदाहरण है। मैं समझता हूं कि वास्तविक इकाइयों के लिए समय इकाइयों के साथ केवल हथौड़ा का पहला जवाब अधिक व्यावहारिक होगा। – Landei

उत्तर

9

आप कुछ इस तरह कर सकता है, लेकिन यह आप कार्यात्मक निर्भरता नहीं देता है।

class TimeUnit a where 
    toSeconds :: a -> Int 
    fromSeconds :: Int -> a 

instance TimeUnit (Time Second) where toSeconds = id; fromSeconds = id 
instance TimeUnit (Time Minute) where toSeconds = (* 60); fromSeconds = (`quot` 60) 

class TimeAdd a b c where 
    add :: a -> b -> c 

instance (TimeUnit a, TimeUnit b, TimeUnit c) => TimeAdd a b c where 
    add a b = fromSeconds (toSeconds a + toSeconds b) 
13

जब तक आप एक अच्छे कारण, मैं सिर्फ प्रकार कक्षाएं छोड़ सकते हैं और प्रयोग करेंगे करने के लिए एक सादे पुराने एडीटी है:

data Time = Hour Int | Minute Int | Second Int 

instance Show Time where 
    show (Hour x) = show x ++ "hrs" 
    show (Minute x) = show x ++ "min" 
    show (Second x) = show x ++ "sec" 

add x y = fromSeconds (toSeconds x + toSeconds y) 

toSeconds (Hour x) = 3600 * x 
toSeconds (Minute x) = 60 * x 
toSeconds (Second x) = x 

fromSeconds x | mod x 3600 == 0 = Hour (div x 3600) 
       | mod x 60 == 0 = Minute (div x 60) 
       | otherwise = Second x 

यह कुछ सरलीकरण करने में सक्षम होने का लाभ है कि प्रकार वर्ग दृष्टिकोण कर सकते हैं 'उदाहरण के लिए टी,:

> add (Second 18) (Second 42) 
1min 
+0

अच्छा बिंदु! असली दुनिया अनुप्रयोगों के लिए मैं निश्चित रूप से ऐसा करूँगा। लेकिन मैंने विशेष रूप से मेरे उदाहरण में प्रेत प्रकारों की बेहतर समझ प्राप्त करने की कोशिश की। – Landei

+5

आप कक्षाओं को टाइप करने के लिए हथौड़ा के कोड को वापस परिवर्तित कर सकते हैं और इसमें वर्गिक विस्फोट नहीं होगा, क्योंकि यह सेकेंड का उपयोग मध्यवर्ती इकाई के रूप में करता है। –

+1

@SjoerdVisscher क्या आप उस पर विस्तृत जानकारी दे सकते हैं? मैं नहीं देखता कि आपको रिटर्न प्रकार में सही प्रेत प्रकार कैसे मिलेगा। – dave4420

0

उदाहरण सभी बॉयलरप्लेट हैं। मैं कहूंगा कि यह टेम्पलेट हास्केल के लिए एक मामला है (हालांकि मैं किसी ऐसे व्यक्ति को ऐसा करने का स्पष्टीकरण छोड़ दूंगा जिसने इसे क्रोध में इस्तेमाल किया है)।

1

हथौड़ा के सुझाव को 1 कदम आगे लेना, मैं इस विशेष उदाहरण के लिए कहूंगा, बस प्रकार की सामग्री को पूरी तरह खत्म कर दूंगा, और इसके बजाय स्मार्ट कन्स्ट्रक्टर का उपयोग करूंगा।

newtype Time = Sec Int 

instance Show Time where 
    show (Sec n) = h ++ " hrs " ++ m ++ " min " ++ s ++ " sec" 
    where h = ... 
      m = ... 
      s = ... 

sec :: Int -> Time 
sec = Sec 

min :: Int -> Time 
min = sec . (*60) 

hr :: Int -> Time 
hr = min . (*60) 

add (Sec n) (Sec m) = Sec (n+m) 

बेशक, यह कोई मजेदार नहीं है, क्योंकि इसमें कोई प्रेत प्रकार नहीं है। मजेदार व्यायाम: hr, min, sec के लिए लेंस बनाएं।

6

जिस तरह से मैं इसे स्तर स्तर पर करूँगा वह है कि प्राकृतिक प्राकृतिक संख्या टाइप करने के लिए प्रेत प्रकारों को मैप करना और सही वापसी प्रकार खोजने के लिए "न्यूनतम" ऑपरेशन का उपयोग करना है और फिर इंस्टेंस रिज़ॉल्यूशन को वहां से नौकरी करना है।

मैं यहां प्रकार के परिवारों का उपयोग करूँगा, लेकिन यदि आप उन्हें पसंद करते हैं तो यह संभवतः कार्यात्मक निर्भरताओं के साथ किया जा सकता है।

{-# LANGUAGE TypeFamilies, EmptyDataDecls, FlexibleInstances #-} 

सबसे पहले, हमें कुछ प्रकार के स्तर के प्राकृतिक और न्यूनतम ऑपरेशन की आवश्यकता होगी।

data Zero 
data Succ n 

type family Min a b 
type instance Min Zero a = Zero 
type instance Min a Zero = Zero 
type instance Min (Succ a) (Succ b) = Succ (Min a b) 

इसके बाद, हम अपने प्रेत प्रकार को परिभाषित करने के लिए और और हमारे प्रकार स्तर प्राकृतिक की मैपिंग उपलब्ध कराएँगे:

data Second 
data Minute 
data Hour 

type family ToIndex a 
type instance ToIndex Hour = Succ (Succ Zero) 
type instance ToIndex Minute = Succ Zero 
type instance ToIndex Second = Zero 

type family FromIndex a 
type instance FromIndex (Succ (Succ Zero)) = Hour 
type instance FromIndex (Succ Zero) = Minute 
type instance FromIndex Zero = Second 

इसके बाद, Time प्रकार और Show उदाहरणों। ये आपके मूल कोड के समान हैं।

data Time a = Time Int 

instance Show (Time Second) where 
    show (Time t) = show t ++ "sec" 

instance Show (Time Minute) where 
    show (Time t) = show t ++ "min" 

instance Show (Time Hour) where 
    show (Time t) = show t ++ "hrs" 

sec :: Int -> Time Second 
sec t = Time t 

minute :: Int -> Time Minute 
minute t = Time t 

hour :: Int -> Time Hour 
hour t = Time t 

बस की तरह मेरी एडीटी जवाब में, हम मध्यवर्ती इकाई के रूप में सेकंड का उपयोग करेंगे:

class Seconds a where 
    toSeconds :: Time a -> Int 
    fromSeconds :: Int -> Time a 

instance Seconds Hour where 
    toSeconds (Time x) = 3600 * x 
    fromSeconds x = Time $ x `div` 3600 

instance Seconds Minute where 
    toSeconds (Time x) = 60 * x 
    fromSeconds x = Time $ x `div` 60 

instance Seconds Second where 
    toSeconds (Time x) = x 
    fromSeconds x = Time x 

अब शेष रह add समारोह को परिभाषित करने के लिए है।

add :: (Seconds a, Seconds b, Seconds c, 
     c ~ FromIndex (Min (ToIndex a) (ToIndex b))) 
     => Time a -> Time b -> Time c 
add x y = fromSeconds (toSeconds x + toSeconds y) 

जादू समानता बाधा प्रकार में होता है, जो सुनिश्चित करता है कि सही रिटर्न प्रकार चुना जाता है।

इस कोड को इस्तेमाल किया जा सकता, जैसा कि आपने चाहता था:

> add (minute 5) (hour 2) 
125min 

एक और इकाई जोड़ने के लिए, Days कहते हैं, आप केवल Show, FromIndex, ToIndex और Seconds, यानी हम सफलतापूर्वक बचा लिया है के लिए उदाहरण हैं जोड़ने के लिए वर्गिक विस्फोट।

+0

वाह, यह वास्तव में inreresting है। – Landei

2

पहले भाग हास्केल 2010 में इस तरह से नहीं किया जा सकता, क्योंकि instantiated प्रकार पर प्रतिबंध यह है कि वे इस रूप में होने है

T t1 ... tn 

जहां t1 ... tn अलग प्रकार चर रहे हैं और है कि वहाँ ज्यादातर एक उदाहरण समर्थक प्रकार और कक्षा में। Frege में, जबकि प्रकार के रूप में प्रतिबंध थोड़ा उठाए जाते हैं, महत्वपूर्ण प्रतिबंध प्रति वर्ग एक उदाहरण पर रहता है औरकन्स्ट्रक्टर टाइप करें।

module Test where 

data Seconds = Seconds 
data Minutes = Minutes 
data Hours = Hours 

data Time u = Time Int 

class TimeUnit u where 
    verbose :: u -> String 
    fromTime :: Time u -> u 

instance TimeUnit Seconds where 
    verbose _ = "sec" 
    fromTime _ = Seconds 
instance TimeUnit Minutes where 
    verbose _ = "min" 
    fromTime _ = Minutes 
instance TimeUnit Hours where 
    verbose _ = "hrs" 
    fromTime _ = Hours 

instance Show (TimeUnit u) => Time u where 
    show ([email protected] t) = t.show ++ verbose (fromTime o) 

main _ = do 
println (Time 42 :: Time Seconds) 
println (Time 42 :: Time Minutes) 
println (Time 42 :: Time Hours) 

fromTime आवेदन कॉल साइट बलों, एक उपयुक्त शब्दकोश का निर्माण करने के लिए इतना है कि एक TIMEUNIT मूल्य पूर्व nihilo बनाया जा सकता है, या तो यह प्रकट होता है: यहाँ शो-भाग फिर भी ऐसा करने के लिए एक तरीका है।

उसी तकनीक, विभिन्न प्रकार के बीच समय गणित करने के लिए इस्तेमाल किया जा सकता एक पहलू यह है कि सबसे छोटी इकाई में संगणना संभव बनाता है बनाने के द्वारा।

संबंधित मुद्दे