非法实例声明/重叠实例

pyr*_*ade 2 haskell overlapping-instances

给定X和Y类,创建彼此类的实例最常用的方法是什么?例如 -

instance (X a) => Y a where ...
instance (Y a) => X a where ...
Run Code Online (Sandbox Code Playgroud)

我想避免扩展.此外,我知道这可能会导致一些讨厌的无限递归,所以我愿意采用一种完全不同的方法来完成相同的事情并保持相对干燥.下面给出了我遇到的确切问题的一些背景 -

data Dealer = Dealer Hand
data Player = Player Hand Cash

class HasPoints a where
    getPoints :: a -> Int

class (HasPoints a) => CardPlayer a where
    getHand :: a -> Hand

    viewHand :: a -> TurnIsComplete -> Hand

    hasBlackjack :: a -> Bool
    hasBlackjack player = getPoints player == 21 &&
                          (length . getCards . getHand) player == 2

    busts :: a -> Bool
    busts player = getPoints player > 21
Run Code Online (Sandbox Code Playgroud)

我想这样做 -

instance (CardPlayer a) => HasPoints a where
    getPoints = getPoints . getHand
Run Code Online (Sandbox Code Playgroud)

但似乎我必须这样做 -

instance HasPoints Dealer where
    getPoints = getPoints . getHand

instance HasPoints Player where
    getPoints = getPoints . getHand
Run Code Online (Sandbox Code Playgroud)

编辑

看来我最钟爱的做法是保持HasPoints类型类和执行CardPlayer作为data代替.

data CardPlayer = Dealer Hand | Player Hand Cash

instance HasPoints CardPlayer where
    getPoints = getPoints . getHand

getCash :: CardPlayer -> Maybe Cash
getHand :: CardPlayer -> Hand
viewHand :: CardPlayer -> TurnIsComplete -> Hand
hasBlackjack :: CardPlayer -> Bool
busts :: CardPlayer -> Bool

-- I wanted HasPoints to be polymorphic
-- so it could handle Card, Hand, and CardPlayer

instance HasPoints Hand where
    getPoints Hand { getCards = [] } = 0

    getPoints hand = if base > 21 && numAces > 0
                     then maximum $ filter (<=21) possibleScores
                     else base
      where base = sum $ map getPoints $ getCards hand
            numAces = length $ filter ((Ace==) . rank) $ getCards hand
            possibleScores = map ((base-) . (*10)) [1..numAces]

instance HasPoints Card where
    -- You get the point
Run Code Online (Sandbox Code Playgroud)

C. *_*ann 7

给定X和Y类,创建彼此类的实例最常用的方法是什么?

习惯的做法,给你的示例代码,是摆在首位不使用类型的类时,他们没有做什么有用的东西.考虑类函数的类型:

class HasPoints a where
    getPoints :: a -> Int

class (HasPoints a) => CardPlayer a where
    getHand :: a -> Hand
    viewHand :: a -> TurnIsComplete -> Hand
    hasBlackjack :: a -> Bool
    busts :: a -> Bool
Run Code Online (Sandbox Code Playgroud)

他们有什么共同点?它们都只取一个类参数类型的值作为它们的第一个参数,所以给定这样一个值,我们可以将每个函数应用于它并获得所有相同的信息,而不需要打扰类约束.

因此,如果你想要一个很好的,惯用的DRY方法,请考虑这个:

data CardPlayer a = CardPlayer
    { playerPoints :: Int 
    , hand :: Hand
    , viewHand :: TurnIsComplete -> Hand
    , hasBlackjack :: Bool
    , busts :: Bool
    , player :: a
    }

data Dealer = Dealer
data Player = Player Cash
Run Code Online (Sandbox Code Playgroud)

在这个版本中,类型CardPlayer PlayerCardPlayer Dealer等同于PlayerDealer你有类型.player这里的记录字段用于获取专门用于播放器类型的数据,并且在您的类中具有类约束的多态的函数可以简单地对类型的值进行操作CardPlayer a.

虽然它可能会更有意义hasBlackjackbusts成为常规功能(比如你的默认实现),除非你真的需要模拟那些不受Blackjack标准规则影响的玩家.

从这个版本开始,HasPoints如果你有非常不同的类型应该是它的实例,你现在可以单独定义一个类,虽然我对它的效用持怀疑态度,或者你可以应用相同的转换来获得另一个层:

data HasPoints a = HasPoints
    { points :: Int
    , pointOwner :: a
    }
Run Code Online (Sandbox Code Playgroud)

但是,这种方法很快就会变得难以处理,因为您可以进一步嵌套这样的特化.

我建议HasPoints完全放弃.它只有一个函数,它只提取一个Int,所以任何HasPoints一般处理实例的代码也可以只使用Ints并完成它.


Mik*_*kov 6

通常,在不进行类型检查不可判定的情况下,不可能将类的所有实例声明为另一个类的实例.因此,您提出的定义仅适用于UndecidableInstances启用:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

instance (CardPlayer a) => HasPoints a where
    getPoints = getPoints . getHand
Run Code Online (Sandbox Code Playgroud)

虽然可以采用这种方式,但我建议重构代码如下:

data Hand = ...

handPoints :: Hand -> Int
handPoints = ...

data Dealer = Dealer Hand
data Player = Player Hand Cash

class CardPlayer a where
  getHand :: a -> Hand
  ...

instance CardPlayer Dealer where ...
instance CardPlayer Player where ...

playerPoints :: (CardPlayer a) => a -> Int
playerPoints = handPoints . getHand
Run Code Online (Sandbox Code Playgroud)