fromJust
中的Data.Maybe
函数以这种方式定义:
fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x
根据我对模式匹配的理解(从上到下匹配收益),我会改变两个定义的顺序。由于Nothing-part通常在it-is-sure-a-Just情况下不匹配,但在达到第二个定义之前总是会检查它。
请您澄清我在推理中的错误?感谢。
修改
实施例:
假设我的文件中每行有一百万个Int
类型的数据,并且在我的程序中y需要这些数字(Int
,而不是String
)。
import qualified Data.ByteString.Lazy.Char8 as L
readInt = fst . fromJust . L.readInt
-- more stuff
根据fromJust
的上述定义,我需要更多时间来阅读这些数字,不是吗?
答案 0 :(得分:9)
我认为这个问题与性能有关。虽然语义模式匹配是从上到下进行测试的,但大多数Haskell编译器会优化ADT构造函数的匹配,使其等效于C switch
语句。
您可以认为ADT的数据表示形式有一个“标记”,表示它的构造函数,以及每个参数的指针。例如,Nothing
可能表示为0 (null)
,Just 42
表示为1 (pointer to 42)
。
然后在这样的函数中:
squash :: Maybe (Maybe Int) -> Int
squash (Just Nothing) = 0
squash Nothing = 0
squash (Just (Just x)) = x
编译器会设置决策树:
squash x =
check tag of x:
0 -> 0
1 y -> check tag of y:
0 -> 0
1 z -> z
每个标记都是通过跳转表或其他内容计算的,因此将0
作为1
进行检查并不是更昂贵。请注意,无论我们的定义中的模式的原始顺序如何,都会生成相同的决策树。
但是,当使用保护而不是在构造函数上匹配时,模式最有可能从上到下进行检查(编译器必须非常智能来优化)。所以如果我们用这种神秘的方式写fromJust
:
fromJust x
| isNothing x = error "fromJust: Nothing"
| isJust x = case x of { Just y -> y }
然后,这可能会轮流检查每个构造函数,我们可以通过切换案例的顺序进行优化。幸运的是,写作方式很重要: - )。
答案 1 :(得分:6)
这里要实现两个重要的事情:首先,Haskell编译器完全清楚任何值都只能是Nothing
或Just x
。因此,它只会测试其中一个。其次,GHC使用指针标记,允许它快速区分指向各种构造函数的指针(details)。
因此,GHC为模式匹配生成了相当不错的代码。以下是fromJust
返回代码的外观,直接来自程序集转储:
andq $7,%rax
cmpq $2,%rax
jae .LcO4
这将获取返回值,然后使用位掩码操作(7 = 111二进制)提取标记。如果此标记为2,则代码知道它指向Just x
构造函数。因此它跳转到适当的代码。否则,它只会继续处理Nothing
。
我认为甚至可以使用程序中只有一个Nothing
闭包的知识进一步优化。因此,您可以使用单个地址比较替换它,从而在实际代码中保存位掩码操作甚至寄存器。不确定为什么GHC不会这样做。
答案 2 :(得分:0)
匹配Nothing
的内容无法匹配Just
。所以两个订单都有效。
如果您通过Just 5
到fromJust
,则Just 5
与Nothing
不匹配。但Just 5
与Just x
匹配。
它的工作方式完全相同。当函数没有在其中一个参数上进行模式匹配时,事情才开始变得棘手。