Clojure: Estados

04 September 2010

A função hello criada anteriormente é pura. Isto significa que ela não tem nenhum efeito colateral. Esse tipo de função é relativamente fácil de escrever, testar e entender além de ser recomendada pra diversas necessidades.

Porém muitos programas possuem estado compartilhado e precisam de funções não puras como essa para manter esse estado compartilhado.

Melhorando a função hello
Nossa função agora manterá um registro de todas as chamadas que forem feitas a ela. Para isso, usaremos um "set" do Clojure. #{} ⇒ #{} Como adicionar elementos nesse set ? Para isso utilizaremos um "conj". Conj é a significa conjoin de forma simplificada e ele cria novas coleções com os ítens adicionados. Podemos então usar o conj com um elemento em um set. (conj #{} "Anderson") ⇒ #{"Anderson"} Agora que sabemos como criar "sets", precisamos de alguma forma manter os registros das chamadas a nossa função hello. Para isso, Clojure possui referencias (refs) e para nomear uma referência usamos def. def é quase igual ao defn mas de uma forma mais generalista. Usando def podemos definir funções ou dados. Usamos def para criar referências e associá-las a nossa função: (def visitors (ref #{})) ⇒ #'user/visitors

E onde está esse controle de estados tão forte em Closure ?


O que acontece se tentarmos alterar essa referência que criamos ? Vamos tentar fazer isso usando "alter". Usando "alter" podemos tentar alterar nossa referência, inclusive passando argumentos se necessário. Vamos tentar alterar "visitor" para "visitors". (alter visitors conj "Anderson") ⇒ java.lang.IllegalStateException: No transaction running

Como vemos acima, clojure protege as referências. Podemos alterá-las mas precisamos de "transactions", deixando para o Clojure o trabalho pesado de lidar com concorrêcia entre múltiplas chamadas a nossa função.

E como crio essa transaction ? Para criar uma transaction usamos dosync. (dosync (alter visitors conj "Anderson")) ⇒ #{"Anderson"} Agora podemos buscar a referência com deref, veja: (deref visitors) ⇒ #{"Anderson"} @visitors ⇒ #{"Anderson"}

Agora podemos construir uma função hello muito mais avançada e elaborada: (defn hello "Returns a hello message, calling you by username. Knows if you have been here before." [username] (dosync (let [past-visitor (@visitors username)] (if past-visitor (str "Welcome back, " username) (do (alter visitors conj username) (str "Hello, " username))))))
comments powered by Disqus