在Haskell中评估函数->()有什么规则?


12

就像标题中所说的那样:对Haskell函数返回单元进行评估有什么保证?有人会认为在这种情况下无需运行任何类型的评估,()除非存在明确的严格性要求,否则编译器可以将所有此类调用替换为立即值,在这种情况下,代码可能必须决定是否应返回()或底部。
我已经在GHCi中对此进行了实验,似乎发生了相反的情况,也就是说,似乎正在评估这种功能。一个非常原始的例子是

f :: a -> ()
f _ = undefined

f 1由于存在,评估会引发错误undefined,因此肯定会发生某些评估。但是,尚不清楚评估的深度。有时它看起来像需要评估返回函数的所有调用一样深入()。例:

g :: [a] -> ()
g [] = ()
g (_:xs) = g xs

如果使用,此代码将无限循环g (let x = 1:x in x)。但是之后

f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()

可用于显示h (f 1)return (),因此在这种情况下,并非所有单位值的子表达式都被求值。这里的一般规则是什么?

ETA:我当然知道懒惰。我在问什么阻止编译器编写者使这种特殊情况变得比平时更懒。

ETA2:示例摘要:GHC似乎与()任何其他类型完全一样,即,似乎存在一个问题,即应该从函数中返回驻留该类型的哪个常规值。优化算法似乎并没有滥用这样一个值的事实。

ETA3:当我说Haskell时,我的意思是报告定义的Haskell,而不是GHC中的Haskell。似乎是一个假设没有像我想象的那样广泛共享(这是“ 100%的读者”),或者我可能能够提出一个更明确的问题。即便如此,我还是很遗憾更改问题的标题,因为它最初询问要为此类函数调用提供什么保证

ETA4:这个问题似乎已经解决了,我认为这个问题没有得到解决。(我一直在寻找一个“封闭式问题”功能,但只找到了“回答您自己的问题”,由于无法回答,所以我没有走这条路线。)没有人从报告中提出任何决定该问题的方式,我很想将其解释为有力但不确定的“不能保证如此语言”的答案。我们所知道的是,当前的GHC实施不会跳过对该功能的评估。

将OCaml应用程序移植到Haskell时遇到了实际问题。原始应用程序具有多种类型的相互递归结构,并且代码assert_structureN_is_correct在1..6或7中声明了许多要求N 的函数,如果结构确实正确,则每个函数都返回单位;如果结构不正确,则返回异常。 。另外,这些函数在分解正确性条件时会相互调用。在Haskell中,使用Either Stringmonad 可以更好地解决此问题,因此我以这种方式进行了转录,但是作为理论问题仍然存在。感谢您的所有输入和回复。


1
这是工作的懒惰。除非需要函数的结果(例如,通过与构造函数的模式匹配),否则不会评估函数的主体。要观察差异,请尝试比较h1::()->() ; h1 () = ()h2::()->() ; h2 _ = ()。同时运行h1 (f 1)h2 (f 1),看到只有第一个需求(f 1)

1
“懒惰似乎决定了用()代替它,而不会进行任何评估。” 这意味着什么?在所有情况下都f 1被“替换” undefined
oisdk

3
函数... -> ()可以1)终止并返回(),2)在发生异常/运行时错误时终止并且无法返回任何内容,或者3)求差(无限递归)。GHC不会优化代码,前提是只会发生1):如果f 1有要求,它不会跳过评估并返回()。Haskell语义是对其进行评估,并查看1,2,3之间会发生什么。

2
这个问题的确没有什么特别的()(类型或值)。如果您将其替换() :: ()0 :: Int无处不在,则会发生所有相同的观察。这些都是懒惰评估的无聊的旧结果。
Daniel Wagner

2
不,“避免”等不是Haskell语义。和有2个的可能的值()的类型,()undefined
Will Ness

Answers:


10

您似乎来自这样的假设,即该类型()只有一个可能的值,()因此期望应该自动假定任何返回一个type值的函数调用()都确实会产生value ()

这不是Haskell的工作方式。每个类型在Haskell中都有一个值,即没有值,错误或由编码的所谓的“底部” undefined。因此,实际上是在进行评估:

main = print (f 1)

等同于核心语言的

main = _Case (f 1) _Of x -> print x   -- pseudocode illustration

甚至(*)

main = _Case (f 1) _Of x -> putStr "()"

和核心的_Case强迫

“对%case[expression]求值会强制对要测试的表达式(“ scrutinee”)进行求值。scrutinee的值绑定到%of关键字... 之后的变量。”

该值被强制为弱磁头正常形式。这是语言定义的一部分。

Haskell是不是一个声明编程语言。


(*) print x = putStr (show x)show () = "()",因此show可以完全将调用编译掉。

该值的确预先称为(),甚至的值show ()也预先称为"()"。仍然接受的Haskell语义要求(f 1)在继续打印预先已知的字符串之前,必须将的值强制设为弱头范式"()"


编辑:考虑concat (repeat [])。应该是[]还是应该是无限循环?

一种“声明性语言”对此的答案很可能是[]。Haskell的答案是无限循环

懒惰 “穷人的声明式编程”,但这仍然不是真实的东西

edit2print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()并且仅h是强制的,不是f;并产生回答h不必强求什么,根据它的定义h _ = ()

分开的话:懒惰可能有存在的理由,但这不是它的定义。懒惰就是它。它被定义为所有最初是thunk的值,根据来自的要求被迫向WHNF迁移main。如果它有助于根据特定情况在特定情况下避免触底反弹,那么它可以做到。如果没有,那就没有。就这些。

它可以帮助您自己以自己喜欢的语言来实现它,以使自己有一种感觉。但是,我们也可以通过仔细命名所有临时值来跟踪任何表达式的评估。


根据报告,我们有

f :: a -> ()
f = \_ -> (undefined :: ())

然后

print (f 1)
 = print ((\ _ ->  undefined :: ()) 1)
 = print          (undefined :: ())
 = putStrLn (show (undefined :: ()))

instance Show () where
    show :: () -> String
    show x = case x of () -> "()"

它继续

 = putStrLn (case (undefined :: ()) of () -> "()")

现在,报告的第3.17.3“模式匹配形式语义 ”说

case[图3.1-3.3中给出了表达式的语义]。任何实现都应表现为使这些身份成立。

和壳体(r)图3.2的状态

(r)     case  of { K x1  xn -> e; _ -> e } =  
        where K is a data constructor of arity n 

() 是arity 0的数据构造函数,因此它与

(r)     case  of { () -> e; _ -> e } =  

因此,评估的总体结果为


2
我喜欢你的解释。这很简单。
箭头

@DanielWagner我case实际上已经想到了Core的功能,而忽略了这个巨大的漏洞。:)我编辑提到了Core。
尼斯,

1
强制不会通过show调用print吗?就像show x = case x of () -> "()"
user253751 '19

1
我指的case是Core,而不是Haskell本身。Haskell被翻译成Core,具有强制caseAFAIK。您是正确的,case在Haskell中并不是强制执行的。我可以用Scheme或ML(如果可以写ML)或伪代码来写东西。
尼斯,

1
有关所有这些问题的权威性答案可能在报告中。我所知道的是,这里没有“优化”,“常规值”在这种情况下不是有意义的术语。无论被迫,被迫。print强制打印所需的内容。它不看类型,在程序运行时类型消失了,被擦除了,已经根据类型在编译时选择了正确的打印子例程并进行了编译;该子例程仍将在运行时将其输入值强制为WHNF,如果未定义,将导致错误。
Will Ness
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.