Haskell: Реализовать "случайности" (a.k.a., переменная типа Ambiguous) - PullRequest
7 голосов
/ 27 декабря 2011

Я читаю через ЛЯХ, и в главе 9 я обнаружил любопытную проблему. Автор приводит пример реализации функции "случайных чисел":

randoms' :: (RandomGen g, Random a) => g -> [a]
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen

Ну, это прекрасно компилируется. Но если я изменю вторую строку на:

randoms' gen = (fst (random gen)) : (randoms' (snd (random gen)))

Этот файл сообщает об ошибке при загрузке:

IOlesson.hs:4:52:
    Ambiguous type variable `a' in the constraint:
      `Random a' arising from a use of `random' at IOlesson.hs:4:52-61
    Probable fix: add a type signature that fixes these type variable(s)
Failed, modules loaded: none.

Если я изменю эту строку на:

randoms' gen = (fst (random gen)) : (randoms' gen)

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

Я озадачен: что такого отличного в версии Миран и моей версии?

Спасибо за любые идеи!

Ответы [ 3 ]

7 голосов
/ 27 декабря 2011

Проблема в том, что random принимает любой экземпляр RandGen и возвращает случайное значение и новый генератор того же типа.Но случайное значение может быть любого типа с экземпляром Random!

random :: (Random a, RandomGen g) => g -> (a, g)

Так что, когда вы вызываете random во второй раз в рекурсии, оно не 'не знаю, какой должен быть тип first !Правда, вас это действительно не волнует (в конце концов, вы выбрасываете snd), но выбор a может повлиять на поведение из random.Таким образом, чтобы устранить неоднозначность, вы должны сказать GHC, кем вы хотите a .Самый простой способ - переписать ваше определение следующим образом:

randoms' gen = let (value, gen') = random gen in value : randoms' gen'

Поскольку вы используете value как часть полученного списка, вы должны иметь тот же тип, что и a вподпись вашего типа - тип элемента результирующего списка.Неоднозначность разрешается, и повторяющиеся вычисления следующего случайного числа исключаются для загрузки.Есть способы устранить это более прямо (сохраняя дублирующиеся вычисления), но они либо уродливы и сбивают с толку, либо включают языковые расширения.К счастью, вы не должны сталкиваться с этим очень часто, и когда вы делаете, такой метод должен работать для устранения неоднозначности.

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

randoms' gen = value : randoms' gen'
  where (value, gen') = random gen
4 голосов
/ 27 декабря 2011

Рассмотрим тип random:

random :: (RandomGen g, Random a) => g -> (a, g)

Результирующий кортеж состоит из значения любого типа, являющегося экземпляром Random, и обновленного значения RNG. Важной частью является «любой экземпляр»: ничто не требует использования обоих символов random для получения значения одного типа.

В fst (random gen) проблем нет, потому что генерируемое значение - это тот тип, который необходим всей функции; однако в snd (random gen) случайное значение выбрасывается, поэтому совершенно неизвестно, каким оно должно быть. Не зная типа, Haskell не может выбрать подходящий экземпляр Random для использования, поэтому вы видите неоднозначную ошибку типа.

1 голос
/ 27 декабря 2011

random имеет тип: RandomGen g => g -> (a, g)

и, следовательно, snd (random gen) относится только к типу g -> g. И тогда он не знает, что такое a. Для каждого типа данных, который вы, возможно, захотите сгенерировать, есть разные random, но в этом случае компилятор не знает, хотите ли вы random :: g -> (Int,g) или random :: g->(Char,g) или что-то еще.

Это объясняет, почему (value, newGen) = random gen работает. Это помогает компилятору связать воедино знания о том, что такое a. value должен иметь тип a и, следовательно, он может выводить тип random gen.

( Отредактировано : я удалил неверную попытку исправить ошибку. Просто придерживайтесь исходного кода в вопросе!)

...