Fork me on GitHub
#beginners
<
2018-04-24
>
quadron00:04:28

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?

sophiago00:04:41

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.

quadron01:04:42

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.

quadron01:04:17

"idiomatic" clojure functions have a strong bias towards collections. people will pay for this bias.

seancorfield02:04:07

@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?

seancorfield02:04:37

Are you objecting to core functions like map?

quadron02:04:01

I am objecting to core functions like map

seancorfield02:04:28

Well, there are plenty of core functions that work that way and their usage is considered idiomatic Clojure...

seancorfield02:04:10

How would you prefer to write a map that could operate on two collections? Or three collections? Or...?

quadron02:04:22

zip them up first

seancorfield02:04:35

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?

seancorfield02:04:05

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...

seancorfield02:04:11

(`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)

quadron02:04:46

i want people to have maps that are curried by default and compose instead of having transducers.

seancorfield02:04:44

Then you want a different language than Clojure, it seems?

quadron02:04:02

(map (uncurry -) (zip nums1 nums2))

seancorfield02:04:23

That's Haskell, not Clojure.

quadron02:04:46

there is no good reason for Clojure not to have this

seancorfield02:04:05

You can't have variadic functions and currying -- they are not compatible.

seancorfield02:04:24

Haskell chose currying. Clojure chose variadic functions.

quadron02:04:25

that's my point, ditch them

seancorfield02:04:43

Backward compatibility says "never going to happen".

quadron02:04:08

ok, but is it a good idea to ditch them?

sophiago02:04:08

Wow, just returning to this. Can I intervene as someone whose two primary languages are Clojure and Haskell?

seancorfield02:04:54

@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.

seancorfield02:04:34

They are both perfectly valid choices. But once the choice is made -- and production code depends on that choice -- you can't change it.

sophiago02:04:37

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.

seancorfield03:04:42

@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).

sophiago03:04:41

Well, as I noted initially you quite literally cannot curry a variadic function. So, yes, it's totally ingrained.

1
sophiago03:04:12

But you also can't typecheck a variadic function, at least using any common methods I'm aware of.

sophiago03:04:21

I don't see any point in reifying argument order if you can't verify it.

sophiago03:04:25

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.

sophiago03:04:01

Like just think about the existence of a function called zipWith7 in your standard library. Really think about how nuts that is.

sophiago03:04:40

Someone decided long before Clojure was even created that seven lists was a reasonable number to stop at 😛

sophiago03:04:07

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.

sophiago03:04:27

Ok, I'm done. Apologies for coming back and hijacking.

sophiago03:04:33

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: ->>

quadron03:04:11

I doubt this pointfree style would get you far with the core.async comonad

sophiago03:04:57

What in core.async is a comonad?

sophiago03:04:54

Unrelated, though, I'm pretty sure pipeline does exactly what you're describing.

seancorfield03:04:25

@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.

sophiago03:04:16

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.

sophiago03:04:58

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.

sophiago03:04:04

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).

sophiago03:04:35

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...

sophiago03:04:56

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.

sophiago03:04:21

I'm always here for more than you wanted to know 😛

sophiago03:04:28

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"

seancorfield05:04:42

(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?)

seancorfield05:04:01

My main academic FP work predates Haskell. My PhD in FP language design and implementation was mid-80's 🙂

😎 1
sophiago06:04:36

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.

😆 1
quadron20:04:10

@U2TCUSM2R would you elaborate on the order in argument lists?

sophiago20:04:56

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.

quadron21:04:54

that makes perfect sense

👍 1
justinlee02:04:29

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)

justinlee02:04:22

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”

mfikes02:04:59

@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.

justinlee02:04:44

@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

justinlee02:04:06

Oh wait that’s not right either.

mfikes02:04:09

Oh, @lee.justin.m in that case the name of the function is still -send

justinlee02:04:32

this works more like a multimethod (?)

mfikes02:04:28

Here is an example

(let [x (reify ICounted (-count [_] 11))]
  (-count x))

mfikes02:04:52

It works more like regular Java / OO polymorphic dispatch (on the type of the first argument)

justinlee02:04:00

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

mfikes03:04:17

Contrast reify above with this

(deftype X [] ICounted (-count [_] 10))
(let [x (->X)]
  (-count x))

mfikes03:04:22

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

mfikes03:04:19

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

justinlee03:04:20

different question: when one uses the (:import [ XhrIo]) 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.

sundarj07:04:38

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

justinlee15:04:32

Ah! Thank you. I actually understand this now!

justinlee03:04:16

(and by the way looking at the source for count was illuminating. thanks for that)

callum04:04:29

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…