在Haskell中硬编码地图最有效的方法是什么?
我想用Haskell硬编码一张地图。我至少可以看到三种方法:在Haskell中硬编码地图最有效的方法是什么?,haskell,Haskell,我想用Haskell硬编码一张地图。我至少可以看到三种方法: 使用多个方程式: message 200 = "OK" message 404 = "Not found" ... 使用大小写表达式: message s = case s of 200 -> "OK" 404 -> "Not found" 实际使用地图 哪种方法最有效?一种解决方案比其他解决方案快吗?为什么? 前两种解决方案等效吗?(编译器会生成相同的代码吗?) 推荐的方法是什么(更容易阅读)
- 使用多个方程式:
message 200 = "OK" message 404 = "Not found" ...
- 使用
表达式:大小写
message s = case s of 200 -> "OK" 404 -> "Not found"
- 实际使用
地图
(请注意,我在示例中使用了
Int
,但这不是必需的。键可能也是String
s,因此我对这两种情况都感兴趣。)案例。。。和多个方程完全等价。它们编译到同一个核心。对于大多数情况,您可能应该这样做:
import qualified Data.Map as Map
message =
let
theMap = Map.fromList [ (200, "OK"), (404, "Not found"), ... ]
in
\x -> Map.lookup x theMap
这只会构造一次地图。如果您不喜欢Maybe字符串
返回类型,可以将fromaybe
应用于结果
对于少数情况(特别是当它们是整数时),如果编译器能够将case语句转换为跳转表,那么case语句可能会更快
在理想情况下,ghc会自动选择正确的版本。函数模式匹配发生在O(1)
(恒定时间)中,而Map
的查找(当然是指Data.Map
)保证是O(logn)
考虑到上述假设,我会选择模式匹配:
message 200 = "OK"
message 404 = "Not found"
Int
s上的模式匹配发生在O(log(n))
时间,就像地图查找一样
考虑以下代码,由ghc-S
module F (
f
) where
f :: Int -> String
f 0 = "Zero"
f 1 = "One"
f 2 = "Two"
f 3 = "Three"
f 4 = "Four"
f 5 = "Five"
f 6 = "Six"
f 7 = "Seven"
f _ = "Undefined"
编译的汇编代码是
.text
.align 4,0x90
.long _F_f_srt-(_sl8_info)+0
.long 0
.long 65568
_sl8_info:
.Lcma:
movl 3(%esi),%eax
cmpl $4,%eax
jl .Lcmq
cmpl $6,%eax
jl .Lcmi
cmpl $7,%eax
jl .Lcme
cmpl $7,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_cm7_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmc:
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clB_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcme:
cmpl $6,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_cm3_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmg:
cmpl $4,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clV_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmi:
cmpl $5,%eax
jl .Lcmg
cmpl $5,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clZ_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmk:
cmpl $2,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clN_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmm:
testl %eax,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clF_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmo:
cmpl $1,%eax
jl .Lcmm
cmpl $1,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clJ_str,0(%ebp)
jmp _stg_ap_n_fast
.Lcmq:
cmpl $2,%eax
jl .Lcmo
cmpl $3,%eax
jl .Lcmk
cmpl $3,%eax
jne .Lcmc
movl $_ghczmprim_GHCziCString_unpackCStringzh_closure,%esi
movl $_clR_str,0(%ebp)
jmp _stg_ap_n_fast
.text
.align 4,0x90
.long _F_f_srt-(_F_f_info)+0
.long 65541
.long 0
.long 65551
.globl _F_f_info
_F_f_info:
.Lcmu:
movl 0(%ebp),%esi
movl $_sl8_info,0(%ebp)
testl $3,%esi
jne .Lcmx
jmp *(%esi)
.Lcmx:
jmp _sl8_info
这是对整数参数进行二进制搜索
.Lcma
是跳转表上的分支,对于像Bool
这样的小型类型来说很有意义(好吧,可能没有那么小),在某些情况下对于像Word8
这样的中型类型来说也很有意义,并且作为更大类型的混合方案的一部分(即,首先检查范围,然后在范围内跳转)但是对于像Int
这样的大类型的任意值,我相信线性扫描(对于小数量的情况)或类似Map
的实现(对于大数量的情况)应该更有效。这个haskell cafe线程涉及到这个主题:你有线性复杂性的证据吗?如果这适用于小的代数类型,我会非常惊讶,但我想这可能适用于Int
s.@dfeuer。有趣。相应地更新了答案。感谢您让我检查。您确定为Int
匹配构造函数的实现方式与为构造函数匹配标记的实现方式相同吗,链接答案中发生了什么?请参阅我的答案,了解在Int的构造函数上进行匹配时实际发生的情况。如果您将ADT与-O2
一起使用,我相信您将得到一个跳转表。还可以看到这个haskell cafe线程:这对字符串不起作用-我有两个生成的函数,大约16000行模式,它为包的构建增加了几分钟时间+速度明显慢(尽管我没有正确地配置)String
在大多数情况下都不是一个好的类型,如果您对效率感兴趣的话。GHC开发人员甚至不可能为模式匹配编写特殊优化:代码>字符串< /代码>。我想它们与其他列表是一样的。对于字符串,您可能需要考虑使用<代码> ByTeString < /C> >和<代码> ByTetryStre < /Cord>包。
.text
.align 4,0x90
.long _F_zdwf_srt-(_F_zdwf_info)+0
.long 65540
.long 0
.long 33488911
.globl _F_zdwf_info
_F_zdwf_info:
.LcqO:
movl 0(%ebp),%eax
cmpl $4,%eax
jl .Lcr6
cmpl $6,%eax
jl .LcqY
cmpl $7,%eax
jl .LcqU
cmpl $7,%eax
jne .LcqS
movl $_F_f1_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.LcqS:
movl $_F_f9_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.LcqU:
cmpl $6,%eax
jne .LcqS
movl $_F_f2_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.LcqW:
cmpl $4,%eax
jne .LcqS
movl $_F_f4_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.LcqY:
cmpl $5,%eax
jl .LcqW
cmpl $5,%eax
jne .LcqS
movl $_F_f3_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.Lcr0:
cmpl $2,%eax
jne .LcqS
movl $_F_f6_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.Lcr2:
testl %eax,%eax
jne .LcqS
movl $_F_f8_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.Lcr4:
cmpl $1,%eax
jl .Lcr2
cmpl $1,%eax
jne .LcqS
movl $_F_f7_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.Lcr6:
cmpl $2,%eax
jl .Lcr4
cmpl $3,%eax
jl .Lcr0
cmpl $3,%eax
jne .LcqS
movl $_F_f5_closure,%esi
addl $4,%ebp
andl $-4,%esi
jmp *(%esi)
.section .data
.align 4
.align 1
_F_f_srt:
.long _F_zdwf_closure
.data
.align 4
.align 1
.globl _F_f_closure
_F_f_closure:
.long _F_f_info
.long 0
.text
.align 4,0x90
.long _F_f_srt-(_srh_info)+0
.long 0
.long 65568
_srh_info:
.Lcrv:
movl 3(%esi),%eax
movl %eax,0(%ebp)
jmp _F_zdwf_info
.text
.align 4,0x90
.long _F_f_srt-(_F_f_info)+0
.long 65541
.long 0
.long 65551
.globl _F_f_info
_F_f_info:
.Lcrz:
movl 0(%ebp),%esi
movl $_srh_info,0(%ebp)
testl $3,%esi
jne _srh_info
jmp *(%esi)
f :: Int -> String
f 1 = "Zero"
f 2 = "One"
f 3 = "Two"
f 4 = "Three"
f 5 = "Four"
f 6 = "Five"
f 7 = "Six"
f 8 = "Seven"
f _ = "Undefined"
f :: Int -> String
f 20 = "Zero"
f 80 = "One"
f 70 = "Two"
f 30 = "Three"
f 40 = "Four"
f 50 = "Five"
f 10 = "Six"
f 60 = "Seven"
f _ = "Undefined"