Haskell:未定义值的模式匹配

时间:2015-12-25 20:55:44

标签: haskell pattern-matching

据我了解,Haskell的App = React.createClass({ mixins: [ReactMeteorData], getMeteorData() { return { applications: Applications.find({}, {sort: {createdAt: -1}}).fetch(), currentApplication: Applications.findOne({_id:this.props.router.params.appid}, {sort: {createdAt: -1}}), } }, getInitialState: function() { return this.loadForm(this.props.router.params.appid); }, loadForm(appId) { var currentApp = Applications.findOne({_id:appId}); if(!currentApp) currentApp = {}; return currentApp; }, clickLoadForm(appId) { var currentApp = this.loadForm(appId); var state = new Object(); var refs = this.refs; Object.keys(refs).map(function(prop,index){ state[prop] = typeof currentApp[prop] == 'undefined' ? "" : currentApp[prop]; }); console.log(state); this.setState(state); }, renderListApplications() { var _this = this; return this.data.applications.map(function(applicationform,i) { return <li key={"li"+i}><a onClick={_this.clickLoadForm.bind(_this,applicationform._id)} href={Meteor.absoluteUrl()+'application/' +applicationform._id} key={"a"+i}>Version {applicationform._id}</a></li>; }); }, handleSubmit(event) { event.preventDefault(); var refs = this.refs; var formVals = new Object(); Object.keys(refs).map(function(prop, index){ if(refs[prop].nodeName.match(/(INPUT|SELECT|TEXTAREA)/).length > 0) formVals[prop] = refs[prop].value; }); Meteor.call("saveApplication", formVals); }, handleChange: function(e) { if(!e.target.id) return; if(typeof e.target.id == 'undefined') return; var state = new Object(); state[e.target.id] = e.target.value; this.setState(state); }, render() { return ( <div className="container"> <ul> {this.renderListApplications()} </ul> <div>{JSON.stringify(this.data.currentApplication)}</div> <form className="new-task" onSubmit={this.handleSubmit} > <input ref="input_36" id="input_36" type="text" value={this.state.input_36} onChange={this.handleChange} /> <input ref="input_37" id="input_37" type="text" value={this.state.input_37} onChange={this.handleChange} /> <textarea ref="input_38" id="input_38" onChange={this.handleChange}>{this.state.input_38}</textarea> <textarea ref="input_39" id="input_39" onChange={this.handleChange}>{this.state.input_39}</textarea> <button type="submit">Submit</button> </form> </div> ); } }); 值 - 特定类型 - 是一个无法确定的值,无论出于何种原因(可能没有合理的定义,或者计算方法不同)。例如:undefined是一个列表,所以它必须用[]或(:)构造,但它不知道哪一个!因此,在undefined上进行大小写分割会使整个表达式未定义是有道理的:我不知道undefined :: [Int]是True还是False,因为我不知道null (undefined :: [a])是否为空。

