LINUX.ORG.RU

Типизация локальных объявлений в Хаскеле


0

0

Доброго времени суток!

Есть такой код:
isChar :: Char -> Bool
isChar c = (c /= '|') && (c /= '*') && (c /= '(') && (c /= ')')

data Tree a = Empty | Leaf a | Node (Tree a) (Tree a) (Tree a)
    deriving (Eq, Show)

rep :: Parser Char (Tree Char)
rep = expr
    where   expr        = term <*> rest_expr <@ (\(t, r) -> r t)

            rest_expr   :: Parser Char (Tree Char -> Tree Char)
            rest_expr   = (sym_or *> term <@ (flip re_or)) <*> rest_expr <@ (\(n, r) -> r . n) <|> f_epsilon

            term        :: Parser Char (Tree Char)
            term        = sqn <*> rest_term <@ (\(s, t) -> t s)

            rest_term   :: Parser Char (Tree Char -> Tree Char)
            rest_term   = sqn <*> rest_term <@ (\(s, t) -> t . ((flip re_cat) s)) <|> f_epsilon

            sqn         :: Parser Char (Tree Char)
            sqn         = factor <*> opt_many <@ (\(f, o) -> o f)

            opt_many    :: Parser Char (Tree Char -> Tree Char)
            opt_many    = sym_any <@ (\_ -> re_any) <|> f_epsilon

            factor      :: Parser Char (Tree Char)
            factor      = letter <|> (parenthesized expr) 

            letter      :: Parser Char (Tree Char)
            letter      = satisfy isChar <@ re_sym

            sym_any     = symbol '*'
            sym_or      = symbol '|' 

            f_epsilon   :: Parser Char (b -> b)
            f_epsilon   = epsilon <@ (\_ -> id)

            re_sym s   = Node (Leaf s) Empty Empty
            re_cat r s = Node r s Empty
            re_or r s  = Node r (Leaf '|') s
            re_any s   = Node s (Leaf '*') Empty

Он работает. Определения использованных нестандартных функций (типа <*>) есть тут: http://ru.wikibooks.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0
%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BF%D0%B0%D1%80%D1%81%D0%B5%D1%80%D1%8B

Хочу обобщить функцию rep, параметризировав ее функциями re_sym,
re_cat, re_or  re_any:
rep :: (Char -> a) -> (a -> a -> a) -> (a -> a -> a) -> (a -> a) -> Parser Char a
rep re_sym re_cat re_or re_any = expr
    where   expr        = term <*> rest_expr <@ (\(t, r) -> r t)

            rest_expr   :: Parser Char (a -> a)
            rest_expr   = (sym_or *> term <@ (flip re_or)) <*> rest_expr <@ (\(n, r) -> r . n) <|> f_epsilon
    
            term        :: Parser Char a
            term        = sqn <*> rest_term <@ (\(s, t) -> t s)

            rest_term   :: Parser Char (a -> a)
            rest_term   = sqn <*> rest_term <@ (\(s, t) -> t . ((flip re_cat) s)) <|> f_epsilon

            sqn         :: Parser Char a
            sqn         = factor <*> opt_many <@ (\(f, o) -> o f)

            opt_many    :: Parser Char (a -> a)
            opt_many    = sym_any <@ (\_ -> re_any) <|> f_epsilon

            factor      :: Parser Char a
            factor      = letter <|> (parenthesized expr)

            letter      :: Parser Char a
            letter      = satisfy isChar <@ re_sym

            sym_any     = symbol '*'
            sym_or      = symbol '|'

            f_epsilon   :: Parser Char (b -> b)
            f_epsilon   = epsilon <@ (\_ -> id)

Такой код не работает, Hugs выдает:
ERROR "lex_analyze.hs":117 - Inferred type is not general enough
*** Expression    : letter
*** Expected type : Parser Char a
*** Inferred type : Parser Char _5

Если убрать объявления типов локальных определений, то все работает. Почему так происходит?
anonymous

Потому что переменная "a" из объявления типа rep не запоминается. В результате Hugs считает, что ты пытаешься ввести переменную letter, полностью полиморфную по a - в то время, как ты имеешь в виду КОНКРЕТНЫЙ тип a, тот, который участвует в типе функции re_sym. Решение элементарное и ты его уже нашёл - не дави на интерпретатор, дай ему вывести тип самостоятельно.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Ясно, спасибо. Но все-таки явное указание типа промежуточных функций здорово помогает делать меньше ошибок в них и делает их более понятными. Как в таких случаях принято поступать? Выносить соответствующий код в отдельные функции?

anonymous
()
Ответ на: комментарий от anonymous

Если в промежуточных функциях много кода - выносить. Если мало - оставлять вывод типов компилеру. Есть ещё фокусы типа where... _ = f x `asTypeOf` x - при этом гарантируется, что функция f будет типа a -> a или более общего (но такой подход не рекомендую). Да, и ещё, для понятности: пиши комментарии.

Miguel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.