Сделайте Haskell обязательным

Примечание: это упражнение, и я пытаюсь понять, как все работает.

Я пытаюсь сделать что-то подобное в Haskell:

f :: Integer -> Integer
f n = def $ do
  i      <- var n
  while i (>0) $ do
    i      -= lit 1
  return i
-- for now, my program returns n 

Ниже моя программа. На данный момент я не могу понять, почему это не работает, но почему-то переменные не меняются.

import Control.Monad.State


data Lit a = Lit a
type Variable a = State Integer a


def :: Variable (Lit a) -> a
def (State f) = fromLit . fst . f $ 0 

fromLit :: Lit a -> a
fromLit (Lit a) = a

lit :: a -> Lit a
lit l = Lit l

var :: a -> Variable (Lit a)
var v = State $ \x -> (lit v, x)

while :: Lit a -> (a -> Bool) -> Variable () -> Variable ()
while (Lit r) cond act = do
                 if cond r then do
                          _ <- act
                          while (Lit r) cond act
                    -- or act >> while (Lit r) cond act
                 else return ()


op :: (Integer -> Integer -> Integer) -> Lit Integer -> Lit Integer -> Variable ()
op f (Lit a) (Lit b) = State $ \n -> ((), if n == a then f a b else n)
-- that is, if the state is (a) change it to (f a b), else don't change it

(+=) = op (+)
(-=) = op (-)
(*=) = op (*)

Пожалуйста, помогите мне понять, что здесь не так и как улучшить код. Спасибо.


person arryn    schedule 22.11.2019    source источник
comment
Я не согласен с отрицательными отзывами, и я думаю, что это хорошее упражнение по моделированию императивных конструкций (ну, пока вы не используете этот стиль для повседневного кода :-)). В любом случае, State Integer не является подходящей монадой для этого, если только ваши императивные программы не будут использовать только одну переменную. Вам, вероятно, понадобится State (VariableName -> Integer) или аналогичный Map для большей эффективности. Для максимальной эффективности я бы использовал вместо этого ST s.   -  person chi    schedule 22.11.2019
comment
@chi, что здесь VariableName?   -  person arryn    schedule 22.11.2019
comment
Это может быть String или любой другой тип, который вы хотите использовать для имен переменных. Можно также использовать Int, в принципе. Если хотите, вместо этого можно назвать местоположения или ссылки, поскольку вы, похоже, создаете их с помощью var.   -  person chi    schedule 22.11.2019


Ответы (1)


Само собой разумеется, что это глупо делать в Haskell; но я полностью за попытки делать глупости, если это поможет вам чему-то научиться.

Я думаю, что вам, вероятно, следует вернуться к более простой задаче и немного больше поэкспериментировать с монадой State, прежде чем пытаться что-то сложное, подобное этому; из фрагментов кода, которые вы опубликовали, я подозреваю, что ваша интуиция немного неверна.

Конечно, вы МОЖЕТЕ делать такие вещи, но сложно обрабатывать переменные «произвольного» типа в монаде State (которая должна иметь фиксированный тип для состояния). Вы можете сделать это относительно безопасным способом, используя Dynamic и Proxy; но я думаю, что это все еще может быть немного вне досягаемости.

Мы начнем с хранения переменных только типа Int. Я не уверен, какую монаду State вы используете, но вместо этого я буду использовать монаду из mtl.

import Control.Monad.State
import qualified Data.Map as M
import Data.Maybe

data Var = Var Int
  deriving (Show, Eq, Ord)

data Env = Env {freshVar :: Int, vars :: M.Map Var Int}
  deriving Show

type Imperative a = State Env a

Я определил тип Env, который отслеживает все переменные, которые мы «создали», а также генератор «свежих имен», который представляет собой просто Int, который продолжает считать вверх по мере того, как мы определяем переменные.

lit :: Int -> Imperative Var
lit n = do
    varID <- gets freshVar
    let newVar = Var varID
    modify (\s -> s{freshVar=n+1, vars=(M.insert newVar n (vars s))})
    return newVar

Этот код создает новую «переменную» из буквального числа, он делает это, получая новое имя переменной из среды, оборачивая его в конструктор, а затем сохраняя его в среде с предоставленным значением. Обратите внимание, что мы увеличиваем число freshVar, чтобы получить другой идентификатор переменной для следующего литерала.

getVar :: Var -> Imperative Int
getVar v = gets (fromJust . M.lookup v . vars)

setVar :: Var -> Int -> Imperative ()
setVar v n = modify (\s -> s{vars=M.insert v n (vars s)})

Это некоторые помощники, которые ищут переменные или устанавливают переменные в нашей Карте переменных. getVar использует fromJust, что в целом небезопасно; но если вы определяете только новые переменные с «освещенным», тогда все работает нормально.

op :: (Int -> Int -> Int) -> Var -> Var -> Imperative ()
op f aVar bVar = do
    a <- getVar aVar
    b <- getVar bVar
    setVar aVar (f a b)

(+=) = op (+)
(-=) = op (-)
(*=) = op (*)

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

Теперь мы можем определить while

while :: Imperative Bool -> Imperative () -> Imperative ()
while cond act = do
    continue <- cond
    if continue then act >> while cond act
                else return ()

Мы можем принять любой императивный оператор, который возвращает логическое значение в качестве условия; при необходимости пользователь может просмотреть состояние переменных в операторе. Мы просто запускаем оператор, если мы хотим продолжить, мы act и затем рекурсивно, в противном случае мы возвращаемся.

f :: Int -> Int
f n = run $ do
  i <- lit n
  while ((>0) <$> getVar i) $ do
    one <- lit 1
    i -= one
  getVar i

Это немного многословно (мы могли бы упростить его, но это сделало бы комбинаторы более сложными). Мы определяем i как новую переменную со значением n, затем проверяем, больше ли оно 0 в условии. В теле цикла мы определяем переменную со значением 1, затем вычитаем ее из переменной i.

После завершения цикла мы проверяем значение i

run :: Imperative a -> a
run m = evalState m (Env 0 M.empty)

Вот функция run, которую мы использовали выше, которая просто запускает состояние без определенных переменных.

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

import Debug.Trace
f :: Int -> Int
f n = run $ do
  i <- lit n
  while ((>0) <$> getVar i) $ do
    getVar i >>= traceShowM
    one <- lit 1
    i -= one
  getVar i

>>> f 10
10
9
8
7
6
5
4
3
2
1
0
person Chris Penner    schedule 22.11.2019