Комбинатор неподвижной точки в Haskell

комбинатор с фиксированной точкой не всегда дает правильный ответ с учетом определения:

fix f = f (fix f)

следующий код не прекращает:

fix (x->x*x) 0

конечно, fix не всегда может дать правильный ответ, но мне было интересно, можно ли это улучшить?

конечно, для приведенного выше примера можно реализовать некоторое исправление, которое выглядит как

fix f x | f x == f (f x)  = f x
        | otherwise       = fix f (f x)

и дает правильный выход.

в чем причина выше определение (или что-то еще лучше, так как это только функция дескриптора с 1 параметром) не используется вместо этого?

5 ответов


fixed point combinator находит наименее определенную фиксированную точку функции, которая в вашем случае ⊥ (non-termination действительно является неопределенным значением).

вы можете увидеть, что в вашем случае

(\x -> x * x) ⊥ = ⊥

то есть действительно является фиксированной точкой \x -> x * x.

Что касается почему fix определено таким образом: основная точка fix - разрешить использовать анонимная рекурсия и для этого вам не нужно более сложное определение.


ваш пример даже не typecheck:

Prelude> fix (\x->x*x) 0

<interactive>:1:11:
    No instance for (Num (a0 -> t0))
      arising from a use of `*'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the expression: x * x
    In the first argument of `fix', namely `(\ x -> x * x)'
    In the expression: fix (\ x -> x * x) 0

и что дает ключ к тому, почему он не работает, как вы ожидаете. The x в вашей анонимной функции должна быть функция, а не число. Причина этого, как предполагает Витус, в том, что комбинатор fixpoint-это способ записи рекурсии без фактического написания рекурсии. Общая идея заключается в том, что рекурсивное определение типа

f x = if x == 0 then 1 else x * f (x-1)

можно записать как

f    = fix (\f' x -> if x == 0  then 1 else x * f' (x-1))

ваш пример

fix (\x->x*x) 0

таким образом соответствует выражению

let x = x*x in x 0

что не имеет смысла.


я не совсем квалифицирован, чтобы говорить о том, что такое" комбинатор фиксированной точки "или что такое" наименее фиксированная точка", но можно использовать fix - esque техника для примерное определенные функции.

перевод Scala по примеру раздел 4.4 Хаскеллу:

sqrt' :: Double -> Double
sqrt' x = sqrtIter 1.0
  where sqrtIter guess | isGoodEnough guess = guess
                       | otherwise          = sqrtIter (improve guess)
        improve guess = (guess + x / guess) / 2
        isGoodEnough guess = abs (guess * guess - x) < 0.001

эта функция работает путем многократного " улучшения "догадки, пока мы не определим, что она"достаточно хороша". Эта картина может быть отвлеченно:

myFix :: (a -> a)       -- "improve" the guess
      -> (a -> Bool)    -- determine if a guess is "good enough"
      -> a              -- starting guess
      -> a
fixApprox improve isGoodEnough startGuess = iter startGuess
  where iter guess | isGoodEnough guess = guess
                   | otherwise          = iter (improve guess)

sqrt'' :: Double -> Double
sqrt'' x = myFix improve isGoodEnough 1.0
  where improve guess = (guess + x / guess) / 2
        isGoodEnough guess = abs (guess * guess - x) < 0.001

см. также Scala на примере раздела 5.3. fixApprox может использоваться для аппроксимации фиксированной точки improve функция передана в него. Он неоднократно вызывает improve на входе до выхода isGoodEnough.

в самом деле, вы можете использовать myFix не только для приближений, но и для точных ответов.

primeAfter :: Int -> Int
primeAfter n = myFix improve isPrime (succ n)
  where improve = succ
        isPrime x = null [z | z <- [2..pred x], x `rem` z == 0]

это довольно глупый способ генерации простых чисел, но он иллюстрирует суть. Хм...теперь я удивляюсь...делает что-то вроде myFix уже существует? Остановка...Время Hoogle!

Hoogling (a -> a) -> (a -> Bool) -> a -> a самый первый хит until.

until p f дает результат применения f до p держит.

Ну вот и все. Как оказалось,myFix = flip until.


вы, вероятно, имели в виду iterate:

*Main> take 8 $ iterate (^2) (0.0 ::Float)
[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
*Main> take 8 $ iterate (^2) (0.001 ::Float)
[1.0e-3,1.0000001e-6,1.0000002e-12,1.0000004e-24,0.0,0.0,0.0,0.0]

*Main> take 8 $ iterate (^2) (0.999 ::Float)
[0.999,0.99800104,0.9960061,0.9920281,0.9841198,0.96849173,0.93797624,0.8797994]
*Main> take 8 $ iterate (^2) (1.0 ::Float)
[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
*Main> take 8 $ iterate (^2) (1.001 ::Float)
[1.001,1.002001,1.0040061,1.0080284,1.0161213,1.0325024,1.0660613,1.1364866]

здесь у вас есть вся история выполнения, явно доступная для вашего анализа. Ты можешь!--8-->попытка для обнаружения фиксированной точки с

fixed f from = snd . head 
                   . until ((< 1e-16).abs.uncurry (-).head) tail 
               $ _S zip tail history
  where history = iterate f from
        _S f g x = f x (g x)

а то

*Main> fixed (^2) (0.999 :: Float)
0.0

но fixed (^2) (1.001 :: Float) будет петля бесконечно, поэтому вам нужно будет разработать отдельное тестирование для сходимости, и даже тогда обнаружение репеллентных фиксированных точек, таких как 1.0, потребует более тщательного исследования.


вы не можете определить fix Как вы упомянули с f x может даже не быть сопоставимым. Например, рассмотрим пример ниже:

myFix f x | f x == f (f x)  = f x
          | otherwise       = myFix f (f x)

addG f a b =
  if a == 0 then
    b
  else
    f (a - 1) (b + 1)

add = fix addG -- Works as expected.
-- addM = myFix addG (Compile error)