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インスタンスの2次爆発をどのように回避できるのでしょうか。

4

6 に答える 6

13

正当な理由がない限り、型クラスを飛ばして単純な古い ADT を使用します。

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
于 2011-12-22T14:31:06.990 に答える
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)
于 2011-12-22T16:46:03.750 に答える
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 

私のADTの回答と同じように、中間単位として秒を使用します。

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インスタンスを追加するだけで済みます。つまり、2次爆発を回避することに成功しました。ShowFromIndexToIndexSeconds

于 2011-12-22T20:21:48.270 に答える
2

最初の部分は、Haskell 2010 ではこの方法では実行できません。

T t1 ... tn

ここで、t1...tn は異なる型変数であり、最大で 1 つのインスタンス pro 型およびクラスが存在します。Fregeでは、型の形式に関する制限が少し緩和されていますが、重大な制限は、クラスおよび型 コンストラクターごとに最大で 1 つのインスタンスのままです。それでも show-Part を実行する方法は次のとおりです。

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 (o@Time 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 値をゼロから作成できるようになります。つまり、そのように表示されます。

同じ手法を使用して、最小単位での計算を可能にする係数を作成することにより、異なる Time タイプ間の計算を行うことができます。

于 2011-12-23T10:56:34.347 に答える
1

hammarの提案をさらに一歩進めて、この特定の例では、型のものを完全に排除し、代わりにスマートコンストラクターを使用すると思います。

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

于 2011-12-22T18:33:34.277 に答える
0

インスタンスはすべてかなりボイラープレートです。これは Template Haskell のケースだと思います (ただし、テンプレート Haskell を使用したことがある人にその方法の説明を任せます)。

于 2011-12-22T18:21:46.517 に答える