Haskell (一种简单的方法)使调用具有特定值的函数成为编译时错误?

Haskell (一种简单的方法)使调用具有特定值的函数成为编译时错误?,haskell,Haskell,我认为不可能(或需要某些语言扩展)生成这样的函数 f :: Maybe Int f (Just n) = n f Nothing = ... -- a compile-time error 你也不可能实现这样的功能: g :: MyClass a => Int -> a g n | n < 10 = TypeClassInstance | otherwise = OtherTypeClassInstance 您可以使用GADT(广义代数数据类型)执行与第一个示

我认为不可能(或需要某些语言扩展)生成这样的函数

f :: Maybe Int
f (Just n) = n
f Nothing = ... -- a compile-time error
你也不可能实现这样的功能:

g :: MyClass a => Int -> a
g n | n < 10    = TypeClassInstance
    | otherwise = OtherTypeClassInstance

您可以使用GADT(广义代数数据类型)执行与第一个示例类似的操作

虽然我怀疑这有多大用处。电路板问题的最简单解决方案可能是在
电路板
数据类型上有一个幻影类型参数:

type Empty = False
type NonEmpty = True
data Board (b :: Bool) = Board ...

newBoard :: Board Empty
newBoard = Board ...

setAt :: (Int, Int) -> Bool -> Board a -> Board NonEmpty 
setAt p b (Board ...) = ...

takeBack :: Board NonEmpty -> Board NonEmpty
takeBack (Board ...) = ...
如果愿意,可以增加类型级别上存储的信息量。例如,您可以拥有填充的“单元格”数量:


上面的示例使用数据类型是为了方便,但这不是必需的。

一种不调用任何类型级编程就可以完成类似任务的简单方法是。要创建智能构造函数,您不需要为数据类型导出真正的构造函数,而是提供一个只创建满足其他规则的类型值的函数

我们可以通过制作智能构造函数来解决这个示例问题,智能构造函数表示一块板是
可玩的
完成的
,或
非空的

type Position = (Int, Int)
type Player = Bool

data Board = Board -- ...
    deriving (Eq, Show, Read, Ord)

newtype Playable = Playable {getPlayable :: Board}
    deriving (Eq, Ord, Show)

newtype Finished = Finished {getFinished :: Board}
    deriving (Eq, Ord, Show)

newtype NonEmpty = NonEmpty {getNonEmpty :: Board}
    deriving (Eq, Ord, Show)
我们谨慎地不提供可能创建任何这些类型的实例;例如,我们没有为它们派生
Read
实例。只有将创建这些的导出函数才会首先检查必要的条件

playable :: Board -> Maybe Playable
playable = undefined

finished :: Board -> Maybe Finished
finished = undefined

nonEmpty :: Board -> Maybe NonEmpty
nonEmpty = undefined
当我们从模块导出类型时,我们不会导出它们的构造函数

module TicTacToe (    
    Playable (getPlayable),
    Finished (getFinished),
    NonEmpty (getNonEmpty),

    playable,
    finished,
    nonEmpty,

    Position,
    Player,

    Board (..),
    move,
    whoWon,
    takeBack,
    playerAt
) where
其余的函数可能要求客户机代码在调用函数之前已经获得必要属性的类型级证明

move :: Position -> Playable -> Board
move = undefined

whoWon :: Finished -> Player
whoWon = undefined

takeBack :: NonEmpty -> Board
takeBack = undefined
对于这个示例问题,聪明的构造函数完全一事无成。任何库用户都将定义助手函数,这样他们只需关心
可能
,而不必关心任何其他特殊的电路板类型

move' :: Position -> Board -> Maybe Board
move' p = fmap (move p) . playable

whoWon' :: Board -> Maybe Player
whoWon' = fmap whoWon . finished

takeBack' :: Board -> Maybe Board
takeBack' = fmap takeBack . nonEmpty
这表明在接口中使用
Maybe
就足够了,而在练习中对编译时错误的要求是多余的。这也符合以下请求的函数,该函数不需要类型级别的证明,即在使用之前有人已经移动到该位置

playerAt :: Position -> Board -> Maybe Player
playerAt = undefined

当存在许多变换时,使用类型级的属性证明更为有利,因为这些变换的属性是不变的或易于推导

takeBack
应具有类型
Board(sn)->Board n
,因为它将从板上删除一个移动。谢谢。幻影类型似乎很有希望,直到我尝试并意识到
回退
可以产生
板空
板非空
,这取决于之前是否有一次或多次移动。因此,基于输入值的类型更改仍然存在相同的问题。@Cirdec对
回退
非常有效,但请参阅我的更新,了解为什么它仍然太有限。关于您的更新和
移动
,练习的目标是玩类型级别的tic tac toe吗?你是否考虑过练习中没有定义的“完成”和“在游戏中”可能意味着“棋盘上有三个棋盘或棋盘已满”和“非空且未完成”以外的其他意思?
move :: Position -> Playable -> Board
move = undefined

whoWon :: Finished -> Player
whoWon = undefined

takeBack :: NonEmpty -> Board
takeBack = undefined
move' :: Position -> Board -> Maybe Board
move' p = fmap (move p) . playable

whoWon' :: Board -> Maybe Player
whoWon' = fmap whoWon . finished

takeBack' :: Board -> Maybe Board
takeBack' = fmap takeBack . nonEmpty
playerAt :: Position -> Board -> Maybe Player
playerAt = undefined