2014-01-19 3 views
5

Я написал программу Haskell и получил ошибку компиляции, которую я не понимаю.Понимание случая Haskell Тип-Ambiguity

Программа должна:

  • Получить аргументы командной строки
  • Соединить лексемы аргументы обратно к одному String
  • Читать String в тип NestedList данных
  • Свести NestedList в List
  • Распечатать List

К сожалению, это не будет скомпилировано из-за неоднозначности типа.

Haskell Код:

{- 
    Run like this: 
    $ ./prog List [Elem 1, List [Elem 2, List [Elem 3, Elem 4], Elem 5]] 
    Output: [1,2,3,4,5] 
-} 
import System.Environment 
import Data.List 

data NestedList a = Elem a | List [NestedList a] 
    deriving (Read) 

main = do 
    args <- getArgs 
    print . flatten . read $ intercalate " " args 

flatten :: NestedList a -> [a] 
flatten (Elem x) = [x] 
flatten (List x) = concatMap flatten x 

Compile Error:

prog.hs:8:21: 
    Ambiguous type variable `a0' in the constraints: 
     (Read a0) arising from a use of `read' at prog.hs:8:21-24 
     (Show a0) arising from a use of `print' at prog.hs:8:3-7 
    Probable fix: add a type signature that fixes these type variable(s) 
    In the second argument of `(.)', namely `read' 
    In the second argument of `(.)', namely `flatten . read' 
    In the expression: print . flatten . read 

Может кто-то помочь мне понять, как/почему существует тип неоднозначность и как я могу сделать код однозначен.

+0

Какой тип, по вашему мнению, должен был найти GHC? – misterbee

ответ

7

Неоднозначные типы встречаются в Haskell всякий раз, когда переменная типа исчезает из-за применения функции. Обычный, read/show. Вот проблема:

Давайте попробуем прочитать строку, эта операция имеет тип

read :: Read a => String -> a 

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

read "()" :: Read a => a 

Другими словами, система типов еще не смогла выбрать конкретный тип - она ​​просто знает, что независимо от ответа он должен быть Read способен.

Проблема заключается в том, что если мы вернемся вокруг и показать это сразу мы применяем функцию

show :: Show a => a -> String 

который также не в полной мере определить тип a. Объединение их дает нам

show (read "()") :: String 

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

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

asTypeOf :: a -> a -> a 
asTypeOf = const 

, который гарантирует, что первые и вторые аргументы имеют одинаковый тип.

> show (read "()" `asTypeOf`()) :: String 
"()" 

В вашем конкретном примере вы должны определить, что a в NestedList a. Легкий способ сделать это - явно указать тип flatten как конкретный тип.

print . (flatten :: NestedList Int -> [Int]) . read $ concat args 
+1

Спасибо за замечательное объяснение! Я удалил тип неоднозначности, и он работает, как ожидалось! –

+0

Рад, что я мог помочь! –

4

Это классическая проблема. «Ad hoc» полиморфизм типов классов делает вывод типа неполным, и вас просто укусили. Давайте посмотрим на куски.

read :: Read x => String -> x 
flatten :: NestedList a -> [a] 
print :: Show y => y -> IO() 

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

Read a => Read (NestedList a) 
Show a => Show (NestedList a) 
Read a => Read [a] 
Show a => Show [a] 

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

print .  flatten    . read 

     y = [a]   NestedList a = x 

Это означает, что нам нужно

Show [a]      Read (NestedList a) 

и, таким образом,

Show a      Read a 

и мы использовали всю нашу информацию без определения a, и, следовательно, соответствующие Read и Show экземпляров.

Как Дж. Абрахамсон уже предложил, вам необходимо сделать что-то, что определяет a. Есть много способов сделать это. Я предпочитаю аннотации типа писать странные термины, единственная цель которых - сделать тип более очевидным. Во-вторых, предложение дать тип одному из компонентов в составе, но я бы, вероятно, выбрал (read :: String -> NestedList Int), так как это операция, которая вводит двузначную типизированную вещь.

+0

Мне больше нравилось 'asTypeOf', пока я не начал называть себя' (\ 'asTypeOf \' (undefined :: Etc)) '. Как бы то ни было, мне пришлось изменить свой пример с «read» 3 «' to 'read»() «', чтобы заставить его работать. Все сказанные, аннотации типа - босс. –

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