Я не знаю, как переназначить переменную в функции.
Например,
elephant = 0
function x = elephant = x
Почему это не работает?
Я не знаю, как переназначить переменную в функции.
Например,
elephant = 0
function x = elephant = x
Почему это не работает?
Haskell - отличный императивный язык, и писать программы, которые могут перенаправить состояние, - это действительно интересная передовая тема! Это определенно не тот подход, который вы хотите прямо сейчас, но возвращайтесь к нему в один прекрасный день 🙂
Требуется немного усилий для определения среды, которая моделирует глобальные изменяемые переменные. Однако, как только вы его повесите, точность типов заканчивается очень удобной.
Мы будем использовать lens и mtl.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad.State
Как мы все знаем, слоны являются целыми числами.
type Elephant = Integer
Вам нужна программа, чье глобальное изменяемое состояние имеет слона. Поэтому сначала определите, что значит иметь слона. Lens
прекрасно отражает это понятие.
class HasElephant a
where
elephant :: Lens' a Elephant
Теперь мы можем определить function
, который присваивает новое значение elephant
.
function :: (MonadState s m, HasElephant s) => Elephant -> m ()
function x =
elephant .= x
Ограничения MonadState s m
и HasElephant s
говорят, что наша программа должна иметь возможность сохранять изменяемое состояние какого-либо типа s
, а тип s
должен иметь слона.
Позвольте также определить программу, которая печатает слона.
printElephant :: (MonadState s m, HasElephant s, MonadIO m) => m ()
printElephant =
use elephant >>= (liftIO . print)
Эта программа выполняет ввод/вывод (печать), поэтому у нас есть дополнительное ограничение MonadIO m
, в котором говорится, что наш тип программы m
должен иметь возможность делать I/O.
Африканский лесной слон (Loxodonta cyclotis) - обитающий в лесу вид слона, найденный в бассейне Конго.
data Congo = Congo
{ _congoElephant :: Elephant
}
makeLenses ''Congo
Мы должны определить способ, в котором Congo
имеет слона.
instance HasElephant Congo
where
elephant = congoElephant
Теперь мы можем написать пример программы. Наша программа напечатает значение elephant
, затем изменит значение elephant
, а затем снова напечатает его.
main' :: StateT Congo IO ()
main' =
do
printElephant
function 2
printElephant
Затем мы можем запустить эту программу.
main :: IO ()
main = Congo 0 & runStateT main' & void
Вывод:
0
2
im пытается повторно назначить существующую переменную
Вы не можете сделать это в Haskell. Вы можете сделать что-то близкое, используя IORef
s, но это очень редко является правильным решением проблемы - конечно, не в ситуациях, с которыми может столкнуться новичок.
Вместо этого вы должны перепроектировать свою программную логику, чтобы она не требовала использования изменяемых переменных.
Haskell является лидером в мире функционального программирования, а функциональное программирование часто называют "программированием без назначения". Это почти вся функциональность программирования, чтобы не использовать назначение. Как только вы его используете, вы больше не делаете это "функциональным" способом. Конечно, есть время для этого, но FP пытается свести к минимуму те времена.
Итак, чтобы ответить на ваш вопрос: "Почему это не работает?" Прежде всего синтаксис неверен. =
не означает назначение в Haskell. Он связывает имя с выражением. Вы не можете сделать это дважды (в том же объеме). Другими словами, "переменные" неизменяемы (как в математике). Во-вторых, мутация является побочным эффектом, и Haskell рассматривает их как нечистые действия, которые должны выполняться в мире IO
.
Я мог бы показать вам, как на самом деле мутировать ссылку в Haskell, но я не думаю, что вам нужно в этот момент.
Самый примитивный способ привязать переменную x
к значению v
- написать функцию, принимающую x
в качестве аргумента, и передать v
этой функции.
Это иногда можно использовать для "имитации" эффекта изменяемой переменной.
Например, императивный код
// sum 0..100
i = s = 0;
while (i <= 100) {
s = s+i;
i++;
}
return s;
становится
final_s = f 0 0 -- the initial values
where
f i s | i <=100 = f (i+1) (s+i) // increment i, augment s
| otherwise = s // return s at the end
Вышеприведенный код не является довольно FP-кодом, но, по крайней мере, он достаточно близко к обязательному коду, чтобы можно было обнаружить соединения.
Окончательное отступление:
Когда кто-то сначала замечает это, его обычно заманивают, чтобы попасть в парадокс Blub. Можно легко подумать: "Что!" Haskell нуждается во всех этих материалах, чтобы имитировать простое задание? Если в языке назначение Blub тривиально, и имитация того, что в Haskell требует столько усилий, то, очевидно, Blub намного лучше, чем Haskell! ". И это было бы идеальным примером парадокса Блуба: когда программист Blub переходит на другой язык, они сразу же воспринимают то, что нельзя напрямую перевести с Blub, и не замечают всех других особенностей нового языка, которых нет в Blub. Их ум теперь мыслит в "Blub" , и он требует больших усилий для адаптации к новым моделям.
Как ни парадоксально, изучение как ПП, так и императивного программирования полезно именно потому, что нетривиально изучать другую парадигму, когда она используется только для одного из них. Если бы шаг между ними был узким, было бы нецелесообразно изучать два близких подхода к одной и той же проблеме.
В общем случае это не работает, потому что вы обычно делаете неизменяемые объявления, а не указываете последовательность операций. Вы можете сделать:
elephant = 3
main = print elephant
Но вы также можете сделать:
main = print elephant
elephant = 3
Поскольку код не указывает порядок выполнения, нет способа интерпретировать множественные назначения как что-либо иное, кроме ошибки.
Если вы хотите указать последовательность операций, используйте обозначение do:
main = do
let elephant = 0
print elephant
let elephant = 1
print elephant
let elephant = 2
print elephant
Код в блоке do выполняется в порядке, поэтому вы можете эффективно переназначать переменные так, как вы можете на большинстве языков программирования.
Обратите внимание, что этот код действительно просто создает новое связывание для слона. Старое значение все еще существует:
main = do
let elephant = 1
print elephant
let printElephant = print elephant
let elephant = 2
print elephant
printElephant
Поскольку функция printElephant, которую я определяю, все еще использует старое значение слона, это печатает:
1
2
1