Hi Scaramaccai,

Several posters in this thread have given you example code for how
to solve your OAuth token problem in Clojure. Perhaps I can add to
this discussion by pointing out the fundamentals of functional
programming that can help you decide how to solve problems like
this.

*Loops -> Recursions and State -> Bindings*

You asked how to handle state in a functional programming language.

In some situations, it may be easiest to just store stateful values
in a mutable container type like a ref, agent, or atom. This is not
a strictly functional approach, but these are tools that Clojure
provides to make a variety of programming tasks somewhat easier (in
the Rich Hickey sense of the word).

In many cases, this is neither necessary nor preferable. Instead,
you can use the functional programming approach of emulating state
change over time by passing your new derived values as arguments to
the next step in a recursive function.

That is, in FP "state change" happens on the stack (through
function bindings and let bindings) rather than on the heap
(through direct assignment to mutable containers).

Consider these two approaches to computing the factorial function.

*1. Imperative (loops + mutation)*

(defn fact-imp [n]
  (let [result (atom 1)]
    (dotimes [i n]
      (swap! result * (inc i)))
    @result))

*2. Functional (recursion + fn bindings)*

(defn fact-rec [n]
  (if (<= n 0)
    1
    (* n (fact-rec (dec n)))))

These two implementations will return the same outputs for the same
inputs. Note that in the functional approach, fact-rec computes the
next value of the result and passes it as the input to itself
rather than mutating a local variable as in the imperative case
with fact-imp.

Savvy readers will notice that fact-rec is not tail recursive and is
therefore prone to stack overflow for large values of n. Rewriting
it to work with loop+recur is left as an exercise for the reader. ;)

The approach shown above is a typical solution for state that goes
through a series of intermediate changes to produce a final result.

A similar approach for the same issue is to model all the values
that you would have stored one at a time in a stateful variable as
an immutable sequence of values. This approach relies on lazy
evaluation.

To illustrate, I will once again implement factorial using this
technique.

*3. Functional (sequences + lazy evaluation)*

(defn fact-lazy [n]
  (letfn [(next-step [[i x]] [(inc i) (* x (inc i))])
          (fact-seq [pair] (lazy-seq (cons pair (fact-seq (next-step pair
)))))]
    (second (nth (fact-seq [0 1]) n))))

In this example, next-step derives the next state value from the
current one. The fact-seq function returns a lazy sequence of all
the [i factorial(i)] pairs from [0 1] to [infinity
factorial(infinity)]. This sequence is obviously never fully
realized since it would throw your application into an infinite
recursion. We then use nth to grab the [n factorial(n)] pair off of
the lazy sequence and second plucks out just factorial(n) to return
as the result of our fact-lazy function.

Once again, I never mutated any variables in place. I simply
created a recursive algorithm that could derive the next value from
the current value. Unlike the non-tail-recursive fact-rec above,
this lazy sequence implementation is immune to stack overflow
errors. However, fact-lazy will use more memory and more CPU cycles
than fact-rec's eager implementation because it has to create and
release the lazy sequence and intermediate vector pairs. These are
all tradeoffs, which you would need to consider in determining the
approach that might work best for your problem.

*Hiding State*

In the three approaches I showed to represent state change in your
Clojure program, none of these went out of their way to either hide
or share the state values over time. Here, you again have broadly two 
choices:

*1. Global mutable variables*

If you don't need to hide your application state, the best approach
is just to store it in a global mutable container like a ref, agent,
or atom. Then you can introspect or manipulate it from your REPL,
and multiple functions in your program can all access it as needed.

(def state (atom 0))

*2. Closures*

If you need to hide your application state for some reason (and
there are often less reasons to do this than you might imagine),
then your best friend is a closure function. A closure is a
function which "closes over" the bindings which exist when it is
defined by capturing references to them in its free variables
(i.e., variables which are not bound as function args or let args
in the function body).

Functions that return closures around mutable state can work
a bit like OOP constructor functions, creating a function that
behaves a bit like an object with internal stateful attributes.
Welcome to OOP inverted!

(defn get-token
  "Somehow get an OAuth token for this user+pass combination."
  [user pass]
  {:token   "some-token" ; replace this with something real
   :expires (+ (System/currentTimeMillis) 1000000)})

(defn expired?
  "Returns true if the token has expired."
  [token]
  (> (System/currentTimeMillis) (:expires token)))

(defn make-http-client
  "Return a closure that retains the token associated with this
  user+pass combination and can make HTTP requests using it."
  [user pass]
  (let [token (atom (get-token user pass))]
    (fn [url]
      (when (expired? @token)
        (reset! token (get-token user pass)))
      (client/get url {:oauth-token (:token @token)}))))

(def get-response (make-http-client "my-user" "my-pass"))

(get-response "some-url")
(get-response "another-url")
(get-response "yet-another-url")
(get-response "even-another-url")

The code above creates a closure function called get-response that contains 
an internal reference to a token atom. This atom is automatically updated 
using the stored user and pass whenever the token expires. All HTTP 
requests generated by get-response include the up-to-date OAuth token as 
expected.

I hope this exposition on some of the approaches used to solve problems in 
a functional programming style will help you both in your current project 
and many more going forward. Just remember to visualize state change over 
time as a sequence of values which can be recursively derived by their 
previous values. Whether the transformation from current value to next 
value is best done in an eager fashion (like fact-rec) or in a lazy fashion 
(like fact-lazy) will be determined by your particular problem domain. In 
many cases, there is not much value to hiding state, so if you choose to 
use mutable container types like refs, atoms, or agents, you are likely 
better off just making them globally available. However, if you really do 
want to capture state lexically within a function (perhaps because you need 
to pass it to another namespace), then closures can give you that ability 
in a powerful yet succinct way.

And to echo James Reeves' comments, your code will be shorter, simpler, and 
easier to extend if you just stick with plain maps. Records and protocols 
are great if you need fast polymorphism in your algorithm or if you need to 
extend a class that you don't control to implement one of your functions. 
However, they add a good bit of extra overhead to your code base that can 
make it more difficult to read, debug, and extend later.

Alright, that's enough from me on this topic. Go forth and write beautiful, 
elegant, functional code.

(hack :the-planet)
  ~Gary

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/clojure/abbce12a-e069-4ef0-adec-5e32e0d80a3do%40googlegroups.com.

Reply via email to