This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-12-08
Channels
- # adventofcode (240)
- # beginners (87)
- # boot (4)
- # cider (27)
- # cljs-dev (20)
- # cljsrn (24)
- # clojure (365)
- # clojure-argentina (1)
- # clojure-brasil (4)
- # clojure-dev (12)
- # clojure-greece (65)
- # clojure-india (1)
- # clojure-italy (15)
- # clojure-japan (1)
- # clojure-losangeles (1)
- # clojure-madison (4)
- # clojure-poland (3)
- # clojure-russia (5)
- # clojure-spec (3)
- # clojure-uk (105)
- # clojurescript (27)
- # core-async (1)
- # core-logic (3)
- # cursive (61)
- # datomic (68)
- # devcards (4)
- # docs (27)
- # duct (67)
- # emacs (15)
- # events (1)
- # fulcro (8)
- # graphql (50)
- # lein-figwheel (1)
- # lumo (15)
- # numerical-computing (1)
- # off-topic (77)
- # om (3)
- # onyx (5)
- # parinfer (3)
- # planck (2)
- # portkey (5)
- # re-frame (4)
- # reagent (16)
- # ring (14)
- # rum (3)
- # shadow-cljs (17)
- # vim (1)
Uh. Switching to HoneySQL. Can't find how add RETURNING into query builder. Can anyone help?
To scan a whole file system (collecting information about file sizes), is recursively applying .listFiles
the fastest option available to me? I couldn't immediately work out whether it was based on nio2 which seems to be the lowest level option available in Java? Anecdotally my approach seems orders of magnitude slower than, for example, DaisyDisk (I am on a Mac) which appears (although maybe they have some way of deferring the fetching of individual file information for a folder?) to be able to gather the same information in under 30s. -- digging around in the Java I see java.nio.file.Files#walkFileTree
and I don't think .listFiles
is using that so I guess I should start digging there.
for recursively looking at the fs, check out file-seq
- it returns a lazy seq of files
also I would assume DaisyDisk is exploiting your mac’s spotlight cache, or you have a very small file system and the clojure approach was extremely inefficient
Thanks @noisesmith, I did start with file-seq
but noticed it was based on .listFiles
and since the laziness didn't seem to be an advantage to me used that directly. I've also tried an nio based approach. There's 458GB of files in on my file system and DaisyDisk is scanning that in ~13s, my approach doesn't complete in less than 15 mins (at which point I abort it).
I'm sure DD must be using something but cannot yet see how it might be the spotlight cache since my researches so far suggest that it doesn't contain a complete list of files on the file-system
Hello all, how do I convert this?
SSHClient ssh = new SSHClient();
ssh.addHostKeyVerifier(
new HostKeyVerifier() {
@Override
public boolean verify(String s, int i, PublicKey publicKey) {
return true;
}
});
how can I do traverse
in clojure? So I have an array of [1 2 3 4]
and I’d like to call let’s say get-user
function, which accepts a user ID and does an http call and then I want to end up with an array of results basically
if I use map, will that hold the computation that happens afterwards, before everything is resolved?
@bravilogy yes, as long as you realize the computation
you could even use https://clojuredocs.org/clojure.core/pmap
and use doall to ensure you produce the side effect https://clojuredocs.org/clojure.core/doall
Hrmm... even using hara.io.file.list
which seems to be nio based is glacially slow. I guess apps like DaisyDisk must be using OSX specific apis not available to Java.
trying to wrap my head around fully qualified keywords, would the following be a valid way to refer to a spec in another namespace:
(ns foo
(:require [clojure.spec :as s]))
(s/def ::layout-string ...)
(ns bar
(:require [clojure.spec :as s]))
(s/fdef parse-layout-string
:args :foo/layout-string
...)
? (I ask since I’m getting an interesting ‘Call to clojure.core/ns did not conform to spec’ error)@fmind seems my error was an incompatibility in my dependency chain with clojure 1.9…could be I’m a bit thick here, but I don’t always find those spec error messages entirely easy to grok
there is this https://github.com/bhb/expound
you can try to make them more readable with https://github.com/bhb/expound
@niklas.collin @fmind thanks for the pointer, I’ll take a look at expound
problem is this was the clojure compiler complaining about code I didn’t write. Not sure it would be possible to force the clojure internal spec conform calls to use expound…
would it be possible to inject this early enough in the build process so that when clojure checks my transitive dependencies, it would use expound/printer? (I’m using leiningen)
not sure, haven’t tried it. But you can execute arbitrary code within Leiningen. The problem is though, that if the exception actually does truly happen during dependency resolution then it is kinda too early since spec itself is a dependency
I would be super surprised the problem would surface during dependency resolution though
after dependencies you can execute code via leiningen :injections
, see example here https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L182
You could put it in user.clj - thats loaded pretty early
user.clj is another option, that comes later than :injections
but if that is enough then it would be preferrable.
I think one of my dependencies was not built to work with clojure 1.9 and fails a few checks when it is, I have solved the issue by upping the dependency to a version which is built for 1.9 so this is somewhat academic for the purposes of my immediate issue, but interesting nevertheless
and here my clojure newbieness shines through a bit. Would not the dependencies be loaded on “first refer” or something, i.e. probably before the code in core.clj etc is executed?
if your code is not AOT compiled then dependencies etc are loaded when you require them
so, if you add the expound hook into user.clj - which is the very first thing Clojure loads - and there only refer spec and expound then you will get that code executed before another piece of code gets executed which will try to require the offending library
which would normally be in the ns block at the top, so the error printer hooking would have to happen before that at least
@niklas.collin ok, thank you again!
np, the dynamic nature of Clojure is really powerful, it has a slight drawback of relatively high startup time but in general it’s benefits far outweigh the downsides
It is ok to seek feedback for a library idea here? Its a rather big idea so before releasing anything I and investing too much I want to make sure its not totally wrong! edit: https://www.reddit.com/r/Clojure/comments/7if66b/objection_another_component_alternative_is_this_a/ gh: https://github.com/riverford/objection
@niklas.collin could you paste your stuff here? 🙂
Empperi [4:17 PM] ok, I read the github page [4:18] really doesn’t look that different from mount to me [4:18] for example, the “lazy construction instead of explicit single start! fn.” is not strictly totally true, you can specify which components you want started with mount when you call it’s start! [4:20] so, I would say that would look great if there were no mount available, since it is however I do not see any real value I would get out of that
so, I wouldn’t say “don’t do it” but as it is now it doesn’t look compelling enough to me to justify a new library
but, I got to go and cannot give you any more relevant feedback. Do not kill your idea based on just what I said 🙂
I do not use mount, I have looked at it in the past - in fact a lot of this came out of a lack of satisfaction with mount/integrant/component for certain kinds of system. I do appreciate 'yet another component/mount/bla' alternative, and one of the main reasons why I am apprehensive. I'll adjust the readme a bit to try and help demonstrate why its different, and where the value prop is of this over the others. Thanks for taking the time to give it a look 🙂
For any that are interested I put a bit more in the readme to attempt to explain things a bit better
Any comments would be greatly appreciated, just so I know whether to invest in it - I have lots of open-source work that I want to do and want to get a feel for whether its worth the risk on this one (as it seems very risky)
for example, the “lazy construction instead of explicit single start! fn.” is not strictly totally true, you can specify which components you want started with mount when you call it’s start!
so, I would say that would look great if there were no mount available, since it is however I do not see any real value I would get out of that
@U0GE2S1NH (pinging him here to add him to this thread)
The big difference vs mount is that this doesn't use global singletons, which is what I absolutely can't stand about mount.
It looks like objection is a global (singleton) registry of any number of components.
So if you need more than one DB connection you can simply start up and register multiple connections. All components seem to have their own UUIDs keeps components from getting lost, as well as allows for multiple instances of the same component.
@U0GE2S1NH I don't not like it, and the more I think about it the more I like it. 🙂
Thanks @U07TDTQNL
Singletons aren't always bad (e.g queue and threadpool used as some internal optimization, no effect on api, want flexibility to change locally) - so I do support them.
Not sure about using them for all state though, so I wanted to make the dynamic/open case the primary one.
I've always been a component/integrant user too, not really used mount - though I tried it a couple of times
Where?
while [[ "$(curl -s -o/dev/null '
Cool, also just noticed this: https://github.com/Homebrew/homebrew-core/pull/21470/files
But http://clojure.org still has nothing about release
$> lein test
Retrieving org/clojure/clojure/1.9.0/clojure-1.9.0.pom from central
Retrieving org/clojure/clojure/1.9.0/clojure-1.9.0.jar from central
: )I have been doing some brain storming on how to make more understandable software. I’m trying to turn those ideas into something actionable, maybe a clojure library. The current idea is to give projects more of a narrative. I wrote up a quick proposal of what this might look like here: https://github.com/drewverlee/narrative/blob/master/ideas.md I welcome any feedback. Thanks
I think good tests read like a commentary track dvd extra for the code - so it should have at least that level of narrative
there’s some interesting ideas here, thanks
Thanks noise, I think good tests can do that too. But that still wouldn't be the same as telling a story about the project ... Or do you disagree? I think this kind of narrative usually gets put in the readme. It also just occurred to me that I should be clear the narrative I had in mind is aimed at developing on the project not being a end user... Though you could probably use the same mental framework too construct one towards that end as well.
Since you mention “developing”, it seems to me that you want to communicate the (eventually shared) mental model of the project. I remember reading an article on the need for this, and how different mental models over time cause problems or technical debt, but can’t find it.
(merge nil {:a 2})
(comment
{:a 2})
(merge (persistent! (transient nil)) {:a 2})
;; ==> exception
is this because transient can't figure out if nil should become {} () [] or #{} ?so yes
What you want is (transient (empty {:a 2})) or something like that
well, thinking that it's because "transient can't figure out if nil should become [] () [] or #{}" is a bit of a mis-statement
Right, "can't create transient from null" is the technical reason. "can't figure out if..." is the reason that it's not fixed
taking this to a thread as this is going to be a bit nit-picky -- the reason why I wouldn't reply "yes" to that question is that often people that don't really understand how nil is treated in the clojure stlib think that e.g (merge nil {1 2})
works is because they think that nil
can "act" as an empty collection of all types, when in reality that's only true in the case of seqs, and accepting that interpretation could be a bit confusing
when the reality is that merge
and other functions explicitely filter out nils or replace them with the appropriate empty coll, not that they know how to "interpret nil" as the correct empty coll
@bronsa: I see, thanks for the clarification. This is precisely the mental model mistakes I was making.
the context is that I wrote this function:
(defn merge-drop-vEmpty [a b]
(persistent!
(reduce (fn [old [k v]]
(if (or (= v nil) (= v {}))
(dissoc! old k)
(assoc! old k v)))
(transient a) b)))
and it's a bit weird, since (merge nil {...})
is fine but (merge-drop-vEmpty nil {...})
is bad
Well you can do the same, you just need to give it a default if a is nil
(transient (or a {}))
@qqq another possible version (not so sure it’s an improvement but it lets me demo transduce)
(defn merge-drop-vEmpty
[a b]
(transduce
identity
(fn
([] (transient (or a {})))
([v] (persistent! v))
([old [k v]]
(if (contains? #{nil {}} v)
(dissoc! old k)
(assoc! old k v))))
b))
I do like the fact that it has a completing arity instead of needing the completion function to wrap the call
but using identity as an xform makes me think this might be silly
oh wait we could skip a bunch of BS by just using into here, because into already does the transient/persistent! thing for you
but you can’t edit the first arg to into
I was actually never sure if (merge nil {:a 1})
working was reliably supported behavior
clojure.core/merge
([& maps])
Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping from
the latter (left-to-right) will be the mapping in the result.
Just because a fn happens to do some behavior with nil now, doesn’t give me a lot of confidence that I’m not relying on an impl detail
There are plenty of cases like this though. The merge
one is just one I’ve wrestled with in my head a few times.
Also, I’m thinking it’d be a pretty harsh change to have merge
stop supporting this behavior - so not likely to change anymore 😛
merge supports a couple other surprises, see the last comment in https://dev.clojure.org/jira/browse/CLJ-1458
I feel like if a spec is added to merge that says (s/nilable m) then a subsequent docstring PR that clarified what happened in the case of nil would prob be accepted
@ghadi good point on the core.specs. I just took a look over at that (haven’t looked in a while). It still has a lot more functions to cover still right? At least from what I see so far.
It's mostly the main macros right now, @alexmiller has more stuff pending somewhere, and there are a couple of github users that have their own specs of core
generally clojure docstrings don't comprehensively specify edge-cases, but http://clojuredocs.org or whatever the thing-du-jour is certainly can
Then other times I have a hard time deciding whether I’m relying on an odd impl detail or not
The stricter interpretation you take of the doc strings, the less likely you are to be relying on an odd impl detail.
I know that is still pretty vague advice, but at least in the case of merge, if you never pass it nil, you are safe. If you do, maybe it could return anything at all or raise an exception in Clojure 1.13
Not saying it is likely, given the backwards compatibility effort Rich et al put into Clojure.
@andy.fingerhut yeah, and I’ve found myself a few times being defensive about it out of that sort of worry
(update x merge {:a 1}) ; not this
(update x (fnil merge {}) {:a 1}) ; this
the trade-off, make more verbose code to not rely on things vs hope for the best, but be prepared for potentially more difficult upgrades in the future
and perhaps watch the Jiras carefully and fight for whatever obscure use-case you see that someone is about to break 😛
I’m not saying what is right vs wrong here. Just explaining my internal conflict and struggle
@mikerod total aside: that update could just use assoc
=> (assoc nil :a 0)
{:a 0}
@noisesmith hah thanks, I didn’t intend to make something realistic
it’s one of my pet clojure style issues, I just had to say something, I compulsively replace calls to merge that use a map literal as a second arg with calls to assoc (or instruct co-workers to do so when doing code reviews)
that’s what makes it an aside haha
oooh (fnil merge {} {})
is also valid though
seems like if-not
could just ditch the expansion with not
i.e.
(defmacro if-not
([test then] `(if ~test nil ~then))
([test then else] `(if ~test ~else ~then)))
(I think I confused myself with the metadata propagation, which only happens when the first arg is not nil)
@bronsa the best parge is that (merge)
returns nil
there's nothing particularly attractive about it, it's just reality in a dynamic langauge. spec is moving us towards Garbage In EXCEPTION
@bronsa merge -> (merge nil nil nil nil) get -> (get get get get)
it’s probably one of my greatest discoveries
@bfabry can you clarify your claim that it’s a “reality in dynamic languages”. it’s not clear to me how typing discipline plays a role here.
for those who are unfamiliar
=> (= get ((get get get get) {get get} get))
true
well, how would you prevent gigo in clojure? spec! what happens if you don't do something like that? gigo! ie, gigo is just the default case in a dynamic language where data is passed around in structures with uniform interfaces
@bfabry that may be true from some cases but that has more to do with the programmer who wrote the source. it’s not inherently a problem in dynamic languages.
another option would be to sanity check all inputs manually. I'm glad no one took the time to do that seeing as spec exists now and that code would just be noise
and only when *check-asserts*
is true. instrumentation with spec is extremely slow as well.
they're certainly useful. but they're noisy, and quite a lot of work. I think spec is a much more elegant solution because it gives you some sort of structural documentation and errors as well as validation
if someone had gone through core and :pre'd every function then the current work of going through core and spec'ing every function would feel a bit redundant
so basically, the argument in favor of GIGO is “i don’t want to write the sanity checks for my code because it’s noisy”?
"I don't want to write the sanity checks because it's noisy and I have more important things to write"
that seems like a weak argument against clearly expressing your domain, range, semantics, etc.
not just that - overly strict preconditions can make later extensions tedious or incompatible
@noisesmith i can see that, although, i’ve never come up against that in practice.
@noprompt simple example - imagine whitelisting the allowed keys in a hash-map - you now need to rewrite your code as soon as code around it is extended, even though it shouldn’t have to care
other cases are more subtle but often just as tedious
@bfabry i’ve seen that attitude fail catastrophically though, enough so that :post (some? %)
has saved me hours of debugging.
(defn foo [x] {:post (some? %)} x)
may look like it works but it actually asserts some?
(truthy) and then asserts %
(the value returned from foo
) so it needs to be (defn foo [x] {:post [(some? %)]} x)
although the following actually works too -- but looks odd: (defn foo [x] {:post (%)} x)
(`[%]` works the same as (%)
here -- it's just a sequence of forms to evaluate with %
bound to the function result. This was a surprise to me when I tried it in the REPL!you can get future proof code without a bunch of complex incantations by failing to validate, once you validate future proofing is more work, and adds complexity in which bugs can hide
(unless the validating comes from the outside, eg. spec)
I don't think anyone disagrees that some validation can be very useful. which is presumably why schema became so popular and why spec was created. I personally think on balance :pre and :post don't give enough extra value to go along with the validation to make them worth it the vast majority of the time. schema gave a bit more value and now spec gives even more to help tip the scales towards making it worth doing
@noisesmith i suppose my argument there would that, to your point, the constraint is too tight. spec doesn’t alleviate you from that either.
if only there were technologies for late-parameterizing specifications with additional requirements
I don't understand what we're arguing about, clojure had a GIGO problem and now we have spec to avoid that
I thought GIGO in Clojure was purely motivated by run-time efficiency ?
And have any core Clojure developers ever called it GIGO, or is that something originated by others?
GIGO isn't a philosophy or a rationale, it's just a statement. if you pass nonsense in that there's no validation checks around you can expect to get nonsense out
@andy.fingerhut I'm sure I've seen alex call it GIGO more than once :)
while GIGO is just a fact, there’s a rationale to prefering code with GIGO behavior over defensive code - which does describe clojure code before spec
yeah but that preference is because of "performance" or "cleaner code" or "whatever". it's not like anyone is specifically trying to write code to silently fail
meh, you make it sound like most GIGO in clojure was a design decision when in reality it is just lack of validation to avoid: - runtime performance costs - having to manually write explicit checks for everything
@bfabry If you mean an example of GIGO being a rationale to avoid adding run-time type checks, the clojure.set operators are the biggest example in my mind, e.g. https://dev.clojure.org/jira/browse/CLJ-1953
they're using "gigo" to describe "that's a case where the function does something unintended because you passed in something unintended". I don't see anyone saying "it should be that way cuz gigo"
@andy.fingerhut no one is saying they're not validating clojure.set "because gigo". If you were to specifically ask my guess would be "because performance and can't be bothered"
core members have said in multiple forums that spec will "solve" the gigo issues of clojure.set
in one of the linked tickets "Now that set
is faster for sets, I think we could actually add checking for sets in some places where we might not have before. "
so the previous reason was performance. and the current reason is "better things to do so far"
also, lucky they did those better things, because they made spec! and now that validation is trivially added in a uniform way rather than ad-hoc
it’s was more of “i’ve seen this GIGO thing be used as a rationale in the context of the clojure community and i’m curious what motivates it.”
@noprompt The community cannot change clojure.core without the approval of one person. If someone else gives a rationale for what changes in clojure.core, or does not change in clojure.core, it is based on their interpretation/guesstimate of the reasons.
hey, someone write specs for the clojure.set functions and file a patch
My interpretation is that "can't be bothered to add the checks" seems unlikely for things like clojure.set functions, since many people would have been happy to provide patches for those years ago if they were desired by the core developer team. spec being a far more general tool is fantastic, and I'm glad it was created.
that’d be like, useful
you don’t even have to wait for them to be in core to use them
@andy.fingerhut i understand that, however, my question was more broad in scope. i’m not calling out clojure core, the libraries, or the members. it was a question for the room in the context of the community purely because i’ve seen it be a rationale for doing something not bound by the performance reason.
just put ’em in a namespace and load them
to say “i won’t do x because typing” can be a fine argument in certain situations but in general it’s very weak.
the point was simply that you see that kind of beahaviour less in typed languages (at an unacceptable cost). again. not a rationale. a statement
in my experience, writing specs for core stuff can bring out many subtle questions. going through those and teasing apart what is expected, what works but is unexpected, and what doesn’t work now but we might want to work in the future has some measure of art to it.
the way i read that argument is “my program won’t/will have this set of properties because (not) typing”.
I don't understand what "typing" you mean here :) the fingers on a keyboard one or the holy war one
i don’t believe in arguing about static vs dynamic typing. it’s a pointless endeavor. folks should be open to good ideas period and stop obsessing about typing discipline.
i’m actually filled with a bit of dread now because it means i have to finish garden 2.0.0. 😂
I’m celebrating with some Templeton Rye 6 year myself :)
I have some scotch that would qualify, but I keep coming back to this Templeton lately
line ’em up
"Ahhh, the 1.9.0 --- that was a good version for Scotch..."
they really don’t know how to do version numbers well in scotch
branching strategies are crazy
it’s like they were all drunk or something
on to Clojure 1.10 :)
@alexmiller Out of curiosity, was it just waiting for bug reports or actually still fixing stuff in the last few weeks?
well the last few weeks has mostly been me rewriting the docs over and over at Rich’s direction :)
Gotta make sure the docs don't over-promise 🙂
but also giving it some soak time in case anything came up
Sorry, I couldn't resist. Bad me[
no, that’s true :)
@andy.fingerhut as long as the specs are valid 😉
may all your specs be valid
scottish blessing I think
Hi! Can you guys give me an idea of how to try to resolve this?
I have a test that calls a functions. This functions executes an async task. Locally if I use a (Thread/sleep 4000)
I can assert my data just fine.
But, after I build my application on travis, the build stop in this test and never finalize the build. Someone hava already seen this?
@tvalerio are you able to patch your test (or code) in such a way that it does not require the task to be executed asynchronously? i know that’s not answering your question, however, it’s one i would ask myself in that situation.
@noprompt I don’t see a way to do that. I have to call this async function and wait for the entire process to finish. Otherwise, when I go to the database the data is not there yet
@tvalerio actually i think a promise sounds like it might do the trick. you could create a promise, then execute your async task and, upon completion, deliver a value to the promise. at the end of the test you could simply dereference the promise as to block the thread and keep the build from exiting.
but if use promise i’ll have to change the async task to put a value in the promisse right?
so, yes. you need to be able to deliver to the promise when the async task completes. i’m guessing there’s probably a function or something that gets handed to whatever does the async work.
(defn foo [x] {:post (some? %)} x)
may look like it works but it actually asserts some?
(truthy) and then asserts %
(the value returned from foo
) so it needs to be (defn foo [x] {:post [(some? %)]} x)
although the following actually works too -- but looks odd: (defn foo [x] {:post (%)} x)
(`[%]` works the same as (%)
here -- it's just a sequence of forms to evaluate with %
bound to the function result. This was a surprise to me when I tried it in the REPL!for implementing a forth interpreter, is vector or list better matched for storing the stack ?
@seancorfield yes, that was a typo.
@noprompt I was genuinely surprised it "worked" -- I rarely use :pre
/`:post` so I nearly always have to look up the syntax!
I tend to use vectors as stacks just because pop-n
and peek-n
are easier to implement efficiently on vectors
@bronsa: I think vector is log_32 #-elemes, which is <= 5 in most caeds, but technically log n
Hey @qqq If you want Clojure + Forth, take a look at https://gershwin.github.io/ 🙂
I was actually sufficiently awed by Gershwin at one point that I tried it out at work and wrote a few functions in the "Forth" style of Clojure! 🙂
@qqq no that's not correct, conj/pop/peek on vectors are amortized constant time, not log_32, nth/assoc are log_32 on vectors
@seancorfield: I've lost track the # of afternoons I've lost due to you providing fasicnating links.
(but since it relies on a fork of Clojure and wasn't being updated, I quickly went back to pure Clojure)
@bronsa: conj has to be log_32 time as it has to create a new node for every level of the b-tree
@qqq if you’re interested have a look at factor, joy, cat, and kitten in this space as well.
1. the depth of the tree is log_32 n 2. when we do a conj, we have to create a new node at each level of the tree ^-- which is the two above statements is false ? -- because if they're both true, it's log_32 n time EVERY TIME
Instead of keeping the rightmost leaf in the tree itself, we keep a direct reference to it in the vector header: That's the last block which has been added to the vector head since last blogpost. The reference to the rightmost leaf node is called the tail.
ahwrt "what are they", I've heard the clj vector impl called bitmapped vector trie, not sure if there's a better name
@seancorfield: I feel like we can get most of forth in clojure by defining macro f->
, where it behaves like ->
except
1. if it sees a constant (number, kw, string), it pushes it to the stack
2. all functionsin it take stack as input and produces stack as output
@qqq the thing is though you’ll need to come up with a way to call out how much of the stack to consume for fn’s that have multiple arities.
https://twitter.com/brandonbloom/status/528262785642545153 this is still my favourite impl of stack programming in clojure :)
https://github.com/brandonbloom/factjor for a more useful impl