Взаимно рекурсивный оценщик в Хаскеле - PullRequest
2 голосов
/ 19 августа 2010

Обновление: Я добавил ответ , который описывает мое окончательное решение (подсказка: одного типа Expr недостаточно).


Я пишу оценщик для небольшого языка выражений, но я застрял на конструкции LetRec.

Это язык:

type Var = String
type Binds = [(Var, Expr)]

data Expr
  = Var     Var
  | Lam     Var    Expr
  | App     Expr   Expr
  | Con     Int
  | Sub     Expr   Expr
  | If      Expr   Expr  Expr
  | Let     Var    Expr  Expr
  | LetRec  Binds  Expr
  deriving (Show, Eq)

И этот оценщик пока:

data Value
  = ValInt   Int
  | ValFun   Env   Var  Expr
  deriving (Show, Eq)

type Env = [(Var, Value)]

eval :: Env -> Expr -> Either String Value
eval env (Var x)       = maybe (throwError  $ x ++ " not found")
                               return
                               (lookup x env)
eval env (Lam x e)     = return $ ValFun env x e
eval env (App e1 e2)   = do
                         v1 <- eval env e1
                         v2 <- eval env e2
                         case v1 of
                           ValFun env1 x e -> eval ((x, v2):env1) e
                           _ -> throwError "First arg to App not a function"
eval _   (Con x)       = return $ ValInt x
eval env (Sub e1 e2)   = do
                         v1 <- eval env e1
                         v2 <- eval env e2
                         case (v1, v2) of
                           (ValInt x, ValInt y) -> return $ ValInt (x - y)
                           _ -> throwError "Both args to Sub must be ints"
eval env (If p t f)    = do 
                         v1 <- eval env p
                         case v1 of
                           ValInt x -> if x /= 0
                                       then eval env t
                                       else eval env f
                           _ -> throwError "First arg of If must be an int"
eval env (Let x e1 e2) = do
                         v1 <- eval env e1
                         eval ((x, v1):env) e2
eval env (LetRec bs e) = do
                         env' <- evalBinds
                         eval env' e
  where
    evalBinds = mfix $ \env' -> do
      env'' <- mapM (\(x, e') -> eval env' e' >>= \v -> return (x, v)) bs
      return $ nub (env'' ++ env)

Это моя тестовая функция, которую я хочу оценить:

test3 :: Expr
test3 = LetRec [ ("even", Lam "x" (If (Var "x")
                                      (Var "odd" `App` (Var "x" `Sub` Con 1))
                                      (Con 1)
                                  ))
               , ("odd",  Lam "x" (If (Var "x")
                                      (Var "even" `App` (Var "x" `Sub` Con 1))
                                      (Con 0)
                                  ))
               ]
               (Var "even" `App` Con 5)

EDIT:

На основании ответа Трэвиса и комментария Люка я обновил свой код , чтобы использовать экземпляр MonadFix для монады Error. Предыдущий пример теперь работает отлично! Однако приведенный ниже пример работает неправильно:

test4 :: Expr
test4 = LetRec [ ("x", Con 3)
               , ("y", Var "x")
               ]
               (Con 0)

При оценке этого вычислитель зацикливается, и ничего не происходит. Я предполагаю, что я сделал что-то слишком строгое, но я не уверен, что это такое. Я нарушаю один из законов MonadFix?

Ответы [ 3 ]

4 голосов
/ 20 августа 2010

Когда Хаскелл подбрасывает, это обычно указывает на то, что вы не подумали об основной проблеме вашей проблемы. В этом случае возникает вопрос: какую оценочную модель вы хотите использовать для своего языка? Вызов по стоимости или по запросу?

Ваше представление окружений как [(Var,Value)] предполагает, что вы хотите использовать вызов по значению, поскольку каждый Expr оценивается в Value сразу перед сохранением в среде. Но letrec не подходит, и ваш второй пример показывает!

Кроме того, обратите внимание, что модель оценки языка хоста (Haskell) будет мешать модели оценки языка, который вы хотите реализовать; фактически, это то, что вы в настоящее время используете для своих примеров: несмотря на их назначение, ваши Value s не оцениваются как нормальная форма слабой головы.

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

Edit: Для примера спецификации letrec на языке вызовов по значению, посмотрите Руководство по Ocaml . На простейшем уровне они допускают только правые части, являющиеся лямбда-выражениями, то есть вещи, которые синтаксически известны как значения.

2 голосов
/ 19 августа 2010

Может быть, я что-то упустил, но разве не работает следующее?

eval env (LetRec bs ex) = eval env' ex
  where
    env' = env ++ map (\(v, e) -> (v, eval env' e)) bs

Для вашей обновленной версии: А как насчет следующего подхода? Он работает так, как вам нужно в вашем тестовом примере, и не выбрасывает ошибки в LetRec выражениях:

data Value
  = ValInt Int
  | ValFun EnvWithError Var Expr
  deriving (Show, Eq)

type Env = [(Var, Value)]
type EnvWithError = [(Var, Either String Value)]

eval :: Env -> Expr -> Either String Value
eval = eval' . map (second Right)
  where
    eval' :: EnvWithError -> Expr -> Either String Value
    eval' env (Var x)        = maybe (throwError  $ x ++ " not found")
                                     (join . return)
                                     (lookup x env)
    eval' env (Lam x e)      = return $ ValFun env x e
    eval' env (App e1 e2)    = do
                               v1 <- eval' env e1
                               v2 <- eval' env e2
                               case v1 of
                                 ValFun env1 x e -> eval' ((x, Right v2):env1) e
                                 _ -> throwError "First arg to App not a function"
    eval' _   (Con x)        = return $ ValInt x
    eval' env (Sub e1 e2)    = do
                               v1 <- eval' env e1
                               v2 <- eval' env e2
                               case (v1, v2) of
                                 (ValInt x, ValInt y) -> return $ ValInt (x - y)
                                 _ -> throwError "Both args to Sub must be ints"
    eval' env (If p t f)     = do 
                               v1 <- eval' env p
                               case v1 of
                                 ValInt x -> if x /= 0
                                             then eval' env t
                                             else eval' env f
                                 _ -> throwError "First arg of If must be an int"
    eval' env (Let x e1 e2)  = do
                               v1 <- eval' env e1
                               eval' ((x, Right v1):env) e2
    eval' env (LetRec bs ex) = eval' env' ex
      where
        env' = env ++ map (\(v, e) -> (v, eval' env' e)) bs
1 голос
/ 22 декабря 2010

Отвечая на мой собственный вопрос; Я хотел поделиться окончательным решением, которое мне пришло.

Как правильно заметил Генрих , я не задумывался над тем, какое влияние оказывает порядок оценки.

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

type Var = String
type Binds = [(Var, Val)]

data Val
  = Con Int
  | Lam Var Expr
  deriving (Show, Eq)

data Expr
  = Val Val
  | Var Var
  | App Expr Expr
  | Sub Expr Expr
  | If Expr Expr Expr
  | Let Var Expr Expr
  | LetRec Binds Expr
  deriving (Show, Eq)

Единственная разница с моим исходным типом данных Expr заключается в том, что я вытащил два конструктора (Con и Lam) в их собственный тип данных Val. Тип данных Expr имеет новый конструктор Val, это означает, что значение также является допустимым выражением.

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

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

См. полный код для обновленной функции оценки.

...