Почему компилятор haskell может вывести этот тип, но ghci не может?

Я работаю через следующие книги чтобы узнать Haskell-глядя, в частности, на главу о случайность:

я запускаю следующее как файл three-coins.hs:

import System.Random 

threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)

main = print ( threeCoins (mkStdGen 21) )

который я затем выполняю с runhaskell three-coins.hs и получить результат, похожий на:

(True,True,True)

теперь они делают точку в примечаниях:

обратите внимание, что мы не должны делать random gen :: (Bool, StdGen). Это потому, что мы уже указали, что нам нужны логические значения в объявлении типа функции. Вот почему Haskell может сделать вывод, что в этом случае нам нужно логическое значение.

это круто.

теперь когда я запускаю это в ghci следующий код:

import System.Random 

:{
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)
:}

Я получаю следующий ответ:

<interactive>:6:9: error:
    • Ambiguous type variable ‘t0’
      prevents the constraint ‘(Random t0)’ from being solved.
    • When checking that the inferred type
        newGen :: forall t. Random t => StdGen
      is as general as its inferred signature
        newGen :: StdGen
      In the expression:
        let
          (firstCoin, newGen) = random gen
          (secondCoin, newGen') = random newGen
          (thirdCoin, newGen'') = random newGen'
        in (firstCoin, secondCoin, thirdCoin)
      In an equation for ‘threeCoins’:
          threeCoins gen
            = let
                (firstCoin, newGen) = random gen
                (secondCoin, newGen') = random newGen
                ....
              in (firstCoin, secondCoin, thirdCoin)

это интересно. Что-то вроде ошибки, о которой нас предупреждали в книге.

поэтому, если мы изменим код, чтобы поместить подсказки типа в:

import System.Random 

:{
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen :: (Bool, StdGen)
        (secondCoin, newGen') = random newGen :: (Bool, StdGen)
        (thirdCoin, newGen'') = random newGen' :: (Bool, StdGen)
    in  (firstCoin, secondCoin, thirdCoin)
:}

это отлично работает - и мы можем проверить это следующим образом:

threeCoins (mkStdGen 21) 

и получаем такой результат

(True,True,True)

Да - это сработало. Таким образом, компилятор Haskell может вывести из типа, который мы предоставили, что мы хотим логическое значение, но ghci не может.

мой вопрос: почему компилятор haskell может вывести этот тип, но ghci не может?

1 ответов


как уже прокомментировал chi, этот код работает только тогда, когда ограничение мономорфности это. Это ограничение заставляет компилятор выбирать один конкретный тип для любого нефункционального определения, т. е. сигнатуры без переменных типа, таких как a на length :: [a] -> Int. Поэтому (если вы вручную не указали локальную подпись) компилятор везде ищет подсказку, каким может быть этот тип, прежде чем он выберет. В вашем примере, он видит, что firstCoin secondCoin thirdCoin используются в конечном результате, который на верхнем уровне подписи объявляется (Bool, Bool, Bool), поэтому он делает вывод, что все монеты должны иметь тип Bool.

это хорошо и прекрасно в таком простом примере, но в современном Haskell вам очень часто нужны значения более общие, поэтому вы можете использовать их в нескольких контекстах с разными типами или в качестве аргументов для функции ранга 2. Вы всегда можете достичь этого, давая явные подписи, но особенно в GHCi это неудобно (это регулярно под названием"боялся ограничение Мономорфизма"), поэтому пару версий назад было решено отключить его по умолчанию в GHCi.

принципиально, firstCoin secondCoin thirdCoin etc. также может быть более общим, чем Bool: random в конце концов способен давать случайные значения любой соответствующий тип (т. е. любой тип, который имеет Random экземпляр). Таким образом, в принципе, локальные определения могут иметь полиморфный тип, такой:

threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let firstCoin, secondCoin, thirdCoin :: Random r => r
        (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)

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

{-# LANGUAGE NoMonomorphismRestriction #-}

сверху.

проблема в том, что ваш код фактически не работает с этими общими локальными подписями. Причина немного связана, это в основном то, что информация о типе r переменная должна быть распространена обратно в кортеж, прежде чем ее можно будет использовать в random генератор, и по причинам, которые я сейчас тоже не понимаю, система типа Хиндли-Милнера не может этого сделать.

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

import System.Random 
import Data.Random 

threeCoins :: RVar (Bool, Bool, Bool)  
threeCoins = do   
    firstCoin <- uniform False True
    secondCoin <- uniform False True
    thirdCoin <- uniform False True
    return (firstCoin, secondCoin, thirdCoin)

main = print . sampleState threeCoins $ mkStdGen 21

который работает с или без ограничения момоморфизма, потому что firstCoin secondCoin thirdCoin теперь выходите из монадической связи,что всегда мономорфные.

кстати, поскольку вы находитесь в монаде, вы можете использовать стандартные комбинаторы и, таким образом, легко сократить его до

import Control.Monad (replicateM)

threeCoins :: RVar (Bool, Bool, Bool)  
threeCoins = do   
    [firstCoin,secondCoin,thirdCoin] <- replicateM 3 $ uniform False True
    return (firstCoin, secondCoin, thirdCoin)