I'm writing this program that is like a web crawler for online forums. For each forum I crawl, I need to do the same thing:
Now while this same logic needs to occur for each forum, the implementation for each forum is different. For example, the inputs of each login form is different per forum. One forum may have a field named "username" the other may have a field named "user". Some of these steps may have default implementations. For example, the default implementation of login
is to not do anything (because you don't have to login to some forums to crawl it).
What I've done is created a function with all these steps in it named crawl-forum
but the implementations are abstract and implemented elsewhere. My question is, what's the best way to get crawl-forum
to use these implementations?
Here's what I've tried so far. I added a new argument to the crawl-forum
function called configs
. It's a map data structure that looks like this:
{ :login login-function
:find-boards find-boards-function
...
}
The code that calls crawl-forum
is responsible for populating that map. What I don't like about this is the configs
need to be passed throughout the crawl-forum
code. It adds a new parameter everywhere. Also, I've got some lame, ad-hoc code for handling default implementations.
I talked on irc about this and someone gave me the idea that I should use multimethods for this instead since it's really polymorphic behavior. They look like this:
(defn get-site-key [& args] (first args))
(defmulti login get-site-key)
(defmethod login :default [site-key cookie-store] nil)
Then the client code has to define its own multimethods on the outside:
(defmethod login :forum-1 [site-key cookie-store] (do-something cookie-store))
What I don't like about this is that just like the config
, I have to pass the site-key
in to the crawl-forum
function and that site-key
still has to be passed around everywhere inside. Also, every defmethod
has to be passed its own site-key
back as a parameter, but none of them will ever use it. It's simply a necessary argument to do dispatch on. It's really hard for me to find a thorough multimethod tutorial though, so if there's a smarter way to do this, let me know.
Is there a 3rd option that's even better? Is there a better way to be using multimethods? Let me know, thanks.
I would go with option 1. If passing the map around bother you, you can always use a dynamic var. For the defaults, what I would suggest is to use merge:
(def defaults { ... })
(def site-specific (merge defaults { ...}))