This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-04-24
Channels
- # architecture (7)
- # beginners (73)
- # boot (4)
- # cider (48)
- # cljsjs (7)
- # cljsrn (27)
- # clojure (206)
- # clojure-boston (2)
- # clojure-italy (21)
- # clojure-nl (8)
- # clojure-spec (7)
- # clojure-uk (94)
- # clojurescript (126)
- # clojutre (7)
- # core-async (3)
- # cursive (7)
- # data-science (1)
- # datascript (4)
- # datomic (6)
- # duct (1)
- # emacs (19)
- # figwheel (1)
- # fulcro (31)
- # graphql (13)
- # jobs (5)
- # jobs-discuss (42)
- # keechma (4)
- # leiningen (10)
- # luminus (3)
- # mount (2)
- # nyc (3)
- # off-topic (37)
- # om-next (3)
- # onyx (45)
- # pedestal (2)
- # re-frame (4)
- # reagent (2)
- # reitit (16)
- # shadow-cljs (118)
- # spacemacs (10)
- # tools-deps (8)
- # vim (20)
does anybody else think that functions with dynamic arity are a bad idea? Is it not simpler to just have fixed arity and currying by default?
Currying is fundamentally incompatible with variadic functions. Also, its primary purpose is for typechecking so doesn't really make sense in a dynamic language. Rich has often alluded to order in argument lists as being inherently problematic, which I very much agree with (I don't with everything he says), but currying just reifies that rather than providing a solution. Before I even thought of it this way, I had a vague intuition based on practical issues I encountered and then found @U064X3EF3 saying the same thing in a blog post: essentially that in most cases functions should either be unary or variadic. They generally compose better.
that's my whole point. On the one hand the freedom to define variadic functions is well, freedom. on the other hand, we are giving away a lot of power when we choose not to know the count of function arguments. we could change the order of arguments by combinators, that's a non-problem. I strongly disagree with your point about composition. For instance, I suspect transducers were introduced as a "special" currying mechanism precisely for the sake of composition.
"idiomatic" clojure functions have a strong bias towards collections. people will pay for this bias.
@UA2JG2Y11 what do you mean by "dynamic arity"? Do you specifically mean the & more
notation, or do you mean any function with more than one arity?
Are you objecting to core functions like map
?
Well, there are plenty of core functions that work that way and their usage is considered idiomatic Clojure...
How would you prefer to write a map
that could operate on two collections? Or three collections? Or...?
So instead of (map f coll1 coll2 coll3)
, you'd want people to write (map f-triple (some-fn [coll1 coll2 coll3]))
where f-triple
accepted a single argument containing a vector of three elements? And some-fn
would be what? It would need to be something that took an arbitrary collection-of-collections and produced a collection-of-tuples?
Instead of computing pair-wise numeric difference by doing (map - numbers1 number2)
I'd have to write a diff
function that accepted a pair and returned the difference between its first and second element. And I'd have to pay the overhead of walking through both numbers
and numbers2
and zipping them together before I could map
my new diff
function over them? Seems like a lot of additional (and unncessary) work...
(`some-fn` would need to be a unary version of interleave
-- and would need to be lazy so that you could still map
over infinite lazy sequences)
i want people to have maps that are curried by default and compose instead of having transducers.
Then you want a different language than Clojure, it seems?
That's Haskell, not Clojure.
You can't have variadic functions and currying -- they are not compatible.
Haskell chose currying. Clojure chose variadic functions.
Backward compatibility says "never going to happen".
Wow, just returning to this. Can I intervene as someone whose two primary languages are Clojure and Haskell?
@UA2JG2Y11 All I'm saying is that languages make different choices -- you can't change horses mid-stream. The decision was made ten years ago.
They are both perfectly valid choices. But once the choice is made -- and production code depends on that choice -- you can't change it.
Currying is not by any means an independent choice from everything else in language design. Imo it's almost necessary for Haskell and equally almost purposeless in Clojure.
@U2TCUSM2R I agree (but that's a more subtle argument -- the basic choice of currying vs variadic, once made in a language, can't be changed).
Well, as I noted initially you quite literally cannot curry a variadic function. So, yes, it's totally ingrained.
But you also can't typecheck a variadic function, at least using any common methods I'm aware of.
This discussion also seems to have shifted to built-in higher order functions vs. defn
. In the former context the fact that dynamic typing allows them to be variadic (although it seems few other dynamic languages take advantage of this?) is a huge win.
Like just think about the existence of a function called zipWith7
in your standard library. Really think about how nuts that is.
Someone decided long before Clojure was even created that seven lists was a reasonable number to stop at š
Using a practical example, I literally had a coworker who generated generic tuple instances for a Haskell Postgres library using the C preprocessor. He stopped at 26 for the obvious reason.
I lied. At least one more point: the pointfree style you get from currying is replicated in Clojure through one very simple macro anyone could have implemented themselves: ->>
@U2TCUSM2R At the risk of hijacking this thread further... as a Haskeller, what do you think about the use of monads et al in a language like Clojure? We have algo,monads
but it's very rarely used. I've written monadic code occasionally with Clojure (my Engine OSS library was in that style) but pretty much every time, it ends up feeling very non-idiomatic and clumsy in Clojure.
I have a strong opinion that they're useless. I mean, there are forms we all use that just happen to be monads but that's not really significant to even think about. And fwiw, I looked around a bit in algo.monad
a while back and wasn't interested by anything. I remember I was using ad hoc continuations in a cljs library at the time and imo they were better than the continuation monad in that library.
There's also the issue of how monads are just so ridiculously horribly taught. The type we use in programming, as opposed to category theory where they're actually quite an advanced concept, should actually be incredibly intuitive to Lisp programmers.
If you're at all familiar with continuation passing style or have written a simple lisp interpreter with lexical scope, just think of the additional environment variable. That's your continuation and the continuations monad is a "strong monad" from which the familiar "weak monads" we tend to use in Haskell can all be constructed (this is somewhat related to "Hask," the category of Haskell types, being a bicategory or "weakly-enriched 2-category...this is one reason the ML folks bash on Haskell).
Then you can think of Reader as just reading from the environment, Writer as writing to it, and State as a restricted version of Writer. IO is actually not a monad, it's compiler magic they make a monad to compose with the actual ones. That obviously doesn't help with explaining this stuff...
I've also grown to use monads in Haskell differently than many Haskellers. Michael Snoyman talks about this pattern a lot and it should be already familiar from using atoms/agents/refs in Clojure. Monad transformers are really obnoxious so it makes sense to consider the necessity of each one. StateT is technically pure, but has all the problems of mutability with a ton of added complexity just to say it's pure. Instead you can just use an IORef/MVar/TVar in a MonadIO context (I default to MVar, which is probably the closest to a Clojure atom). Then ReaderT handles tracking these actions between functions without actually modifying the environment.
Actually, though, I just spit all that out because I'm writing a blog post where I tack that bit on at the end as a half-joke: "btw, you just read a monad tutorial"
(sorry, got distracted by TV š ... yeah, it's been a long, long time since I did CPS, category theory, and all that stuff -- like maybe 35 years?)
My main academic FP work predates Haskell. My PhD in FP language design and implementation was mid-80's š
Cool. I never knew you had an academic background (I suppose it says something I assume not by default in these parts). And in the UK too so that's extra serious: the Romans had to build a wall to keep the type theorists out.
@U2TCUSM2R would you elaborate on the order in argument lists?
Just that you pass arguments to functions in a given order, which I find inherently problematic since it's not first-class. You can make it first-class, though, with apply
, which is dependent on the allowance of variadic functions (since they represent arguments as collections, just as they are syntactically as well as in compilation). Or just rest args, which are literally what you consider harmful. Currying doesn't make them first-class; its primary purpose is for typechecking.
what does reify exactly do in clojurescript? (Iām trying to puzzle through https://github.com/funcool/httpurr/blob/master/src/httpurr/client/xhr.cljs#L62)
the docstring says a lot about reify without ever saying what itās for. in particular I cannot parse this sentence ārecur works to method heads The method bodies of reify are lexical closures, and can refer to the surrounding local scopeā
@lee.justin.m Is your question ClojureScript-specific? In general, it lets you create an object that implements a protocol, on the fly (as opposed to employing deftype
or defrecord
.)
I think the ārecurs works to method headsā fragment probably needs some copyediting, but in general if you recur
in the implementation of a protocol method, without a loop
, it goes where youād expect (the method head, just like it would if you were using recur
in a function, with the only trick being that you donāt pass the āthisā first argument in your recur
call.
@mfikes thanks much. I have had the hardest time wrapping my head around deftype
and defprotocol
. The basic gist of this is that you end up with an object-like thing that allows you to use the dot-accessor method invocation (whatever that is called). For example once this particular piece reify
returns something that you can do (.send result-of-reify)
because the protocol being implement here requires a send
method
Oh, @lee.justin.m in that case the name of the function is still -send
It works more like regular Java / OO polymorphic dispatch (on the type of the first argument)
okay right. thanks. that makes sense here since they are implementing an async network call in different ways, and then the top-level function internally does a (-send client ...)
and itās your job to hand it the right client to that top-level
Contrast reify
above with this
(deftype X [] ICounted (-count [_] 10))
(let [x (->X)]
(-count x))
Incidentally, if you call count
on an object in ClojureScript, it will employ a mixture of cond
-based dispatch (for example, if it it satisfies string?
it will do .-length
) and protocol based dispatch, calling -count
, which will then do the right thing based on the myriad ICounted
collection implementations, as well as nil
If you are curious about that bit about recur
and dropping the implicit this
, here is a post http://blog.fikesfarm.com/posts/2017-06-27-clojurescript-method-head-recur-in-defrecord.html
different question: when one uses the (:import [
syntax, is that XhrIo
object constructed already? in the cljs-http
library, they use the same object and call a constructor on it first (https://github.com/r0man/cljs-http/blob/f4b3e6b7d8bef925741b132801282a5469ac54c4/src/cljs_http/core.cljs#L53) but in this library I swear they never do that (https://github.com/funcool/httpurr/blob/8b3e23240c6c4e2a03dcae6f7a9af4fce6b98476/src/httpurr/client/xhr.cljs#L12). It doesnāt seem like that could work unless the import syntax works differently than I think it does.
yes, the XhrIo
object is already constructed; imports can only import an existing object. the thing you're missing is that this object has both static and instance methods: https://google.github.io/closure-library/api/goog.net.XhrIo.html
Hi there, Iām having trouble figuring out how to run tests with midje.repl
while excluding a specific subset of them. For example, with lein-midje
I can run lein midje :filter -smoke
to run all tests which donāt have :smoke
specified in the test metadata (this works well).
As far as https://github.com/marick/Midje/wiki/The-compendium describes, I should be able to replicate this behaviour using midje.repl
by running (load-facts :all (complement :smoke))
. However, it just goes ahead and runs all the tests, including the :smoke
testsā¦