Как я могу переписать переменную в функции в Haskell?

Я не знаю, как переназначить переменную в функции.

Например,

elephant = 0

function x = elephant = x

Почему это не работает?

Ответ 1

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

Ответ 2

im пытается повторно назначить существующую переменную

Вы не можете сделать это в Haskell. Вы можете сделать что-то близкое, используя IORef s, но это очень редко является правильным решением проблемы - конечно, не в ситуациях, с которыми может столкнуться новичок.

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

Ответ 3

Haskell является лидером в мире функционального программирования, а функциональное программирование часто называют "программированием без назначения". Это почти вся функциональность программирования, чтобы не использовать назначение. Как только вы его используете, вы больше не делаете это "функциональным" способом. Конечно, есть время для этого, но FP пытается свести к минимуму те времена.

Итак, чтобы ответить на ваш вопрос: "Почему это не работает?" Прежде всего синтаксис неверен. = не означает назначение в Haskell. Он связывает имя с выражением. Вы не можете сделать это дважды (в том же объеме). Другими словами, "переменные" неизменяемы (как в математике). Во-вторых, мутация является побочным эффектом, и Haskell рассматривает их как нечистые действия, которые должны выполняться в мире IO.

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

Ответ 4

Самый примитивный способ привязать переменную 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" , и он требует больших усилий для адаптации к новым моделям.

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

Ответ 5

В общем случае это не работает, потому что вы обычно делаете неизменяемые объявления, а не указываете последовательность операций. Вы можете сделать:

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