2010年8月30日星期一

快速排序

看了《The Joy of Clojure》的快速排序,觉得比较丑,而且不通用。所以自己又写了一个。不过有一个比不上书里面的。书里面的支持 lazy-seq,而下面这个不支持。可能正是因为要支持 lazy-seq 所以书里面的那个才会那么繁琐吧……

(defn- split-by [pred coll]
[(filter pred coll) (remove pred coll)])

(defn qsort-by [comparator work]
(letfn [(step [ret [pivot & rst :as work]]
(if-not work
ret
(let [[left right] (split-by #(< (comparator % pivot) 0) rst)]
(if (seq left)
(recur ret (concat left [pivot] right))
(recur (conj ret pivot) rst)))))]
(seq (step [] (seq work)))))

(println (qsort-by - [57 66 72 27 16]))
(println (qsort-by - []))
(println (qsort-by - nil))

;=> (16 27 57 66 72)
;=> nil
;=> nil

快速排序(升序)我理解为:给定一个轴(pivot),遍历所有还没有排序完整的元素(rst),把小于轴的元素放到轴的左边(left),把不小于轴的元素放到轴的右边(right)。完全排序好的元素放到结果集(ret)里。如果左边有元素,说明找到了比轴还要小的元素。那么结果集不动,因为还没有找到最小的元素。如果左边没有元素,说明当前的轴就是最小的,把它加入结果集。

split-by 不同于 split-withsplit-with 在遇到第一个不满足条件的元素后就终止了。而 split-by 会把整个集合都遍历一遍(这里的实现是遍历了两遍)。

如果需要排序,请用 Clojure 核心库的 sort 函数。这个函数转调 java.util.Arrays.sort 函数,它是经过高度优化的。我的测试表明我的函数比标准库的慢了两三个数量级…… -_-!

2010年8月25日星期三

Named arguments in Clojure

Clojure doesn't provide direct support for named arguments, which are supported under some popular dynamic languages like Ruby, Python. The following Python code was exerpted from `The Joy of Clojure':

def slope(p1=(0,0), p2=(1,1)):
return (float(p2[1] - p1[1])) / (p2[0] - p1[0])
slope((4,15), (3,21))
#=> -6.0
slope(p2=(2,1))
#=> 0.5
slope()
#=> 1.0

It calculates the slope of a line, which can be determined by two given points. The following Clojure code mimics the named arguments:

(defn slope
([] (slope [0 0] [1 1]))
([{:keys [p1 p2] :or {p1 [0 0] p2 [1 1]}}] (slope p1 p2))
([p1 p2]
(/ (- (float (p2 1)) (p1 1)) (- (p2 0) (p1 0)))))
(slope [4 15] [3 21])
;=> -6.0
(slope {:p2 [2 1]})
;=> 0.5
(slope)
;=> 1.0

I think it's sufficient for most of the cases. However, if you want to go deeper, let's use the defnk from clojure.contrib.def.

-------------------------
clojure.contrib.def/defnk
([fn-name & fn-tail])
Macro
Define a function accepting keyword arguments. Symbols up to the first
keyword in the parameter list are taken as positional arguments. Then
an alternating sequence of keywords and defaults values is expected. The
values of the keyword arguments are available in the function body by
virtue of the symbol corresponding to the keyword (cf. :keys destructuring).
defnk accepts an optional docstring as well as an optional metadata map.

Unfornately, no examples were given in the doc string. Here's a simple example:

(require '[clojure.contrib.def :as def])
(def/defnk t [a :b 1] [a b])
(t 3)
;=> [3 1]
(t 3 :b 4)
;=> [3 4]


It should be noted that, the optional/named arguments must be put at the tail of the argument list. i.e. You can't write this: (def/defnk t [a :b 1 c] [a b c]) If you do, weird thing would happen. Or, your function may not even compile.

(def/defnk t [a :b 1 c] [a b c])
(t 1 2 3)
;=> [1 1 nil]
(def/defnk t [a :b 1 c d] [a b c d])
;=> java.lang.Exception: Unable to resolve symbol: d in this context

2010年8月16日星期一

在 Clojure 中处理异常的重要方法

Clojure 中虽然使用了 Java 的异常处理机制。但是,Clojure 很难自然地自定义自己的异常。我在与 Java 类库进行交互就时恰恰遇到了这种需求。下面的代码是与 svn-kit 进行交互的代码,它们提供了 svn-kit 的一个 wrapper。

(defmacro- try-catch-svn-ex [& exprs]
`(try ~@exprs
(catch org.tmatesoft.svn.core.SVNAuthenticationException e#
:auth-ex)
(catch org.tmatesoft.svn.core.SVNException e#
(if (re-matches #".*404 Not Found.*" (.getMessage e#))
nil
(throw e#)))))

(defn svn-get-file! [svn-repo file-path local-file]
(with-open [os (output-stream (file local-file))]
(try-catch-svn-ex
(.getFile svn-repo file-path -1 (SVNProperties.) os)
local-file)))

调用 svn-get-file! 时可能会出现用户名密码无效的问题,这时候我希望能给用户重新输入的机会。但是又不想被其它的异常干扰。这时候我可以选择将 SVNAuthenticationException 暴露出去,但是明显捕获这样一个异常是很让外层函数头疼的事。同时,自定义 Clojure 异常在外部捕获更让人头疼。所以,我在捕获了 SVNAuthenticationException 后返回一个 :auth-ex。

这种异常处理机制的最大的问题就是回到 C 语言时代检查函数返回值的方式上。这种方式写出来的程序会比较繁琐。最好的办法是用 Stuart Chouser 写的 clojure.contrib.error-kit 库。它提供了类似 Common Lisp 的异常处理体系。比传统的 try...catch 要强大很多。现在,我用 error-kit 库重写上面的函数:

(require '[clojure.contrib.error-kit :as ek])

(ek/deferror *svn-auth-error* [] [msg]
(:msg msg)
(:unhandled (ek/throw-msg Exception)))

(defmacro- try-catch-svn-ex [& exprs]
`(try ~@exprs
(catch org.tmatesoft.svn.core.SVNAuthenticationException e#
(ek/raise *svn-auth-error* (.getMessage e#)))
(catch org.tmatesoft.svn.core.SVNException e#
(if (re-matches #".*404 Not Found.*" (.getMessage e#))
nil
(throw e#)))))

(defn svn-get-file! [svn-repo file-path local-file]
(with-open [os (output-stream (file local-file))]
(try-catch-svn-ex
(.getFile svn-repo file-path -1 (SVNProperties.) os)
local-file)))

注意我用 raise 调用代替了 :auth-ex 返回值。如果捕获到了权限异常,那么我们就 raise 一个 error。这个 error 必须用 deferror 函数定义。这个 *svn-auth-error* 在没有处理函数来处理它时会通过 throw-msg 调用抛出 Exception 异常,异常的消息内容就是 :msg 所指定的消息。

注意 *svn-auth-error* 后面的第一个括号表示“父”error 是谁。这个父子关系内部通过标准库的 derive 方法定义。这里它没有父 error,所以留空。这时调用 svn-get-file! 的函数就可以拿到这个 error,可以选择让栈爆掉,也可以选择在异常抛出点继续执行。这里我们选择简单地处理后重新执行函数:

(defn svn-get-file-ex! [svn-repo file-path local-file]
(let [ret (ek/with-handler
(svn-get-file! svn-repo file-path local-file)
(ek/handle *svn-auth-error* [msg]
(println (str "Error getting " file-path ", authentication failed"))
(rm-scm-repo-username!)
(rm-scm-repo-password!)
(get-scm-repo-username!)
(get-scm-repo-password!)
(svn-get-file-ex! (get-scm-repo) file-path local-file)))]
(if
(nil? ret)
(ek/raise *get-scm-file-error* (str "404 not found: " file-path))
ret)))

注意此时对 svn-get-file-ex! 的递归调用不能用 recur。很遗憾,可能是因为 with-handler 或 handle 宏展开后定义了新的函数或者 loop。同时也请注意 deferror 时的 :unhandled 后面的 throw-msg 不要用 (throw (Exception. msg)) 来代替。如果这样做,你会发现异常是抛出去了,但是却捕获不到。原因是 :unhandled 后面期望跟的是一个函数定义。具体可以参看 throw-msg 的实现。

更多关于 error-kit 的信息,比如 continue,请参阅:ANN: clojure.contrib.error-kit

但是如果你不需要 error-kit 里的 continue 相关的功能的话,也可以使用 clojure.contrib.condition。这个库比较容易使用。而且还带了一个 print-stack-trace 方法,可以打印出比较干净的栈。示例可以参看 contrib 库源代码里面的 example 目录中的 condition/example.clj。

这两种库实现上都利用 Java 的异常来跳出栈。所以,如果你想捕获所有的异常,包括这两种库抛出来的,可以用 catch Throwable。值得一提的是,condition 库的 print-stack-trace 是通用的。不仅可以打印 condition 库抛出来的异常,也可以打印其它的异常。

contrib 库中还有一个 except,也是用来处理异常的。作者跟 condition 库是一个人。根据作者的原话,condition 库是 except 库的加强。

2010年8月6日星期五

Functions added in Clojure 1.2

The following function docs were generated from Clojure 1.2.0-RC1, in the namespace of clojure.core.

-------------------------
clojure.core/agent-error
([a])
Returns the exception thrown during an asynchronous action of the
agent if the agent is failed. Returns nil if the agent is not
failed.
-------------------------
clojure.core/bound?
([& vars])
Returns true if all of the vars provided as arguments have any bound value, root or thread-local.
Implies that deref'ing the provided vars will succeed. Returns true if no vars are provided.
-------------------------
clojure.core/case
([e & clauses])
Macro
Takes an expression, and a set of clauses.

Each clause can take the form of either:

test-constant result-expr

(test-constant1 ... test-constantN) result-expr

The test-constants are not evaluated. They must be compile-time
literals, and need not be quoted. If the expression is equal to a
test-constant, the corresponding result-expr is returned. A single
default expression can follow the clauses, and its value will be
returned if no clause matches. If no default expression is provided
and no clause matches, an IllegalArgumentException is thrown.

Unlike cond and condp, case does a constant-time dispatch, the
clauses are not considered sequentially. All manner of constant
expressions are acceptable in case, including numbers, strings,
symbols, keywords, and (Clojure) composites thereof. Note that since
lists are used to group multiple constants that map to the same
expression, a vector can be used to match a list if needed. The
test-constants need not be all of the same type.
-------------------------
clojure.core/defprotocol
([name & opts+sigs])
Macro
A protocol is a named set of named methods and their signatures:
(defprotocol AProtocolName

;optional doc string
"A doc string for AProtocol abstraction"

;method signatures
(bar [this a b] "bar docs")
(baz [this a] [this a b] [this a b c] "baz docs"))

No implementations are provided. Docs can be specified for the
protocol overall and for each method. The above yields a set of
polymorphic functions and a protocol object. All are
namespace-qualified by the ns enclosing the definition The resulting
functions dispatch on the type of their first argument, which is
required and corresponds to the implicit target object ('this' in
Java parlance). defprotocol is dynamic, has no special compile-time
effect, and defines no new types or classes. Implementations of
the protocol methods can be provided using extend.

defprotocol will automatically generate a corresponding interface,
with the same name as the protocol, i.e. given a protocol:
my.ns/Protocol, an interface: my.ns.Protocol. The interface will
have methods corresponding to the protocol functions, and the
protocol will automatically work with instances of the interface.

Note that you should not use this interface with deftype or
reify, as they support the protocol directly:

(defprotocol P
(foo [this])
(bar-me [this] [this y]))

(deftype Foo [a b c]
P
(foo [this] a)
(bar-me [this] b)
(bar-me [this y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
=> 45

(foo
(let [x 42]
(reify P
(foo [this] 17)
(bar-me [this] x)
(bar-me [this y] x))))
=> 17
-------------------------
clojure.core/defrecord
([name [& fields] & opts+specs])
Macro
Alpha - subject to change

(defrecord name [fields*] options* specs*)

Currently there are no options.

Each spec consists of a protocol or interface name followed by zero
or more method bodies:

protocol-or-interface-or-Object
(methodName [args*] body)*

Dynamically generates compiled bytecode for class with the given
name, in a package with the same name as the current namespace, the
given fields, and, optionally, methods for protocols and/or
interfaces.

The class will have the (immutable) fields named by
fields, which can have type hints. Protocols/interfaces and methods
are optional. The only methods that can be supplied are those
declared in the protocols/interfaces. Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy.

Method definitions take the form:

(methodname [args*] body)

The argument and return types can be hinted on the arg and
methodname symbols. If not supplied, they will be inferred, so type
hints should be reserved for disambiguation.

Methods should be supplied for all methods of the desired
protocol(s) and interface(s). You can also define overrides for
methods of Object. Note that a parameter must be supplied to
correspond to the target object ('this' in Java parlance). Thus
methods for interfaces will take one more argument than do the
interface declarations. Note also that recur calls to the method
head should *not* pass the target object, it will be supplied
automatically and can not be substituted.

In the method bodies, the (unqualified) name can be used to name the
class (for calls to new, instance? etc).

The class will have implementations of several (clojure.lang)
interfaces generated automatically: IObj (metadata support) and
IPersistentMap, and all of their superinterfaces.

In addition, defrecord will define type-and-value-based equality and
hashCode.

When AOT compiling, generates compiled bytecode for a class with the
given name (a symbol), prepends the current ns as the package, and
writes the .class file to the *compile-path* directory.

Two constructors will be defined, one taking the designated fields
followed by a metadata map (nil for none) and an extension field
map (nil for none), and one taking only the fields (using nil for
meta and extension fields).
-------------------------
clojure.core/deftype
([name [& fields] & opts+specs])
Macro
Alpha - subject to change

(deftype name [fields*] options* specs*)

Currently there are no options.

Each spec consists of a protocol or interface name followed by zero
or more method bodies:

protocol-or-interface-or-Object
(methodName [args*] body)*

Dynamically generates compiled bytecode for class with the given
name, in a package with the same name as the current namespace, the
given fields, and, optionally, methods for protocols and/or
interfaces.

The class will have the (by default, immutable) fields named by
fields, which can have type hints. Protocols/interfaces and methods
are optional. The only methods that can be supplied are those
declared in the protocols/interfaces. Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy. Fields can be qualified
with the metadata :volatile-mutable true or :unsynchronized-mutable
true, at which point (set! afield aval) will be supported in method
bodies. Note well that mutable fields are extremely difficult to use
correctly, and are present only to facilitate the building of higher
level constructs, such as Clojure's reference types, in Clojure
itself. They are for experts only - if the semantics and
implications of :volatile-mutable or :unsynchronized-mutable are not
immediately apparent to you, you should not be using them.

Method definitions take the form:

(methodname [args*] body)

The argument and return types can be hinted on the arg and
methodname symbols. If not supplied, they will be inferred, so type
hints should be reserved for disambiguation.

Methods should be supplied for all methods of the desired
protocol(s) and interface(s). You can also define overrides for
methods of Object. Note that a parameter must be supplied to
correspond to the target object ('this' in Java parlance). Thus
methods for interfaces will take one more argument than do the
interface declarations. Note also that recur calls to the method
head should *not* pass the target object, it will be supplied
automatically and can not be substituted.

In the method bodies, the (unqualified) name can be used to name the
class (for calls to new, instance? etc).

When AOT compiling, generates compiled bytecode for a class with the
given name (a symbol), prepends the current ns as the package, and
writes the .class file to the *compile-path* directory.

One constructors will be defined, taking the designated fields.
-------------------------
clojure.core/denominator
([r])
Returns the denominator part of a Ratio.
-------------------------
clojure.core/error-handler
([a])
Returns the error-handler of agent a, or nil if there is none.
See set-error-handler!
-------------------------
clojure.core/error-mode
([a])
Returns the error-mode of agent a. See set-error-mode!
-------------------------
clojure.core/extend
([atype & proto+mmaps])
Implementations of protocol methods can be provided using the extend construct:

(extend AType
AProtocol
{:foo an-existing-fn
:bar (fn [a b] ...)
:baz (fn ([a]...) ([a b] ...)...)}
BProtocol
{...}
...)

extend takes a type/class (or interface, see below), and one or more
protocol + method map pairs. It will extend the polymorphism of the
protocol's methods to call the supplied methods when an AType is
provided as the first argument.

Method maps are maps of the keyword-ized method names to ordinary
fns. This facilitates easy reuse of existing fns and fn maps, for
code reuse/mixins without derivation or composition. You can extend
an interface to a protocol. This is primarily to facilitate interop
with the host (e.g. Java) but opens the door to incidental multiple
inheritance of implementation since a class can inherit from more
than one interface, both of which extend the protocol. It is TBD how
to specify which impl to use. You can extend a protocol on nil.

If you are supplying the definitions explicitly (i.e. not reusing
exsting functions or mixin maps), you may find it more convenient to
use the extend-type or extend-protocol macros.

Note that multiple independent extend clauses can exist for the same
type, not all protocols need be defined in a single extend call.

See also:
extends?, satisfies?, extenders
-------------------------
clojure.core/extend-protocol
([p & specs])
Macro
Useful when you want to provide several implementations of the same
protocol all at once. Takes a single protocol and the implementation
of that protocol for one or more types. Expands into calls to
extend-type:

(extend-protocol Protocol
AType
(foo [x] ...)
(bar [x y] ...)
BType
(foo [x] ...)
(bar [x y] ...)
AClass
(foo [x] ...)
(bar [x y] ...)
nil
(foo [x] ...)
(bar [x y] ...))

expands into:

(do
(clojure.core/extend-type AType Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type BType Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type AClass Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type nil Protocol
(foo [x] ...)
(bar [x y] ...)))
-------------------------
clojure.core/extend-type
([t & specs])
Macro
A macro that expands into an extend call. Useful when you are
supplying the definitions explicitly inline, extend-type
automatically creates the maps required by extend. Propagates the
class as a type hint on the first argument of all fns.

(extend-type MyType
Countable
(cnt [c] ...)
Foo
(bar [x y] ...)
(baz ([x] ...) ([x y & zs] ...)))

expands into:

(extend MyType
Countable
{:cnt (fn [c] ...)}
Foo
{:baz (fn ([x] ...) ([x y & zs] ...))
:bar (fn [x y] ...)})
-------------------------
clojure.core/extenders
([protocol])
Returns a collection of the types explicitly extending protocol
-------------------------
clojure.core/extends?
([protocol atype])
Returns true if atype extends protocol
-------------------------
clojure.core/flatten
([x])
Takes any nested combination of sequential things (lists, vectors,
etc.) and returns their contents as a single, flat sequence.
(flatten nil) returns nil.
-------------------------
clojure.core/fnil
([f x] [f x y] [f x y z])
Takes a function f, and returns a function that calls f, replacing
a nil first argument to f with the supplied value x. Higher arity
versions can replace arguments in the second and third
positions (y, z). Note that the function f can take any number of
arguments, not just the one(s) being nil-patched.
-------------------------
clojure.core/frequencies
([coll])
Returns a map from distinct items in coll to the number of times
they appear.
-------------------------
clojure.core/get-in
([m ks] [m ks not-found])
Returns the value in a nested associative structure,
where ks is a sequence of ke(ys. Returns nil if the key is not present,
or the not-found value if supplied.
-------------------------
clojure.core/group-by
([f coll])
Returns a map of the elements of coll keyed by the result of
f on each element. The value at each key will be a vector of the
corresponding elements, in the order they appeared in coll.
-------------------------
clojure.core/keep
([f coll])
Returns a lazy sequence of the non-nil results of (f item). Note,
this means false return values will be included. f must be free of
side-effects.
-------------------------
clojure.core/keep-indexed
([f coll])
Returns a lazy sequence of the non-nil results of (f index item). Note,
this means false return values will be included. f must be free of
side-effects.
-------------------------
clojure.core/map-indexed
([f coll])
Returns a lazy sequence consisting of the result of applying f to 0
and the first item of coll, followed by applying f to 1 and the second
item in coll, etc, until coll is exhausted. Thus function f should
accept 2 arguments, index and item.
-------------------------
clojure.core/namespace-munge
([ns])
Convert a Clojure namespace name to a legal Java package name.
-------------------------
clojure.core/numerator
([r])
Returns the numerator part of a Ratio.
-------------------------
clojure.core/object-array
([size-or-seq])
Creates an array of objects
-------------------------
clojure.core/partition-all
([n coll] [n step coll])
Returns a lazy sequence of lists like partition, but may include
partitions with fewer than n items at the end.
-------------------------
clojure.core/partition-by
([f coll])
Applies f to each value in coll, splitting it each time f returns
a new value. Returns a lazy seq of partitions.
-------------------------
clojure.core/rand-nth
([coll])
Return a random element of the (sequential) collection. Will have
the same performance characteristics as nth for the given
collection.
-------------------------
clojure.core/reductions
([f coll] [f init coll])
Returns a lazy seq of the intermediate values of the reduction (as
per reduce) of coll by f, starting with init.
-------------------------
clojure.core/reify
([& opts+specs])
Macro
reify is a macro with the following structure:

(reify options* specs*)

Currently there are no options.

Each spec consists of the protocol or interface name followed by zero
or more method bodies:

protocol-or-interface-or-Object
(methodName [args+] body)*

Methods should be supplied for all methods of the desired
protocol(s) and interface(s). You can also define overrides for
methods of Object. Note that the first parameter must be supplied to
correspond to the target object ('this' in Java parlance). Thus
methods for interfaces will take one more argument than do the
interface declarations. Note also that recur calls to the method
head should *not* pass the target object, it will be supplied
automatically and can not be substituted.

The return type can be indicated by a type hint on the method name,
and arg types can be indicated by a type hint on arg names. If you
leave out all hints, reify will try to match on same name/arity
method in the protocol(s)/interface(s) - this is preferred. If you
supply any hints at all, no inference is done, so all hints (or
default of Object) must be correct, for both arguments and return
type. If a method is overloaded in a protocol/interface, multiple
independent method definitions must be supplied. If overloaded with
same arity in an interface you must specify complete hints to
disambiguate - a missing hint implies Object.

recur works to method heads The method bodies of reify are lexical
closures, and can refer to the surrounding local scope:

(str (let [f "foo"]
(reify Object
(toString [this] f))))
== "foo"

(seq (let [f "foo"]
(reify clojure.lang.Seqable
(seq [this] (seq f)))))
== (\f \o \o))
-------------------------
clojure.core/remove-all-methods
([multifn])
Removes all of the methods of multimethod.
-------------------------
clojure.core/restart-agent
([a new-state & options])
When an agent is failed, changes the agent state to new-state and
then un-fails the agent so that sends are allowed again. If
a :clear-actions true option is given, any actions queued on the
agent that were being held while it was failed will be discarded,
otherwise those held actions will proceed. The new-state must pass
the validator if any, or restart will throw an exception and the
agent will remain failed with its old state and error. Watchers, if
any, will NOT be notified of the new state. Throws an exception if
the agent is not failed.
-------------------------
clojure.core/satisfies?
([protocol x])
Returns true if x satisfies the protocol
-------------------------
clojure.core/set-error-handler!
([a handler-fn])
Sets the error-handler of agent a to handler-fn. If an action
being run by the agent throws an exception or doesn't pass the
validator fn, handler-fn will be called with two arguments: the
agent and the exception.
-------------------------
clojure.core/set-error-mode!
([a mode-keyword])
Sets the error-mode of agent a to mode-keyword, which must be
either :fail or :continue. If an action being run by the agent
throws an exception or doesn't pass the validator fn, an
error-handler may be called (see set-error-handler!), after which,
if the mode is :continue, the agent will continue as if neither the
action that caused the error nor the error itself ever happened.

If the mode is :fail, the agent will become failed and will stop
accepting new 'send' and 'send-off' actions, and any previously
queued actions will be held until a 'restart-agent'. Deref will
still work, returning the state of the agent before the error.
-------------------------
clojure.core/shuffle
([coll])
Return a random permutation of coll
-------------------------
clojure.core/spit
([f content & options])
Opposite of slurp. Opens f with writer, writes content, then
closes f. Options passed to clojure.java.io/writer.
-------------------------
clojure.core/thread-bound?
([& vars])
Returns true if all of the vars provided as arguments have thread-local bindings.
Implies that set!'ing the provided vars will succeed. Returns true if no vars are provided.
-------------------------
clojure.core/vector-of
([t])
Creates a new vector of a single primitive type t, where t is one
of :int :long :float :double :byte :short :char or :boolean. The
resulting vector complies with the interface of vectors in general,
but stores the values unboxed internally.