在Haskell中,()类型有两个值,即()bottom和bottom。如果你有一个表达式e :: (),那么实际检查它是没有意义的,因为无论是它e = ()还是通过检查它,你都会使一个本来不会崩溃的程序崩溃。
因此,我认为对类型值的操作()可能不会检查该值,也不会区分()和底部。
然而,这是完全不正确的:
\n\xe2\x96\x8e\xce\xbb ghci\nGHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help\nghci> u = (undefined :: ())\nghci> show u\n"*** Exception: Prelude.undefined\nCallStack (from HasCallStack):\n error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err\n undefined, called at <interactive>:1:6 in interactive:Ghci1\nghci> () == u\n*** Exception: Prelude.undefined\nCallStack (from HasCallStack):\n error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err\n undefined, called at <interactive>:1:6 in interactive:Ghci1\nghci> f () = "ok"\nghci> f u\n"*** Exception: Prelude.undefined\nCallStack (from HasCallStack):\n error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err\n undefined, called at <interactive>:1:6 in interactive:Ghci1\nRun Code Online (Sandbox Code Playgroud)\n这是什么原因呢?以下是一些猜想:
\n由于某种我想不出的原因,在 . 上保持非懒惰是有用的()。有时我们希望底部能够传播。
Haskell 语义的编写方式可以解构任何ADT,即使是琐碎的 ADT,也可以检查它们。这意味着不case (undefined :: ()) of { () -> ... } 抛出将违反语言语义
()是一个极其特殊的情况,根本不值得关注像 Haskell 这样的大型语言来维持这一点额外的安全性
还有 2+3 的可能组合解释,Haskell可能有语义规定表达式case e of检查,e 除非它是类型(),但这会污染语言规范,带来相对较低的好处
我将解决这一部分:
由于某种我想不出的原因,在 . 上保持非懒惰是有用的
()。有时我们希望底部能够传播。
让我们看一下Control.Parallel.Strategies(版本1,较旧的版本)。这是一个用于并行评估的模块。为了简单起见,让我们重点关注它的一个功能:
parMap :: Strategy b -> (a -> b) -> [a] -> [b]
Run Code Online (Sandbox Code Playgroud)
的结果parMap strat f xs与 相同map f xs,只是列表是并行计算的。论点是什么strat?出色地,
strat :: Strategy b
Run Code Online (Sandbox Code Playgroud)
方法
strat :: b -> ()
Run Code Online (Sandbox Code Playgroud)
您只能做两件事strat:
()是底部。parMap后者是并行执行的。这允许调用者指定一个参数,根据需要strat计算 type 的列表值。例如b
parMap (\(x,y) -> ()) f xs
parMap (\(x,y) -> x `seq` ()) f xs
parMap (\(x,y) -> x `seq` y `seq` ()) f xs
Run Code Online (Sandbox Code Playgroud)
are valid calls, and will cause parMap to evaluate the new list-of-pairs only to expose the pair constructor, also the first component, also the second component, respectively.
Hence, forcing the () result of strat in this case allows the user to control how much evaluation to perform during parMap, i.e. how much to force the result (in parallel), and consequently which parts of the result should be left unevaluated. (By comparison map f xs would leave the result fully unevaluated -- it is completely lazy. parMap can not do that otherwise it is not longer parallel.)
Minor digression: note that the GADT
data a :~: b where
Refl :: t :~: t
Run Code Online (Sandbox Code Playgroud)
has one constructor like (). Here, it is mandatory that such values are forced as in:
foo :: Int :~: String -> Int -> String
foo Refl x = x ++ " hello"
Run Code Online (Sandbox Code Playgroud)
Here the first argument must be a bottom. By forcing that, we make the function error out with an exception. If we did not force that, we would get a very nasty undefined behavior like those in C and C++, completely breaking type safety. Haskell will correctly reject any attempt to circumvent that:
foo :: Int :~: String -> Int -> String
foo _ x = x ++ " hello"
Run Code Online (Sandbox Code Playgroud)
triggers a type error at compile time.
我不确定,但我怀疑这不是你所说的。相反,这是为了使语言是可预测的和一致的。
从本质上讲,您观察到了两件事,我认为它们是不同的事情。第一个是检查 a 是否x确实()带有case语句强制评估x; 第二个是实例 (ofShow和Eq) 被编写为使用case语句。
模式匹配:这里可预测的、一致的规则是,如果您编写case <e0> of <pat> -> <e1>,那么e0将被评估得足够远,以检查 中的构造函数pat实际上是否位于给定位置。好吧,好吧,这里有一些与无可辩驳的模式有关的皱纹;假设对它e0进行了足够的评估以检查是否pat确实匹配!对于该()类型,这意味着该模式()会导致完整评估——因为您已经指定了您期望的完整值——而模式x或_可以匹配而无需进一步评估。
类实例:指定各种类实例执行的操作的自然归纳方法是始终有一个最外层case与每个可用构造函数匹配,并为字段提供简单的变量模式,然后依次对每个字段执行某些操作(可能是递归调用) 。也就是说,稍微简化一下,show实现如下:
show x = case x of
<Con0> field00 field01 field02 <...> -> "<Con0>"
++ " " ++ show field00
++ " " ++ show field01
++ " " ++ show field02
++ <...>
<Con1> field10 field11 field12 <...> -> "<Con1>"
++ " " ++ show field10
++ " " ++ show field11
++ " " ++ show field12
++ <...>
<...>
Run Code Online (Sandbox Code Playgroud)
将该方案专门化为单构造函数、零字段类型是非常自然的():
show x = case x of
() -> "()"
Run Code Online (Sandbox Code Playgroud)
(此外,报告指出,这(==)两个参数始终是严格的;但该属性也会自然地从编写通用Eq实例派生算法的明显方式中产生。)因此,最不令人惊讶的路径是类实例在其上进行模式匹配参数。