Coming from Python, for the most part, I felt right at home in the Clojure REPL. However, one of my trustiest old tricks in the Python interpreter didn't work nearly as well in Clojure.
I'm referring, of course, to the "what the hell was that called again?" fixer: dir.
In Python, dir will get you the fns/classes in a namespace, but the first major obstacle in clojure is the lack of support for namespace aliases.
Let's fix that, shall we?
Looking at the source for the dir macro, we find:
user> (source dir)
(defmacro dir
"Prints a sorted directory of public vars in a namespace"
[nsname]
`(doseq [v# (dir-fn '~nsname)]
(println v#)))
Ok, this is really no more than a bit of sugar, to save us having to quote the namespace and to print the vars on their own line. We'll have to dig deeper for some meat.
user> (source dir-fn)
(defn dir-fn
"Returns a sorted seq of symbols naming public vars in
a namespace"
[ns]
(sort (map first (ns-publics (the-ns ns)))))
One thing I really love about Lisps: many times when I look under the hood, I'm expecting a monstrous Rube Goldberg machine, but what I find is some beautifully simple thing, that will still be pretty much the same thing in 50 years. (Go back and read some of the early algorithmic lisp code, it comes very easy.)
The first part of dir that bugs me is you must specify the full namespace, even if you've aliased it to something much simpler.
(ns yournamespace
(:require [clojure.string as str]
[clojure.repl :refer :all]))
(dir str)
=> Exception No namespace: str found clojure.core/the-ns (core.clj:3830)
user> (dir clojure.string)
blank?
capitalize
escape
...
You can get aliases via ns-aliases, so...
(defn alias-dir-fn [nsname]
(-> (ns-aliases *ns*)
(get nsname) ; look up the alias or nil trying
(or nsname)
dir-fn))
(defmacro alias-dir
"Prints a sorted directory of public vars in a namespace or an alias
namespace."
[nsname]
`(doseq [v# (alias-dir-fn '~nsname)]
(println v#)))
Now we can...
user> (alias-dir str)
blank?
capitalize
escape
...
But I'm not done with dir yet. Stay tuned for part 2.
Edit: I've filed a ticket for this functionality to be added to Clojure
Showing posts with label snippets. Show all posts
Showing posts with label snippets. Show all posts
Sunday, February 8, 2015
Using memoization to change a creation function into get-or-create.
N.B. This should work in any language with first class functions, memoization, and immutable values.
tl;dr: In the past I'd always thought of memoization as a way to save the computer work. It hadn't occurred to me that it could also save me work.
I was re-reading "The Joy of Clojure" and came across a gem I'd missed the first time. Listing 14.12 is described as "A function to create or retrieve a unique Agent for a given player name".
(def agent-for-player
(memoize
(fn [player-name]
(-> (agent [])
(set-error-handler! #(println "ERROR: " %1 %2))
(set-error-mode! :fail)))))
;; The above doesn't quite work for me, set-error-handler!
;; doesn't seem to return the agent. Doesn't make the pattern
;; less compelling, though.
The authors comment that this allows you to maintain player-name as an index into a table of agents without having to explicitly manager and lookup agents.
There are two caveats to this approach: 1) player-name must be immutable, and 2) You really need to understand the memoization mechanism. clojure.core/memoize, for instance, will keep a internal map of args/response until the end of time. You could use http://clojure.github.io/core.memoize/ to modify the strategy if you so choose.
The place where I'd try this first is in what I call "micro-logs". Frequently as I'm working, I want to log some data to a side channel, and this pattern saves having to manage this manually and cluttering up my code.
(def get-or-create-micro-log
(memoize
(fn [file]
(io/make-parents file)
(.createNewFile file)
(-> (io/writer file :append true)
(agent
:error-mode :fail
:error-handler #(println "ERROR: " %1 %2))))))
(defn microlog
"Useful micro-pattern to send off a write to various
files via agents without having to maintain a lookup
table. Symlinks can get you into trouble; at a
minimum they will duplicate the agent."
[lg line]
(let [a (-> (io/file lg)
(.getAbsoluteFile)
(get-or-create-micro-log))
output (str (str/trim-newline line) "\n")]
(send-off a
(fn [writer]
(doto writer
(.write output)
(.flush))))))
(defn microlog-all
[lg all-data]
(doseq [d all-data]
(microlog lg d)))
tl;dr: In the past I'd always thought of memoization as a way to save the computer work. It hadn't occurred to me that it could also save me work.
I was re-reading "The Joy of Clojure" and came across a gem I'd missed the first time. Listing 14.12 is described as "A function to create or retrieve a unique Agent for a given player name".
(def agent-for-player
(memoize
(fn [player-name]
(-> (agent [])
(set-error-handler! #(println "ERROR: " %1 %2))
(set-error-mode! :fail)))))
;; The above doesn't quite work for me, set-error-handler!
;; doesn't seem to return the agent. Doesn't make the pattern
;; less compelling, though.
The authors comment that this allows you to maintain player-name as an index into a table of agents without having to explicitly manager and lookup agents.
There are two caveats to this approach: 1) player-name must be immutable, and 2) You really need to understand the memoization mechanism. clojure.core/memoize, for instance, will keep a internal map of args/response until the end of time. You could use http://clojure.github.io/core.memoize/ to modify the strategy if you so choose.
The place where I'd try this first is in what I call "micro-logs". Frequently as I'm working, I want to log some data to a side channel, and this pattern saves having to manage this manually and cluttering up my code.
(def get-or-create-micro-log
(memoize
(fn [file]
(io/make-parents file)
(.createNewFile file)
(-> (io/writer file :append true)
(agent
:error-mode :fail
:error-handler #(println "ERROR: " %1 %2))))))
(defn microlog
"Useful micro-pattern to send off a write to various
files via agents without having to maintain a lookup
table. Symlinks can get you into trouble; at a
minimum they will duplicate the agent."
[lg line]
(let [a (-> (io/file lg)
(.getAbsoluteFile)
(get-or-create-micro-log))
output (str (str/trim-newline line) "\n")]
(send-off a
(fn [writer]
(doto writer
(.write output)
(.flush))))))
(defn microlog-all
[lg all-data]
(doseq [d all-data]
(microlog lg d)))
Edits: Used io/make-parents instead of File calls. Changed send to send-off, since this is I/O. Cleaned up creation of agent.
Friday, June 20, 2014
Om examples: Scrubbing Calculator component
I'm starting to get the hang of Om. While I've used cljs & jayq for a while, understanding the right way to use om + core.async has taken a bit of doing. They're both great libraries, and have fairly small APIs, but I've been hungry for fairly simple examples. In that vein, here are a couple, the second inspired by Bret Victor's Scrubbing Calculator.
Update: git repo with examples
Prelude: simple clock
Scrubbing Int, Approach one: all local state
Two utility functions:
My first attempt entirely used internal state. Not terribly useful, but it set the stage.
It works entirely in the span element. Click and drag changes the value. If you drag out of the element before you release the mouse button, the state becomes inconsistent. The snippet below will let you tune the sensitivity if you use it in the :onMouseMove handler.
Now
Now
Approach two: Moving out the state
So here I've switched things to use an external atom for the value, and moved most of the logic into event listeners under IWillMount. This works correctly even if you drag off the span element. It's pretty much everything I set out to do with this.
The only drawback is the event listeners should be connected in the handler for onMouseDown, and removed when "mouseup" is received. I'll update it when I've figured out how to remove existing listeners.
Afterward: Stuff you should be looking at
- devcards are awesome, a great way to prototype this kind of stuff
- Prismatic/om-tools
- om-sweet-om-high-functional-frontend-engineering-with-clojurescript-and-react/
- Isomorphic Clojurescipt, and his example omelette
Subscribe to:
Posts (Atom)