在Haskell中硬编码地图最有效的方法是什么?

在Haskell中硬编码地图最有效的方法是什么?,haskell,Haskell,我想用Haskell硬编码一张地图。我至少可以看到三种方法: 使用多个方程式: message 200 = "OK" message 404 = "Not found" ... 使用大小写表达式: message s = case s of 200 -> "OK" 404 -> "Not found" 实际使用地图 哪种方法最有效?一种解决方案比其他解决方案快吗?为什么? 前两种解决方案等效吗?(编译器会生成相同的代码吗?) 推荐的方法是什么(更容易阅读)

我想用Haskell硬编码一张地图。我至少可以看到三种方法:

  • 使用多个方程式:

    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"