Что Крис говорит, что в его ответе все верно. Хотя вы не можете полностью полагаться на выражение для совместного использования, это эффективная оптимизация для GHC для его совместного использования, и обычно это делается при оптимизации. Тем не менее, вы можете не захотеть полагаться на эту функциональность, и если вы можете сделать явный доступ к ним, подняв вызов лямбды, я бы так сказал.
Использование Debug.Trace.trace
для таких целей - хороший способ получить представление о том, когда и как часто оцениваются вещи.
Другой вариант - посмотреть на сгенерированный код ядра. Для этой программы:
main = print x
x = let strings = ["foo", "bar", "baz"]
in [filter (== char) (concat strings) | char <- "oa"]
Давайте компилировать заменяется без оптимизаций и посмотреть на полученный код:
$ ghc NrEval -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling Main (NrEval.hs, NrEval.o)
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 50, types: 54, coercions: 0}
main
main =
print
($fShow[] ($fShow[] $fShowChar))
(let {
a_ssN
a_ssN = unpackCString# "foo" } in
let {
a1_ssQ
a1_ssQ = unpackCString# "bar" } in
let {
a2_ssT
a2_ssT = unpackCString# "baz" } in
let {
a3_ssU
a3_ssU = : a2_ssT ([]) } in
let {
a4_ssV
a4_ssV = : a1_ssQ a3_ssU } in
let {
strings_ahk
strings_ahk = : a_ssN a4_ssV } in
letrec {
ds_dsE
ds_dsE =
\ ds1_dsF ->
case ds1_dsF of _ {
[] -> [];
: ds3_dsG ds4_dsH ->
: (filter
(\ ds5_dsI -> == $fEqChar ds5_dsI ds3_dsG) (concat strings_ahk))
(ds_dsE ds4_dsH)
}; } in
ds_dsE (unpackCString# "oa"))
main
main = runMainIO main
Мы можем видеть, как даже без оптимизаций списка понимание было обессахаренная в (встраиваются) применения map
в ds_dsE
. Однако concat strings_ahk
остается под лямбдой (ds1_dsF
), что означает, что он будет оцениваться каждый раз, когда функция будет оценена, что два раза: один раз в вызове ds_dsE
в строку "oa"
и один раз в рекурсивном вызове ds_dsE ds4_dsH
.
Теперь давайте сравним результаты с оптимизаций:
$ ghc NrEval -fforce-recomp -ddump-simpl -dsuppress-all -O
[1 of 1] Compiling Main (NrEval.hs, NrEval.o)
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 89, types: 105, coercions: 9}
main9
main9 = unpackCString# "foo"
main8
main8 = unpackCString# "bar"
main7
main7 = unpackCString# "baz"
main6
main6 = : main7 ([])
main5
main5 = : main8 main6
main_strings
main_strings = : main9 main5
main4
main4 =
\ ds_dsT ds1_dsS ->
: (letrec {
go_ato
go_ato =
\ ds2_atp ->
case ds2_atp of _ {
[] -> [];
: y_atu ys_atv ->
let {
z_XtU
z_XtU = go_ato ys_atv } in
letrec {
go1_XtX
go1_XtX =
\ ds3_XtZ ->
case ds3_XtZ of _ {
[] -> z_XtU;
: y1_Xu6 ys1_Xu8 ->
case y1_Xu6 of wild2_atz { C# c1_atB ->
case ds_dsT of _ { C# c2_atF ->
case eqChar# c1_atB c2_atF of _ {
False -> go1_XtX ys1_Xu8;
True -> : wild2_atz (go1_XtX ys1_Xu8)
}
}
}
}; } in
go1_XtX y_atu
}; } in
go_ato main_strings)
ds1_dsS
main3
main3 = unpackFoldrCString# "oa" main4 ([])
main2
main2 = showList__ $fShowChar_$cshowList main3 ([])
main1
main1 = \ eta_B1 -> hPutStr2 stdout main2 True eta_B1
main
main = main1 `cast` ...
main10
main10 = \ eta_Xp -> runMainIO1 (main1 `cast` ...) eta_Xp
main
main = main10 `cast` ...
Здесь мы можем видеть, что многое произошло, но, в частности, призыв к concat strings
была поднята до верхнего уровня и развернул полностью в время компиляции, в результате чего main_strings
указывает на объединенный трехэлементный список строк. Это явно разделяется, если go_ato
вызывается с main_strings
.
Перечисления в Haskell - это всего лишь синтаксический сахар для монады списка. Таким образом, ваш вышеуказанный код эквивалентен '" oa ">> = (\ char -> return $ filter (== char) (concat string))'. Следовательно, 'concat strings' будет оцениваться дважды. –
'let strings = concat [" foo "," bar "," baz "] in ...' и ваша проблема решена! – kqr
!! Это зависит от уровня оптимизации в GHC, см. Мой ответ ниже –