2012-06-21 5 views
10

Я пытался ответить another question о полиморфизме против совместного использования, когда я наткнулся на это странное поведение.NoMonomorphismRestriction помогает сохранить общий доступ?

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

> let fib :: Num a => [a]; fib = 1 : 1 : zipWith (+) fib (tail fib) 
> fib !! 30 
1346269 
(5.63 secs, 604992600 bytes) 

С другой стороны, если я пытаюсь добиться того же, опуская тип подписи и отключив ограничение мономорфизма, моя постоянная неожиданно становится общей!

> :set -XNoMonomorphismRestriction 
> let fib = 1 : 1 : zipWith (+) fib (tail fib) 
> :t fib 
fib :: Num a => [a] 
> fib !! 50 
20365011074 
(0.00 secs, 2110136 bytes) 

Почему?

Ugh ... Скомпилированный с оптимизацией, он быстр даже с ограничением мономорфизма.

+3

В стороне: рассуждение о производительности в ghci немного странно - его а) примерно в 30 раз медленнее, чем сам ghc, и б) любой код реального мира будет использовать оптимизацию, поэтому уроки, извлеченные в ghci, не будут полезны. –

ответ

11

Давая явную сигнатуру типа, предотвратить GHC от делать определенные предположения о вашем коде. Я покажу пример (взятый из этого question):

foo (x:y:_) = x == y 
foo [_]  = foo [] 
foo []  = False 

Согласно GHCi, тип этой функции Eq a => [a] -> Bool, как можно было ожидать. Однако, если вы объявите foo с этой подписью, вы получите ошибку «неоднозначной переменной типа».

Причина, по которой эта функция работает только без сигнатуры типа, связана с тем, как работает проверка типов в GHC. Когда вы опускаете подпись типа, предполагается, что foo имеет монотип [a] -> Bool для определенного фиксированного типа a. Как только вы закончите набирать группу привязки, вы обобщаете типы. Вот где вы получаете forall a. ....

С другой стороны, когда вы объявляете полиморфный тип подписи, вы явно указать, что foo является полиморфным (и, таким образом, тип [] не должен совпадать с типом первого аргумента) и стрелы, вы получите неоднозначный тип переменная.

Теперь, зная об этом, давайте сравним ядро:

fib = 0:1:zipWith (+) fib (tail fib) 
----- 
fib :: forall a. Num a => [a] 
[GblId, Arity=1] 
fib = 
    \ (@ a) ($dNum :: Num a) -> 
    letrec { 
     fib1 [Occ=LoopBreaker] :: [a] 
     [LclId] 
     fib1 = 
     break<3>() 
     : @ a 
      (fromInteger @ a $dNum (__integer 0)) 
      (break<2>() 
      : @ a 
      (fromInteger @ a $dNum (__integer 1)) 
      (break<1>() 
       zipWith 
       @ a @ a @ a (+ @ a $dNum) fib1 (break<0>() tail @ a fib1))); } in 
    fib1 

И второй:

fib :: Num a => [a] 
fib = 0:1:zipWith (+) fib (tail fib) 
----- 
Rec { 
fib [Occ=LoopBreaker] :: forall a. Num a => [a] 
[GblId, Arity=1] 
fib = 
    \ (@ a) ($dNum :: Num a) -> 
    break<3>() 
    : @ a 
     (fromInteger @ a $dNum (__integer 0)) 
     (break<2>() 
     : @ a 
     (fromInteger @ a $dNum (__integer 1)) 
     (break<1>() 
      zipWith 
      @ a 
      @ a 
      @ a 
      (+ @ a $dNum) 
      (fib @ a $dNum) 
      (break<0>() tail @ a (fib @ a $dNum)))) 
end Rec } 

С явной сигнатуры типа, как и с foo выше, GHC должен относиться fib, как потенциально полиморфно рекурсивное значение. Мы могли передать несколько разных Num словарь на fib в zipWith (+) fib ..., и в этот момент нам пришлось бы отбросить большую часть списка, так как разные Num означает разные (+). Конечно, после компиляции с оптимизацией GHC замечает, что словарь Num никогда не изменяется во время «рекурсивных вызовов» и оптимизирует его.

В ядре выше вы можете увидеть, что GHC действительно дает fib словарь Num (названный $dNum) снова и снова.

Поскольку fib без типа подписи принимались мономорфным перед обобщением всей группы связывания было закончено, то fib подразделов получили точно такой же тип, как и весь fib. Благодаря этому, fib выглядит следующим образом:

{-# LANGUAGE ScopedTypeVariables #-} 
fib :: forall a. Num a => [a] 
fib = fib' 
    where 
    fib' :: [a] 
    fib' = 0:1:zipWith (+) fib' (tail fib') 

И потому, что тип остается фиксированной, вы можете использовать только один словарь заданный при запуске.

+1

Ага! Это многое объясняет. Спасибо! – Rotsor

4

Здесь вы используете fib с аргументами того же типа в обоих случаях, а ghc достаточно умен, чтобы увидеть это и выполнить общий доступ.

Теперь, если вы использовали функцию, в которой ее можно вызвать с помощью различных аргументов типа, а дефолт привел к тому, что один из них сильно отличается от другого, тогда ограничение ограничения мономорфизма укусит вас.

Рассмотрим использование термина x = 2 + 2 полиморфно в двух контекстах без ограничения мономорфизма, где в одном контексте вы show (div x 2) и в другом использовании show (x/2), В одной установки вы получаете Integral и Show ограничения, которые заставляют вас по умолчанию Integer, в в другом вы получаете ограничение Fractional и Show, которое по умолчанию вам соответствует Double, поэтому результат вычисления не используется, поскольку вы работаете с полиморфным термином, применяемым к двум различным типам. При включенном ограничении мономорфизма он старается по умолчанию отключить что-то как Интегральное, так и Дробное и терпит неудачу.

Имейте в виду его Tricker, чтобы получить все это, чтобы стрелять в эти дни с пусть не обобщаю, и т.д.

 Смежные вопросы

  • Нет связанных вопросов^_^