(顺便说一下 - 如果你不同意我的建议undefined :: [a] 是用构造函数构建的,那么肯定undefined应该评估为False吗?毕竟{{1不等同于[]!)

但是,Haskell模式匹配并不遵循这种思维方式。

null (undefined :: [a])

(我知道newtype在这里表现不同。)

此外:

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? 应该被理解为“这会导致你的程序永远运行”而不是“这个定义不明确”,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. 应该被理解为“注意!你的程序永远不会如果未定义是一个无限循环,而不是“我不知道该怎么做,因为我不知道undefined的值是什么”,终止了。“

有人可以证实这是正确的吗?如果是这样,我可能会接受mb14的回答。

谢谢大家。对不起因为速度太慢了!

5 个答案:

答案 0 :(得分:8)

基本上,您说undefined :: FooFoo undefined undefined应该是相同的,因为(正如您所说But whatever the value is, it must be constructed with Foo.)

这个假设是不正确的。只有可以构造时,才必须使用Foo来构造Foo 。如果我有foo = if x then (Foo 1 "a") else (Foo 2 "b")怎么办?如果x未定义,则尝试评估f分歧。那时,没有构造函数的概念。它只是分歧。

您的示例null (undefined :: [a])也应该是undefined(发散),因为您需要尝试评估null的参数以了解它是否为{{1}或[]

Haskell中的未定义并不神奇,它只是一个循环。您可以定义自己的:。每次尝试评估它时都会循环。 调用loop = let x = x in xnull loop会循环,因此等于sill loop,不是吗?你还认为looploop :: Foo是一样的吗?我不。我希望Foo loop loop循环而不是silly loop

silly (Foo loop loop)loop之间的唯一区别只是ghc可以检测到undefined是一个循环,因此打破外观并显示错误消息而不是永远循环。它只是为了方便起见,如果不存在,人们会定义undefined

答案 1 :(得分:5)

  

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

这意味着它不应该评估为True,而且它不会。

  

我的建议是,如果任何子模式失败,那么整个模式应该失败;如果没有失败,但有些分歧,那么整个模式应该分歧;如果一切顺利,那么整个模式应该会成功。

如何实施?要查找第一个子模式是否失败,您需要评估该参数;如果它出现分歧,则评估不会完成,整个模式匹配按定义分歧。您可以尝试同时评估所有模式&#34;并且#34;但这会使语言复杂化:当前定义将多个方程式转换为在第一个参数上执行case,然后在第二个参数上执行的方程式一等等,所以它没有引入任何新的概念;你的必须。

我不会说#34; undefined应该代表一个非终止计算&#34;,但你不应该能够区分它们:

  

Errors in Haskell are semantically equivalent to ⊥ (“bottom”). Technically, they are indistinguishable from nontermination, so the language includes no mechanism for detecting or acting upon errors. However, implementations will probably try to provide useful information about errors. See Section 3.1.

答案 2 :(得分:4)

你是从错误的前提开始的。

  

例如:undefined :: [Int]是一个列表,因此必须使用[](:)构建,但它不知道哪一个!

不,它既没有构建,也没有。对于某些undefined :: [Int][a]是一个a类型值的列表。它不是使用[](:)构建的列表。这将是循环推理:&#34;它是一个列表,因为它是用[](:)构建的。它由[](:)构成,因为它是一个列表。&#34;

  

(顺便说一下 - 如果您不同意我的建议undefined是使用构造函数构建的,那么null (undefined :: [a])肯定会评估为False吗?毕竟未定义的是{n}相当于[]!)

根据您的逻辑,let loop = loop in null loop还必须提供False因为loop不等同于[],所有其他形式的非终止也必须等同。这将使null解决停机问题*!

您犯的错误是null实际上并未询问该值是否使用[] 构建。它询问该值是否使用[]构建,使用(:)或上述构造。由于undefined既不是[]也不是(:)null undefined既不是True也不是False。这是因为Haskell的布尔逻辑实际上是一个三值系统:BoolTrueFalse和底部居住。谓词完全有可能得到既不是True也不是False的答案。实际上,因为Haskell不是一种完整的语言,所以必须才有可能。

这是另一个反例:

data Void

是没有构造函数的类型。如果必须构建undefined,则它无法居住Void,因为Void没有构造函数。但确实居住在Void。 GHC很乐意接受undefined :: Void

由于不能构造undefined,我不会基于这个前提来解决其余的论点。

现在,让我们谈谈为什么undefined的给定定义可能如此。我将论证undefined是一种用户友好的替代方法,可用于表示底部。

由于Haskell是一种具有一般递归的非整体语言,因此可以始终定义:

undefined :: a
undefined = undefined

这个undefined是一个底价值,是每种类型的居民。但是,它不是一个特别用户友好的底值,因为任何审查都会导致程序无限期挂起。 Prelude中的undefined是一个用户友好的替代方案:审查不会导致程序挂起,而是立即终止程序。但是,在所有其他方面,Prelude.undefined必须充当最低价值才能使用。

考虑另一种选择,其中Prelude.undefined在评估时与某种情况有明显的不同之处#&true;&#34;底价。这意味着我们有两个各种类型的额外居民:&#34; true&#34;底值和额外的非底值,具有微妙的不同语义。这是没有用的,因为我希望这是显而易见的原因。

因此,为了使Prelude.undefined有用,它必须在评估undefined = undefined时具有相同的行为。行为不是相同这一事实是一个方便的问题,但我们可以而且应该将其评估结果视为等效。考虑它所引发的错误&#34; GHC设法为您检测这个非终止程序。欢迎你,但下次你可能不会那么幸运。&#34;实际上,通过这种逻辑,您还应该考虑使用error构建的任何值都是类似的等效底值。这是唯一有用,一致的解释。

*或者更确切地说nullnotNull类似地检查(:)并为底部提供False将形成暂停问题的解决方案:{{1} }。

答案 3 :(得分:1)

您可能需要查看The 2010 Haskell Language Report,特别是关于错误的第3.1节。

简答:您的示例因WHNF而失败 - &gt; NF评估,这与Haskell的懒惰有关,这使得某些值永远不会被使用或者#34;&#34 ;;如果Haskell的懒惰规则允许跳过undefined,那么您将无法获得崩溃和错误代码。否则,你会的。在那种情况下,你做到了。改变这种行为不会涉及改变undefined,因为这会涉及改变Haskell的评估策略。

<强>观察:

[]只是重复应用(:) +一个Nil的语法糖,所以它不是真的&#34;它可以用&#34;构造,或者它是真的,但是误导,因为它们实际上是一回事。

您表达undefined的方式使得它似乎对于类型(或模式匹配)无法完全确定的情况而言是合理的价值,但是,我理解,这不是undefined的目的。相反,假设它具有undefined :: t类型,它允许您将其放在函数内部并在大多数情况下具有该函数类型检查。

让我用另一种方式说明:undefined 应该用于在某个真正意义上保留一个函数,未定义,同时允许你编译,因为{{1} }&#39; s类型将使其成为类型检查(否则您必须从范围中删除该函数)。 undefined,我不相信,有任何其他目的(理想情况下,您应该使用undefined代替)。如果你在其中调用一个带有error的函数,那么 Haskell会评估undefined(我认为这会将它从WHNF表示中转移到NF),然后你&#39 ; ll会收到崩溃+错误消息。

人们因为检查Haskell的懒惰属性等其他原因而使用undefined的事实并不意味着undefined本身应该具有除了每次抛出错误之外的任何行为或属性。编译器看到它。事实上,这就是为什么它是查看Haskell的那些属性的有用函数。关键的观察是,这显示了Haskell如何评估任何事物,而不仅仅是undefined

答案 4 :(得分:1)

除了所有其他答案之外,还有一种捕获异常的方法。缺点是您将必须在IO monad中执行操作。我会解释。要看的东西是evaluatetryErrorCall。它是这样的:

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

在使用undefined以外的任何其他字符时,字符串例如

Right "foo"