Может ли приложение частичной функции быть ленивым в том смысле, что оно не повторяет одну и ту же частичную оценку дважды?

Почему частичная оценка не строго оценивается в момент частичного применения и почему она переоценивается более одного раза? Как вопрос о хипстере, примеры из Scala и Haskell (на мгновение я думал, что Haskell будет вести себя по-другому):

В Scala:

scala> def f(x: Int)(y: Int) = {println("inside"); x * y}
f: (x: Int)(y: Int)Int

scala> val f2 = f(2) _ 
f2: Int => Int = <function1>

scala> f2(3)
inside                     //internals of f calculated for the first time
res7: Int = 6

scala> f2(7)
inside                     //internals of f recalculated
res8: Int = 14

В Haskell:

  Prelude> import Debug.Trace

  Prelude Debug.Trace> let f x y = trace "inside" x * y

  Prelude Debug.Trace> let f2 = f 2

  Prelude Debug.Trace> f2 3
  inside                   //internals of f calculated for the first time
  6

  Prelude Debug.Trace> f2 3
  inside                   //internals of f recalculated
  6

  Prelude Debug.Trace> f2 7
  inside                   //internals of f recalculated
  14

Я знаю, что можно переопределить f, чтобы вернуть функцию, как в коде ниже, но было бы интересно, чтобы функции, которые действительно были частично оценены даже до того, как они были полностью оценены:

scala> def f(x: Int) = {println("inside"); (y:Int) => x * y}
f: (x: Int)Int => Int

scala> val f2 = f(2)
inside                           //internals of f calculated only this time
f2: Int => Int = <function1>

scala> f2(3)
res12: Int = 6

scala> f2(7)
res13: Int = 14

Ответ 1

В Хаскелле, да. Вы должны быть осторожны в отношении того, какие лямбда-выражения определяют ваше определение функции. Например

Prelude> import Debug.Trace
Prelude Debug.Trace> let f x = let x1 = trace "inside" x in \y -> x1 * y
Prelude Debug.Trace> let f2 = f 2
Prelude Debug.Trace> f2 3
inside
6
Prelude Debug.Trace> f2 3
6
Prelude Debug.Trace> f2 7
14

Дополнительный пример, предложенный Сасса Н.Ф. Обратите внимание, что в g замыкание trace "inside" succ воссоздается каждым вызовом, тогда как в h замыкание привязано к h раз и навсегда. Это сокращение не сохраняет эксплуатационную семантику в Haskell!

Prelude Debug.Trace> let g = \x -> (trace "inside" succ) x :: Int
Prelude Debug.Trace> g 1
inside
2
Prelude Debug.Trace> g 2
inside
3
Prelude Debug.Trace> g 3
inside
4
Prelude Debug.Trace> let h = trace "inside" succ :: Int -> Int
Prelude Debug.Trace> h 1
inside
2
Prelude Debug.Trace> h 2
3
Prelude Debug.Trace> h 3
4