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