#clojure logs

2009-09-19

01:13taliosAfternoon
01:31JAS415what is the difference between an atom and a ref?
01:32hiredmanrefs are coordinated
01:32JAS415is it just the transactional locking?
01:32hiredmanpretty much
01:34JAS415so i would use a ref if i were for example, doing an in memory database that is getting updated from multiple threads
01:34JAS415and i wanted it to be safe
01:35hiredmanyou would use a ref if you needed to coordinate mutation
01:36hiredmanyou can update an atom from multiple threads no problem
01:36clojurebotPeople have a problem and think "Hey! I'll use a regular expression!". Now they have two problems....
01:38hiredmanhttp://groups.google.com/group/clojure/msg/fd0371eb7238e933 <-- rhickey on atoms/refs/agents
01:38JAS415so i guess i'm just mulling over what it is to coordinate mutation
01:39hiredmanif the new value of Ref1 is a function of the value of Ref2
01:40JAS415oh okay
01:40JAS415so its a mechanism to keep them in sync
01:40hiredman
01:41hiredmancoordinate
01:41hiredmanugh
01:41JAS415coordinate
01:41JAS415sorry
01:42hiredmansorry, ugh was for something else
01:42JAS415I'll try to read this stuff and understand it
01:43JAS415the impression that i'm getting is that atom will be enough for what i'm doing currently
03:56leafwHouston, I have a problem. I can't start the clojure Repl from the github master branch
03:56leafwI get: java.lang.ExceptionInInitializerErr
03:57leafwat clojure.main.<clinit>(main.java:20)
03:57leafwI am trying to launch it like: java -cp jars/clojure.jar clojure.main
03:57leafwclojure.lang.Repl also fails.
03:57leafwthe line that fails in clojure.main is final static private Var REQUIRE = RT.var("clojure.core", "require");
03:59leafwI built clojure the old way ... so now, only when doing so with ant and AOT generation of class files, clojure will work?
03:59taliosdoes the same happen running 'java -jar clojure.jar'?
04:00hiredmanleafw: runs fine here, try ant clean and rebuild
04:00hiredmanwhat do you mean the old way?
04:00leafwtalios: yes.
04:00leafwhiredman: only .clj files inside; no .class files.
04:01leafwclojure.jar with .class files inside runs just fine.
04:01hiredmana. that is not going to work, the jvm only executes bytecode, so there need to be some .class files in there
04:01taliosgiven that a lot of clojure is written in java - you'd need -some- .class files...
04:02leafwhiredman: ok. I get it.
04:02leafwI just haven't used the ant way of building in ages.
04:02taliostheres another way?
04:02hiredmanb. the slim jar files generate by ant compile the java code and leave the clojure code as .clj files
04:03hiredmanleafw: then how are you building if not with ant?
04:03leafwhiredman: with our own build system. I found out about the differences when doing an update.
04:04hiredman:(
04:04hiredmanI would recommend using clojure's ant setup
04:04hiredmanit's there, and it gets used
04:05leafwhiredman: sure, but it's just inconvenient when building clojure as part of another ~100 jars or so. Ant xml is just ... not expressive enough.
04:05hiredmanusing a private building system for it is bound to run into problems as clojure changes over time
04:06leafwhiredman: well, as maintainer, I will be fixing it.
04:07taliosmaintainer of what?
04:08leafwclojure in fiji : http://pacific.mpi-cbg.de
04:09taliosoh - I thought it was a clojure deployment IN Fiji :)
04:09hiredmanif clojure's current build system is not enough, it's better if it's made to be enough, and not have people maintaining a bunch of code in private
04:10hiredman(not that it has much of a build system)
04:10leafwclojure AOT is one of a kind.
04:10leafwnver mind, we are happy.
04:11taliosmmm that reminds me - must check my github forkqueue for the maven plugin
04:11hiredmanI'm just saying, it makes everyone's life easier if code is pushed out
04:17taliosbah - a) can't apply stuart sierra's patched as they break things, b) github just went down, c) out of coke :(
04:17talioss/ed/es/
05:07tomojhmm.. I don't think *print-level* should affect eldoc
06:39pixelmanWriting a nested if seems like it could be done with a macro instead, what I have is "if a elsif b elsif c else d". where should I look?
06:39arbscht,(doc cond)
06:39clojurebot"([& clauses]); Takes a set of test/expr pairs. It evaluates each test one at a time. If a test returns logical true, cond evaluates and returns the value of the corresponding expr and doesn't evaluate any of the other tests or exprs. (cond) returns nil."
06:40pixelmansweet! tnx.
06:41arbscht,(doc case)
06:41clojurebot"clojure.contrib.fcase/case;[[test-value & clauses]]; Like cond, but test-value is compared against the value of each test expression with =. If they are equal, executes the \"body\" expression. Optional last expression is executed if none of the test expressions match."
06:56triyoCan someone explain to me how the clojure.core/future works? Maybe through some example or something pls.
06:58triyo,(doc future)
06:58clojurebot"([& body]); Takes a body of expressions and yields a future object that will invoke the body in another thread, and will cache the result and return it on all subsequent calls to deref/@. If the computation has not yet finished, calls to deref/@ will block."
06:58hiredmanit wraps code in a thunk, and runs on the thunk on another thread
06:59hiredmanfutures can be derefed
06:59hiredmanif you deref them, they block waiting for the result of the thunk
06:59triyohiredman: what does thunk mean?
06:59hiredman(fn [] )
07:00triyooh ok
07:01triyoso future will actually be ran on a saperate thread correct?
07:01hiredmanfutures are run on the agent send-off threadpool
07:02triyoahh, thanks, now that makes sense, i understand
07:04triyohiredman: I was going through this updated example of "sleeping barber" http://gist.github.com/189002 and came across future func and wasn't to sure how it works
07:45killy971in clojure, map is lazy by default ?
07:47tomojkilly971: map returns a lazy sequence, yes
07:48killy971is it possible to use a non-lazy version of it ? (for performance)
07:48tomojnot that I know of
07:50tomojyou could write your own I suppose
07:51tomojis the laziness of map really a bottleneck?
07:52killy971I suppose that it is not actually...
07:53arbscht,(doc doall)
07:53clojurebot"([coll] [n coll]); When lazy sequences are produced via functions that have side effects, any effects other than those needed to produce the first element in the seq do not occur until the seq is consumed. doall can be used to force any effects. Walks through the successive nexts of the seq, retains the head and returns it, thus causing the entire seq to reside in memory at one time."
07:53killy971but doall it just there to force the evaluation by the way
07:54killy971it won't get rid of the internal lazy processing
07:54arbschtoh right, performance
07:54killy971but actually I shouldn't think of this as a bottleneck
07:55arbschtyeah, I don't see the problem with it
08:00killy971I was trying to make a fast prime sieve
08:01killy971the clojure version of the first answer on this page : http://stackoverflow.com/questions/1023768/sieve-of-atkin-explanation
08:02killy971when I read the wiki about the sieve of atkin, I didn't see the relation between this implementation and the original algorithm... but if I just believe the SO page, it would mean that this algorithm if fundamentally faster than the eratosthene sieve
08:03killy971so I implemented it in clojure, and compared performance with the eratosthene one, and I can't get as fast as it....
08:05killy971my reference for the eratosthene sieve implementation : http://paste.lisp.org/display/69952
08:07killy971something that I especially don't understand here is the fast that (aset arr j (int 1)) is far faster than (aset-int arr j 1)
08:07killy971but in my implementation, it makes no difference
08:11hiredmansure
08:12hiredmanaset is fastern than any of the aset-* functions
08:13tomojwhy do the aset-* functions exist?
08:13hiredmanI forget
08:13tomojmaybe aset used to not work with primitives?
08:13pixelmanhow can i pretty print any object i clojure, I'm looking for something like ruby's .inspect
08:14tomojpixelman: there's clojure.contrib.pprint
08:15pixelmantomoj: thanks!
08:17killy971aset is fastern than any of the aset-* functions >> but replacing (aset-int arr i 1) by (aset arr i (int 1)) in my code doesn't change anything... so I was wondering why did it change performance on rich hickey code
08:20hiredman~performance
08:20clojurebothttp://clojure.org/java_interop#toc46
08:24killy971in my code, I have an (aset arr i 1) producing in a reflection warning
08:24killy971so I suppose using aset-int is the only way to solve this ?
08:25hiredmanno
08:25hiredmanyou need to hint arr and i
08:25hiredmanor coerce
08:28leafwhum
08:28leafw,(unchecked-add (byte 4) (byte 7))
08:28clojurebotjava.lang.IllegalArgumentException: No matching method found: unchecked_add
08:28leafw,(find-doc "unchecked-add")
08:28clojurebot------------------------- clojure.core/unchecked-add ([x y]) Returns the sum of x and y, both int or long. Note - uses a primitive operator subject to overflow.
08:28leafw???
08:28leafwwhat am I doing wrong?
08:29killy971is it possible to have a type hint a let for an int-array ?
08:29leafwkilly971: yes: (let [#ints ia (make-array Integer/TYPE 10)] a)
08:30killy971I have 2 loops
08:30leafw,(unchecked-add 4 7)
08:30clojurebot11
08:30leafwha! So unchecked-add doesn't like bytes
08:30killy971in the fouter one I have an int-array, that I give to the inner loop
08:31leafwkilly971: if you are looping, you are likely doing something wrong.
08:31leafwuse amap or areduce on primitive arrays.
08:31killy971is it possible to to (let [arr (#^ings array)]) ?
08:31hiredmana type hint is not a function
08:31leafwkilly971: see the hinting example I pasted for you above.
08:32leafwwhich should be #^ints, sorry, not #ints
08:37killy971great !
08:37killy971thank you very much
08:38killy971it seems that the problem came of the index variable
08:54JomyootHow do I parse "(1 2 3)" into clojure
08:55JomyootI have a String that contains Clojure Syntax -- representing a hash. How would I parse it.
08:56tomoj,(read (java.io.PushbackReader. (java.io.StringReader. "{:foo 3}")))
08:56clojurebot{:foo 3}
08:56tomojnot sure if there is a shorter way
08:56hiredmanread-string
08:56tomojoh, haha :)
08:56Jomyoot,(read-string "(1 2 3)")
08:56clojurebot(1 2 3)
08:57Jomyootwow coolly
08:57Jomyoot,(read-string [1 2 3])
08:57clojurebotjava.lang.ClassCastException: clojure.lang.LazilyPersistentVector cannot be cast to java.lang.String
08:57Jomyoot,(read-string "[1 2 3]")
08:57clojurebot[1 2 3]
08:58Jomyoot,(read-string "[\"1\"]")
08:58clojurebot["1"]
08:58Jomyootooh
08:58tomojthat's nice cus it seems to assume the stuff is quoted, too
08:58tomojin my example you'd need to quote yourself
08:58Jomyootwhat do u mean? in that I need to quote myself?
08:59tomojyou don't
08:59tomojoh actually my example is the same
09:00tomojread is not read and eval :)
09:00Jomyooti see
09:00Jomyootbut it allows me to get the structure right?
09:00Jomyootdata structure
09:02tomojyep
09:13killy971when I get a class cast exception, what does the type I am told refer to ?
09:13killy971the real type of the object, or the type I was trying to give it ?
09:13killy971to cast it to
09:16ChouserI think the class mentioned is what's expected.
09:16killy971by the way, does (int-array ...) makes an array of primitive ints ?
09:17killy971because somewhere in my code I was trying to cast this kind of array to #^ints, and I get a class cast exception with [I (which refer to Integer array)
09:18Chouser,(int-array 0)
09:18clojurebot#<int[] [I@a30a4e>
09:18Chouseryep, primitive ints
09:27killy971somewhere in my code I have this : (loop [j (+ (* (* i (+ (int i) 3)) 2) 3)]
09:27killy971when I try to coerce j to int here with "j (int ...)", I get this :
09:27killy971java.lang.RuntimeException: java.lang.IllegalArgumentException: recur arg for primitive local: j must be matching primitive
09:28killy971I don't understand the error message
09:33arbschtwhat's in your recur form?
09:45lisppaste8killy971 pasted "prime sieve" at http://paste.lisp.org/display/87313
10:09JomyootIs there a good testing framework for clojure?
10:33dnolenjomyoot: clojure core ships with one.
10:33Jomyootis it worth looking into?
10:51clojurefirefoxwhat's ClassName.class in java and clojure?
10:51dnolenjomyoot: most definitely.
10:51Jomyooti will look into it then
10:55clojurefirefoxany diffs between Classname.class and Classname.getClass()?
10:56clojurefirefoxclassname/class not work in clojure
11:52ambientis Hickey's JVM summit 2009 speech available anywhere on the web? or just slides?
13:37grosourshi ^^
13:43tomojbeing forced to program in python makes me crazy after clojure
13:50leafwtomoj: in python you also have map and reduce, and lamda.
13:51tomojyes but they suck
13:52leafwwhat a great attitude. Not quite.
13:52tomojmaybe there is a partial somewhere?
13:53leafwcurrying ?
14:10LauJensentomoj: What frustrates you about Python ?
14:12tomojgeneral ugliness, feel like I have to fight it to program in a functional style
14:12tomojprobably just because I'm new to python
14:12tomojbut I miss clojure :(
14:12LauJensenfight it?
14:13tomojI mean what would feel perfectly natural in clojure feels unnatural and difficult in python to me
14:13LauJensenThat would be beginners ticks :) But I'm considering Python for a future blogpost, so let me know if you stumble on something worth writing about
14:15tomojI miss ruby blocks too
14:16danleiwhen in rome, do as the romans do ;)
14:18tomojromans wiped their asses with sticks
14:19danlei:)
14:20danleiI know what you mean, but If you feel like fighting the language, you'll be better of adapting (even if it implies stick-wiping) ... ;)
14:20danlei(at least, if someone is supposed to read your code later on)
14:22tomojyeah
14:22tomojluckily this class assumes no programming experience so I can get away without knowing python idiom
14:41LauJensenBy the way, it seems clojure-concurrency is a very hot topic these days, my blog got hit with 5000 visitors yesterday, I think most of them looking at the STM vs Actor comparison
14:52wtetznercan you refer to the 'this' pointer in proxy?
15:57LauJensenSo...quiet night? :)
15:58licoresseyes
15:58licoresseat least the kids are asleep
16:14dreishIt looks like synchronized () blocks don't work in Java when they occur in code in a subclass of Thread. It seems as though the JVM thinks both threads are the same because they're executing code within the same Thread object.
16:14dreishEither that, or I'm crazy.
16:58LauJensenGot an example dreish ?
16:59dreishNot a simple, standalone one. I'm thinking of trying to write one to see whether I'm right.
17:16dreishLauJensen: Apparently the "I'm crazy" theory is the more successful one at the moment.
17:16LauJensendreish: That's always scary to discover :(
17:18dreishWell, I shouldn't be surprised. This was an over-ambitious project I started in Java several months before Clojure broke out onto the scene. I had no idea how complicated it is to do concurrency well in Java.
17:24LauJensenConcurrency is amazingly difficult if you haven't got Rich on your side
17:33LauJensen,(keyword 2)
17:33clojurebotjava.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
17:33LauJensenShouldn't that be implemented?
17:46rafsoakencould somebody explain to me what's the practical difference of (reduce + [1 2 3]) and (apply + [1 2 3]) ?
17:46rafsoakenplz
17:48Chousukerafsoaken: none :)
17:48Chousukerafsoaken: + for more than two arguments uses reduce internally
17:49rafsoakenlol, thx - I've just seen (reduce + 1 [2 3 4]) and (apply + 1 2 [3 4]) is a difference.. the only i could spot
17:56dreishrafsoaken: The only practical difference I've run into is that apply will hold on to the head, so if you have a huge Seq, such as over a large file, reduce + will work and apply + will run out of memory.
17:57rafsoakenthanks dreish, interesting..
18:12criosdreish, the synchronized block should work inside a Thread subclass, too
18:12dreishcrios: Yes, that's what I found when I wrote a small test.
18:12criosit depends on what you are synchronizing
18:14criosthe sentinel object which you synchronize on must be shared between all threads
18:15dreishYes, it is.
18:18criosthis is a simple java thread example: http://paste.lisp.org/display/87223
18:19criosif it can help
19:44danlei(ns a) (def foo 1) (ns b) (def foo 2) (binding [*ns* (find-ns 'a)] foo) => 2. how to do it right?
20:21dreishdanlei: (binding [*ns* (find-ns 'a)] @(resolve 'foo)) => 1
20:24danleidreish: thanks
20:25danleibut I don't know if that will help. In my mud client, I want the users to be able to eval some code in a user package - so @(...) is not such a nice solution.
20:25dreishdanlei: Otherwise, eval does the resolving, then calls the compiled code, which begins with binding.
20:26dreishSounds like you're going to use eval anyway, so you might as well do (binding [...] (eval ...))
20:27danleihm, I'll check that, thanks
20:29danleiok, works
20:33killy971I am trying to write the atkin sieve in clojure, and I am quite lost concerning the variable scope and side-effect prevention
20:34killy971I declare an int-array at the beginning of my function, and I try to modify it in a for loop
20:34killy971but it seems that the array doesn't get modified
20:35dreishkilly971: for is for list comprehension, not for side-effects. Try doseq
20:36killy971I see
20:36killy971thank you, I am going to try
20:37killy971what about "when" ?
20:37killy971can (when something do-side-effet)
20:37dreishOr you could surround the for with doall to force it to walk the entire seq.
20:39dreishBetter yet, dorun, which won't hold the head.
20:39killy971can I show you my code ?
20:39dreish~paste
20:39clojurebotlisppaste8, url
20:39lisppaste8To use the lisppaste bot, visit http://paste.lisp.org/new/clojure and enter your paste.
20:40lisppaste8killy971 pasted "atkin sieve" at http://paste.lisp.org/display/87336
20:40killy971(it is not yet optimized for performance, but I first want it to return a correct result)
20:41dreishWhere's the for?
20:41killy971I changed them with doseq as you said
20:42dreishOkay, so I'm guessing your array changes at least, right?
20:42killy971yes
20:48killy971misunderstanding
20:48killy971I would like the array to change
20:48killy971but it doesn't seem to
20:49killy971well it just doesn't (if you do (println (seq sieve)) just before the last loop
20:49dreishMaybe aset isn't being reached?
20:51killy971true..
20:51killy971sorry, I am going to investigate this way
21:03killy971ok... all my (rem n ...) should have been with n1, n2 and n3
21:11killy971thank you very much for your help
21:38gerryxiaohello
21:38Chousergerryxiao: hi
21:39gerryxiaoi have question about agent
21:39gerryxiaohow much args in agent function?
21:39gerryxiao,(def a (agent 0))
21:39clojurebotDENIED
21:40gerryxiao,(defn f [v] (inc v))
21:40clojurebotDENIED
21:40gerryxiao(add-watcher a :sendoff a f)
21:41gerryxiaoit seems not work?
21:41gerryxiaooops
21:41gerryxiao(def b (atom 0))
21:42gerryxiao(add-watcher b :send-off a f)
21:42gerryxiaoi got agent error
21:43gerryxiao(swap! b inc)
21:43gerryxiao@ a
21:43hiredman,(dco agent)
21:43clojurebotjava.lang.Exception: Unable to resolve symbol: dco in this context
21:43hiredman,(doc agent)
21:43clojurebot"([state] [state & options]); Creates and returns an agent with an initial value of state and zero or more options (in any order): :meta metadata-map :validator validate-fn If metadata-map is supplied, it will be come the metadata on the agent. validate-fn must be nil or a side-effect-free fn of one argument, which will be passed the intended new state on any state change. If the new state is unacceptable, the validate-fn
21:43hiredman,(doc send)
21:44clojurebot"([a f & args]); Dispatch an action to an agent. Returns the agent immediately. Subsequently, in a thread from a thread pool, the state of the agent will be set to the value of: (apply action-fn state-of-agent args)"
21:44gerryxiaobut i change f to (defn f [v t] (inc v)), it works
21:45gerryxiaoat least two args? is that ture?
21:46gerryxiaoaccording doc, f with one arg should work
21:47gerryxiaoi have to add one useless arg
21:47gerryxiaoChouser?
21:48gerryxiaohmm
21:52gerryxiaouser=> (def a (agent 0))
21:52gerryxiao#'user/a
21:52gerryxiaouser=> (def b (atom 0))
21:52gerryxiao#'user/b
21:52gerryxiaouser=> (defn f [v] (inc v))
21:52gerryxiao#'user/f
21:52gerryxiaouser=> (add-watcher b :send-off a f)
21:52gerryxiao#<Atom@3cc262: 0>
21:52gerryxiaouser=> (swap! b inc)
21:52gerryxiao1
21:52gerryxiaouser=> @a
21:52gerryxiaojava.lang.Exception: Agent has errors (NO_SOURCE_FILE:0)
21:55hiredman,(doc add-watcher)
21:55clojurebot"([reference send-type watcher-agent action-fn]); Experimental. Adds a watcher to an agent/atom/var/ref reference. The watcher must be an Agent, and the action a function of the agent's state and one additional arg, the reference. Whenever the reference's state changes, any registered watchers will have their actions sent. send-type must be one of :send or :send-off. The actions will be sent after the reference's state is
21:56hiredmanit doesn't say, but I would imagine the new value for the ref is also passed to the agent action
21:56hiredmanah
21:57hiredmanit does actually say
21:57hiredman"The watcher must be an Agent, and the action a function of the agent's state and one additional arg, the reference."
21:59gerryxiao(clear-agent-errors a)
21:59gerryxiao(remove-watcher b)
21:59gerryxiao(defn f [v n] (inc v))
21:59hiredmanyou don't need to paste every line like that
22:00gerryxiao(add-watcher b :send-off a f)
22:00gerryxiao(swap! b inc)
22:00hiredmanlisppaste8: url
22:00lisppaste8To use the lisppaste bot, visit http://paste.lisp.org/new/clojure and enter your paste.
22:00gerryxiao@a
22:00gerryxiao1
22:00hiredmanif you are having trouble with some code, paste it, togther with the exception you are getting, into the pastebin
22:00gerryxiaoit works, but why?
22:00hiredmangerryxiao: are you kidding?
22:01hiredmanread the docs for add-watcher
22:01hiredmanread the line I quoted from the docs for add-watcher
22:03gerryxiaoanyone here?
22:04hiredman
22:19gerryxiaoso i have to pass ref as second arg?
22:21gerryxiaook
22:31gerryxiaosure ,the second arg is the ref
22:32gerryxiaobut i dont want use it in my case
22:33gerryxiao:)
22:42interferoni'm a clojure beginner and need help cleaning up an ugly piece of code. i want to accept new connections on a ServerSocket and fire them off to a thread:
22:42interferon (loop [socket (.accept server)]
22:42interferon (. (Thread. #(process-request socket)) start)
22:42interferon (recur (.accept server)))
22:42interferonis there a better way to write that loop without the repetition of the accept call?
22:44manic12isn't loop all about repetition?
22:45interferonyes, but i'm talking about typing it twice
22:45interferoni want something similar to the while( (socket = server.accept() ) ) idiom in other languages
22:46Chouserjust use a 'let' inside the loop
22:47jamesswiftcan anyone suggest a simple lib or sample code to graphically display a list as a tree? thanks.
22:47Chouser(loop [] (let [socket (.accept server)] ...))
22:47interferonnice
22:47interferonlike
22:47interferon (loop []
22:47interferon (when-let [socket (.accept server)]
22:47interferon (. (Thread. #(process-request socket)) start)
22:47interferon (recur)))
22:48Chouserinterferon: sure. Next time use pastebin for more than a couple lines.
22:48manic12<hand slap>
22:49killy971it is really hard to get good performance with clojure while keeping code clean
22:49interferonwhere is pastebin?
22:49manic12killy971: use c++, is that clean enough?
22:49Chouserlisppaste8: url
22:49lisppaste8To use the lisppaste bot, visit http://paste.lisp.org/new/clojure and enter your paste.
22:49killy971I want to use clojure
22:50killy971but would like not to have to cast ints everywhere just because it's faster
22:50manic12anytime you optimize code it becomes less clean
22:50jamesswiftsometimes it becomes cleaner ;)
22:51manic12unless you wrote the algorithm wrong to begin with
22:51interferonkilly971: do you really "have" to use ints? and how is that less clean than other languages which will probably always require type declarations?
22:51killy971well
22:51killy971type declaration is only to be used once
22:51manic12symbolics didn't need declarations
22:52Chouserclojure is young. have patience.
22:53manic12i've been doing all this stuff with native calls from clojure, and I had three major bugs, and they were all in the clojure code ;)
22:57manic12i found bugs in msvc++ debugger though. imagine that.
23:10lisppaste8jamesswift pasted "tree transform" at http://paste.lisp.org/display/87340
23:11jamesswiftanyone want to help my tired brain out and describe how they might transform the inTree to the outTree?
23:11jamesswiftyears of imperative is making it hard to describe algorithms functionally
23:13jamesswiftshould be simple though, right?