2011-12-26 2 views
7

Я читаю LYAH, а в главе 9 я нашел любопытную проблему. Автор представляет пример реализации функции «randoms»:Haskell: Реализация «randoms» (a.k.a., Неоднозначная переменная типа)

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))) 

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

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) 

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

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

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

ответ

7

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

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

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

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

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

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

randoms' gen = value : randoms' gen' 
    where (value, gen') = random gen 
+0

Спасибо! Это настолько противоречиво, но вполне понятно. –

+0

Добро пожаловать; Ошибки двусмысленности typeclass могут быть сложными на первый взгляд, но вы должны скоро их повесить :) – ehird

4

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

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

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

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

1

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.

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

+1

Для этого исправления требуется '{- # LANGUAGE ScopedTypeVariables # -}' и явный 'forall' в сигнатуре типа. – ehird

+0

(Я далеко не разбираюсь в деталях того, что является расширением, а что нет). Я удивлен тем, что @ehird, тип переменной 'a' уже находится в сигнатуре типа. Я просто использую это, чтобы четко указать тип выражения 'random gen'. Я бы подумал, что это очень стандартный Хаскелл. –

+0

В стандартном Haskell всякий раз, когда вы используете переменную типа после '::', она находится в новой области. Это очень глупо. – ehird