Haskell:未定义值上的模式匹配

Haskell:未定义值上的模式匹配,haskell,pattern-matching,Haskell,Pattern Matching,据我所知,Haskell的未定义的值——属于特定类型——是一个无法确定的值,无论出于何种原因(可能没有合理的定义,或者计算存在分歧)。例如:undefined::[Int]是一个列表,所以它必须用[]或(:)来构造,但它不知道是哪一个!因此,在undefined上拆分大小写使整个表达式未定义是有意义的:我不知道null(undefined:[a])是真是假,因为我不知道undefined:[a]是否为空 (顺便说一句,如果你不同意我的建议,undefined是用构造函数构建的,那么肯定null(

据我所知,Haskell的
未定义的
值——属于特定类型——是一个无法确定的值,无论出于何种原因(可能没有合理的定义,或者计算存在分歧)。例如:
undefined::[Int]
是一个列表,所以它必须用[]或(:)来构造,但它不知道是哪一个!因此,在undefined上拆分大小写使整个表达式未定义是有意义的:我不知道
null(undefined:[a])
是真是假,因为我不知道
undefined:[a]
是否为空

(顺便说一句,如果你不同意我的建议,
undefined
是用构造函数构建的,那么肯定
null(undefined::[a])
应该计算为False?毕竟
undefined
并不等同于[]!)

然而,Haskell模式匹配并不遵循这种思维方式

data Foo = Foo Int String  -- Only one data constructor

silly :: Foo -> Bool
silly (Foo _ _) = True

ghci> silly (undefined :: Foo)
*** Exception: Prelude.undefined    -- But whatever the value is, it must 
                                    -- be constructed with Foo.
                                    -- So why fail to match?
(我知道newtype在这里的行为不同。)

此外:

我认为目前关于模式匹配的规则说,如果一个子模式失败,那么该模式应该立即失败;如果它发散,那么整个模式也应该发散;如果成功,那么应该尝试下一个子模式。如果所有子模式都成功,那么整个模式都成功

我的意思是,如果任何一个子模式失败,那么整个模式都应该失败;如果没有失败,但有一些分歧,那么整个模式应该分歧;如果所有人都成功了,那么整个模式就应该成功

为什么哈斯克尔不是这样做事的


编辑:

因此,总结一下我对所读回复的理解:

未定义的
应理解为“这将导致您的程序永远运行”,而不是“这没有定义好”,并且
异常:前奏。未定义的
应理解为“小心!如果未定义的是无限循环,您的程序将永远不会终止”,而不是“我不知道该怎么做,因为我不知道未定义的值是什么”

有人能证明这是正确的吗?如果是这样,我可能会接受mb14的答案


谢谢大家。很抱歉理解得这么慢!

您可能想看一看关于错误的第3.1节

简短回答:您的示例失败是因为WHNF->NF评估,这与Haskell的懒惰有关,这使得某些值可能永远不会被使用或“查看”"; 如果Haskell的惰性规则允许跳过
未定义的
,则不会得到崩溃和错误代码。否则,你会。在这种情况下,你做到了。改变这种行为并不需要改变
未定义的
,而是需要改变Haskell的评估策略

观察结果:

[]只是重复应用(:)+Nil的语法糖,所以“它可以用其中一个构造”不是真的,或者它是真的,但会产生误导,因为它们实际上是相同的东西

对于类型(或模式匹配)不完全可确定的情况,您对
未定义的
的表述方式似乎是一个合理的值,但是,据我所知,这不是
未定义的
的目的。相反,考虑到它有一个类型为
undefined::t
,它允许您将它放在函数中,并在大多数情况下对该函数进行类型检查

让我用另一种方式来表述:
undefined
应该用来保留一个真正意义上的函数,undefined,同时允许您进行编译,因为
undefined
的类型将使它进行类型检查(否则您必须从范围中删除该函数)<代码>未定义的
,我不认为有任何其他用途(理想情况下,您应该使用
错误
)。如果您调用一个函数,其中包含
未定义
,并且Haskell曾经计算
未定义
(我认为这将把它从WHNF表示形式转换为NF),那么您将得到一条崩溃+错误消息

人们使用
undefined
作为其他原因,比如检查Haskell的惰性属性,这一事实在我看来并不意味着
undefined
本身应该有任何行为或属性,而不是每次编译器看到它时抛出错误。事实上,这就是为什么它是一个有用的函数,用于查看Haskell的那些属性。关键的观察结果是,这显示了Haskell如何评估任何东西,而不仅仅是
未定义的

(顺便说一句,如果您不同意我的建议,即未定义是使用构造函数构建的,那么肯定null(未定义::[a])应该计算为False?毕竟未定义并不等同于[]!)

这意味着它不应该计算为
True
,也不应该

我的意思是,如果任何一个子模式失败,那么整个模式都应该失败;如果没有失败,但有一些分歧,那么整个模式应该分歧;如果所有人都成功了,那么整个模式就应该成功

如何实现这一点?要确定第一个子模式是否失败,您需要评估该参数;如果它发散,评估将不会完成,整个模式匹配将根据定义发散。您可以尝试“并行”计算所有模式,但这会使语言变得非常复杂:当前的定义将多个方程转换为一个方程,第一个参数是
case
,第二个参数是
,等等,因此它不会引入任何新概念;你的就必须这么做

我不会说“
未定义的
应该表示一个非终止计算”,但你不应该能够区分它们:


基本上,您是说
undefined::Foo
Foo undefined undefined
应该是相同的,因为
foo :: Int -> String -> Bool
foo 8 "Hello" = True
foo _ _       = False

ghci> foo undefined undefined
*** Exception: Prelude.undefined     -- GOOD - can't tell which case to choose.

ghci> foo undefined "Hello"
*** Exception: Prelude.undefined     -- GOOD - still can't tell.

ghci> foo undefined "Goodbye"
*** Exception: Prelude.undefined     -- BAD  - should return false!
                                     -- Pattern match on first line should fail,
                                     -- because whatever the int is, the
                                     -- string can't match the given pattern.
data Void
undefined :: a
undefined = undefined
import Control.Exception

test :: a -> IO (Either ErrorCall a)
test val = try (evaluate val)

main = do
    result <- test (undefined :: String)
    print result
Left Prelude.undefined
CallStack (from HasCallStack):
 error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
 undefined, called at main.hs:7:21 in main:Main
Right "foo"