This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-04-02
Channels
- # arachne (1)
- # bangalore-clj (3)
- # beginners (4)
- # boot (6)
- # cider (3)
- # cljs-dev (14)
- # cljsrn (2)
- # clojure (309)
- # clojure-ireland (1)
- # clojure-russia (7)
- # clojure-spec (10)
- # clojure-uk (5)
- # clojureremote (2)
- # clojurescript (68)
- # cursive (7)
- # emacs (7)
- # luminus (3)
- # lumo (21)
- # off-topic (14)
- # om (1)
- # onyx (53)
- # perun (3)
- # re-frame (4)
- # slack-help (14)
- # unrepl (56)
- # untangled (3)
@qqq in general transients are meant for efficient adding and removal of data. They don't support much besides get
, assoc
, and without
(or the equivalent vector/set functions). So it's probably best to do stuff like checking for empty before converting to a transient.
In addition, once you convert a transient to a persistent collection, you can't re-use the transient, you have to create a new one.
@slipset @tbaldridge : got it; thanks!
would someone mind explaining why I'm getting:
(clojure.core/unquote x)
when I run:
(apply (fn [x] (quote ~x)) "1")
It's because (quote ~x)
produces the literal ~x
, i.e,. (clojure.core/unquote x)
(so your anonymous function always returns that literal value)
but, I thought that when unquote is inside of a quote, it is supposed to evaluate whatever it precedes? (shown here: https://clojuredocs.org/clojure.core/unquote#example-542692d5c026201cdc3270af)
Yup. You got it!
is there every a reason to use loop/recur instead of reduce for performance reasons? or is this just silly micro optimizations that are pure evil
it's not even an optimization, if you are visiting items in something sequential in order
@noisesmith : don't oyu save the cost ofa function call ?
anyone here from the ClojureTV youtube channel ?
It would be really cool if comments on the video's could be turned on
Newbie here. I'm having trouble understanding keywords. If i'm right, they are mostly used in maps like keys in key-value pairs.
#{:a π 2 3}
sets are not key-value pairs
why would i add these keywords in sets anyway?
You can do some funky things with sets, but a common way to use them is a validation tool
Symbols, which are words etc without the colon in the front, are evaluated to their value
So defn
is a symbol that evaluates to a macro, +
is a symbol that evaluates to a function etc
Keywords aren't evaluated, and they're also descriptive, so they're especially good in maps
Since you can easily check the :age
or :first-name
of something in a map, and it's easily readable to what you're getting out of it
I haven't got to symbols and macros yet though
If I get you they are some kind of values, not value holders, like variables in other languages, right?
zivanovicb: note that kws are also functions; you can apply them to maps: (:foo {:foo 9}) => 9. and maps are fns too, so it works the other way around as well. how cool izzat?
Hm okay I'll get into it soon
So far I've been playing with
(def name "Value")
and to me it looked something like variable
A basic map might be {:name {:first-name "John" :last-name "Smith"} :age 25 :role :user}
Totally
I wasn't thinking about them as values
so you helped a lot, thanks!
what's the best practice for creating core.async channels ? creating them in the main function and passing them to other smaller functions ? or each function create its own channel and return them ? or create global channels ?
@lxsameer the first. More parameters often equals more flexibility.
βnamespacingβ a map appears to prevent checking for duplicate keys:
miraj.html.test-meta> (def m #::h{:foo 0 :foo 9})
#'miraj.html.test-meta/m
miraj.html.test-meta> m
#:miraj.html{:foo 9}
is this a bug or is it expected?sounds like a bug @mobileink . Do you have a JIRA account to file it?
@tbaldridge thanks man
@emccue yes, it's possible, but being explicit about recur points has its benefits, like being able to throw compile errors when you write the tail call wrong
In school we use a theorem proving software called ACL2s, which uses a barebones common lisp
I'm writing a "defunc" macro with the intent that I can simply copy paste code that I proved to work into a clojure file, and move it back into acl2s if I need to prove modifications
The language has the assumption of automatic tail call optimization, so I figure if it is possible I might as well include that as part of the macro
does it only assume self-call optimization, or does it assume generalized tail call? for the latter you'll need trampoline
trampoline might be a simpler fix for converting the self calls too? (maybe)
anyway - a macro can easily detect self calls in the body form by checking symbol equality with the binding name, if it is provided both of course
I'm imagining it would need to do a tree walk on the body form, and detect tails that are self calls
you could probably adapt tree-seq to return a series of tails
ultimate, but I've never tried community, we already had ultimate before starting with clojure
I use both, sine my client had an ultimate license, haven't noticed much difference
SQL tooling is lot better in ultimate and only ultimate has less/sass syntax highlighting
I quite common heard the follow sentence regarding side-effects β isolate the side-effects β what that means ? what isolate means on that context ( new namespace ?? my computation should not have side-effect ? )
make something (usually a function) that does the side effect without any data logic
then, all your logic and data manipulation can be separate definitions that don't have side effects at all
this is an ideal - it gets tricky if you need the result of a side effect to drive further logic, for example...
but the more side effects and computation of data can be isolated from each other, the more testable and coherent your code is likely to be
> make something (usually a function) that does the side effect without any data logic so my function must receive as argument the βside-effect isolatedβ function
or just the data it needs
oliv it's not super different from the idea of separation of concerns
eg. if your object both constructs a csv structure and outputs to a file, it's less reusable
right - I'm not making a direct identity / equality there - just saying there's a similar type of design
(at a higher level of abstraction)
It would be the same thing, just in your DAO, you also seperate flow functions from IO read functions and IO write functions
My understanding is that it's useful have functions that do nothing except read data from IO, functions that do nothing except write data to IO and functions in the middle that implement you business logic by transforming data. That way the functions in the middle can be trivially tested.
yes, this is exactly it
mobileink no transformation of data
that too
it involves data that already existed - produced by a pure function
if it gets data back, we don't touch it in the input function, we transform it using a pure function
impure isn't just i/o of course, this also applies to using impure apis that have implicit state
instead of (defn make-csv [a-hashmap a-filename] ...) you have (defn to-csv-string [a-hashmap]...) to-bytes and (defn [write-file] bytes)
the data transfomations don't mix with the io
(let [input (get-data :from "somewhere") ;; just gets data -- no logic
data (pure-transformation input)] ;; pure and testable
(store-it :somewhere data)) ;; just writes data -- no logic
Like that, you mean?that's what I am talking about, yes
mobileink yes that function is easy to write, but not easy to test, and becomes a bit harder to write if you want to use threads
that will be inside one βnot pureβ function , right
(let [input (get-data :from "somewhere") ;; just gets data -- no logic
data (pure-transformation input)] ;; pure and testable
(store-it :somewhere data)) ;; just writes data -- no logic
I mean, not pure because will read/right from outsideyeah- having to be impure at the outer edge is common
so really I should have introduced the three categories - only impure, only pure, and the glue that makes your app work at the "top"
Not possible to be pure if you need IO for the functionality, so the idea is to have the IO all in one place with as little of anything else, so it's easier to manage.
and it's impossible to do this perfectly - it's a guideline
ok, you mean sth like "sequester your side-effectful stuff in routines that do not also do functional stuff with it"? split out what you do with it from how you get it?
something like that, yes
pragmatically, I do it to make testing easier (no need for mocks or with-redef or whatever, no need for unit test coverage if all you do is invoke an external api as documented)
just test if my pure stuff generates the data that api wants
i tend to agree. problem is clojure is full of side-effects! like def and defn. π
well, for starters, never call those from inside other code
and never put side effects in a def body
@mobileink but yes, there are trickier cases - like I said, it's not a law I have to follow perfectly, but a guideline that consistently improves my code if followed
well, that depends on what kind of peogramming you're doing. lately i've been doing lots of metaprogramming, where side-effects are central. i don't call def or defn, but lots of calls to other side-effectful stuff like intern and alter-whatever. tons of fun!
another kind of example that might be instructive: input is a large matrix. output is another matrix, with different values in many locations, and the only way to write this is to loop through the indexes one by one. So I create a mutable matrix (in core.martix) and fill its elements one by one. Then I return it. The internal code used side-effects, but outside of the function, all that you can see is an input and an output. (Transients are kind of sort of like this, I think.)
@noisesmith agreed.
@mars0i - if the mutation happens inside your local scope, and no caller can hear it, did it make a sound?
@mars0i that doesn't smell like side effects to me, just (internal) transformation, just like assoc on a map. unless you're actually altering the input.
Well, ya, but I mean
(defn get-name [] (rand))
(defn welcome [name] (str "Welcome " name))
(defn set-msg [msg] (println msg))
(defn greet [] (set-msg (welcome (get-name))))
Get name and set-msg are not pure, in the sense they don't map deterministic input to output.
Is there an equivalent of Miniboxing for clj? I have a large cache of 8M items and I'd like it to be memory-efficient http://scala-miniboxing.org/'
Well, yeah, no sound is the point. But I had a wild party going inside that function. And I felt a slightly dirty. But I accepted it.
@didibus right - and on that grounds I wouldn't be opposed to merging get-name and set-message, but I still want welcome to be its own thing so I can test it without mocking rand or io
(or reuse it with different input or output sources)
@noisesmith Ya, trivially here I'd say that's fine. But I was saying that ideally, you also want to split the IO read (get-name) from the IO write (set-msg) and from the control flow (greet).
right, you might want to change those individually, makes sense
I tried to take that to it's (seemingly) logical conclusion with a library I created called Engine but it turned out that non-trivial application code often has to conditionally produce quite an array of side-effects so it gets very hard to keep them fully separate.
The readme explains why I no longer use it.
fascinating - yeah, eventually things get trickier if you nest and compose things
But it was an extremely educational experiment π
Interesting. You need a balance, I think it's important to recognise when a function is getting hairy, and that's when you should break it up as such.
@seancorfield very interesting.
problem is we have no model for side-effectful computing. compositon really only works for turing/lambda/etc. models.
there are monads, but those don't come naturally in clojure - and then proper effect systems, which I don't get at all
yeah, tried and failed to grok effect systems a few times. btw, have y'all looked at idris? 1.0 just came out. re: monads, love/hate. not really a computational idea, imho, just a way to organize code.
@mobileink Ya, my understanding is most models, IO or uniqueness types, they just delay the IO to the last possible moment. So you'd do defn welcome [IO] (str "Welcome " IO)
. And welcome would be pure because IO is not executed yet. If we peak inside IO, before calling welcome it would be: read-line
, and after calling welcome it would be #(str "Welcome " (read-line))
. So welcome is pure, it mapped IO to IO.
Does anyone here have used http-kit extensively for handling web sockets before? I have some questions regarding its scalability π
@olfol yes, I use http-kit web sockets in production
(though I am strongly considering switching to aleph as it is better maintained)
threads are fine too
@didibus : hmm, yes and no, good example of ambiguity. 1st, since IO is an arg it will be evaluated before welcome is evaled. welcome itself has no side effects. so the Great Existential Question is: if you apply a pure fn to impure args, is the fn really pure? or: does the meaning of a fn include the meaning of its arguments?
@mobileink Oh ya, that doesn't work in Clojure, you need a fully lazy evaluation model. So if I'm correct, in languages that do this, the program is evaluated only when it exits the main function. What the program does is build the chain.
laziness is really fascinating. i have not entirely wrapped my head around it, mainly, i think, because clojure conflates lazy eval and infinite data, two very different ideas.
@mobileink Could welcome be memoized? I think it could, if you consider that what it does is concatenate two computations together. Imagine it worked on code, it takes code that does IO and injects it inside code that uses IO. So every time it receives the code read-line as input, the output will always be a function which when evaluated does (str "Welcome " (read-line)).
i'll quibble about whether it concatenates "computations". IO, strictly speaking, is not a computation, at least not on the turing/lambda model.
i'm not sure what it would take to memoize a routine that does IO. you only memoize stuff when you know the next call will produce the same result as the previous call, no?
Think of it like this:
(defn welcome [io] (str "(str \"Welcome \" io)")
(defn main [] (eval (welcome "(read-line)")))
maybe we have different ideas about memoization. (read-line) will presumably produce different results, usually. what do you want to memoize?
The program is not pure, but welcome is. So we pushed the impurity all the way up to the main function. So everything except main is pure.
In my example welcome takes a string. So it maps the input string "(read-line)"
to the output string "(str \"Welcome \" (read-line))"
You can memoized this. Eve tu time the argument is "(read-line)" the output is the same. This is deterministic.
Has anyone else noticed IntelliJ/Cursive is about 4x slower in Windows 10 vs. even Ubuntu in a VM? Repls seem to take forever to load
@bradford: I was going to try cursive; but after your msg, I'm going to stick with emacs for a bit more
@qqq Cursive is fantastic, I'm mostly puzzled re: windows. It's not the editor itself that's as slow as compiling/repl
Random is IO, but welcome simply concatenates its argument instruction to its own. Only once we get to main does it get evaluated in a non deterministic way. So welcome is a deterministic pure function, even though it builds an IO computation.
@didibus you can memoize anything, all you have to to is remember previous inputs, no matter how they were produced. but "remembering" is already a side-effect.
@mobileink You can only memoize pure functions. So I'm using it as a way to prove that welcome is pure
(defn welcome [io]
(str "(str \"Welcome \" " io ")"))
(def mem-welcome (memoize welcome))
(defn main []
(load-string (mem-welcome "(rand)")))
(main)
Ok, apology about messing the channel, its not possible for me to run this here. But try it in a REPL
In my example, I've turned my code fully pure all the way to main. Main is the only impure function. In Haskell, this is pretty much what happens when you use IO, but instead of using strings to assemble the computation, it uses Monads. At least, that's how I understand it, I'm no expert.
@didibus But now you're just constructing a giant string as your program and then, in one fell swoop, evaluating it. Saying that's a "pure function" is rather tenuous since you're just building strings, not actually doing any computation at all.
No, it's really not.
I'm constructing a big giant string as a cheap way of building up computation which is ran only from within main.
In Haskell -- and where you have lazy evaluation in Clojure -- you have actual syntactically valid, compiled code being executed to produce structures that, when realized, yield the next step in the evaluation.
My progam is not one giant string though, my program evaluates to a data structure which when realized yields the computation which has side effect in it.
That's... a ridiculous argument, I'm afraid.
A string is not structured. Not beyond having a sequence of characters. There's no "code structure" in that.
But Haskell would do this, everything will evaluate one giant continuation of functions to functions, and when main returns, that will get interpreted, and if there is any side effect, it will be executed at that moment only.
i think you missed the part about "referentially transparent" in memoize. "rand" is not rt, so it is not a candidate for memoize.0
Haskell is not "interpreting" anything.
I have almost no words to respond to what you're saying. It's just daft π
@mobileink That's the beauty of it. In a way, welcome uses (rand) for IO, but it does not really, because welcome is a function from IO to IO. My strings are a metaphor for type IO.
I don't think you understand how Haskell works...
Maybe, I'm wrong, about haskell. I'm not an expert true. But I wanted to show an example of a model that can move all side effects to the top level of execution. Using strings is nasty, but it does work.
It doesn't "work" because it's not even vaguely doing what an actual program would do.
This is a pointless discussion.
In Haskell, code is compiled -- so it is all checked to be valid -- so functions are real functions that operate on real data structures. The laziness is due to expressions compiled as thunks. IO is just part of the type system that makes you write side effects in a particular way.
@didibus: I applaud your boldness. all progress depends on unreasonable people. but, respectfully, i think you need to think about it some more. and take folks like @seancorfield very seriously, he's tryinahep.
If you wanted to model Haskell semantics, you'd write code as (fn [] (some expression))
and you'd have extra (f)
style function calls in contexts where a value was needed.
Maybe this will be clearer:
(do
(defn welcome [io]
(fn [] (str "Welcome " (io))))
(def mem-welcome (memoize welcome))
(defn main []
((mem-welcome rand)))
(println (main))
(println (main))
(println (main)))
Right, now you have actual code, based on thunks.
In my perspective, this is the same code pretty much, in the sense that welcome concatenates computation.
The evaluation process is very different with that.
That's a very odd perspective, to describe it as "concatenates" computations...
Haskell can have arguments evaluated eagerly (as well as lazily). You can't really think of it as a "concatenation" model.
Hum, I wan't really trying to explain Haskell. More so how you can model a program that does side effects in a way where all functions except the main entry one is pure. I'm actually not sure how Haskell works. But, can you eagerly run IO in Haskell?
IO is just a type.
> In Haskell, the top-level main function must have type IO (), so that programs are typically structured at the top level as an imperative-style sequence of I/O actions and calls to functional-style code. The functions exported from the IO module do not perform I/O themselves. They return I/O actions, which describe an I/O operation to be performed. The I/O actions are combined within the IO monad (in a purely functional manner) to create more complex I/O actions, resulting in the final I/O action that is the main value of the program.
As long as you have a way to describe an IO operation, which you have with strings or fns, and a way to combine them, like using string concatenation if describing them with strings, or higher order function composition if using fns, then you can do what IO Monad does.
didibus: sorry dude, i think you may have missed the boat, but i'll give you bonus pts for trying.
Now if you give it the rand function as input, it will always return an equal output.
Since you want to tie this to Haskell, understand that if you want welcome
to accept an impure function, then welcome
also becomes impure.
If you want welcome
to be pure, you would have to declare a non-IO argument type and couldn't pass rand
to it.
A function is only pure if all the things it calls are pure.
I define pure as equal input gives equal output all the time. At least in this sense, welcome is pure. Maybe not in the way Haskell defines purity
If welcome
calls io
, and io
is not pure, then welcome
isn't pure.
Ya, but my welcome implementation does not call rand. It simply creates a function that can call rand.
Right, but we're talking at cross-purposes.
I give up. I should have stopped trying to have this conversation with you half an hour ago...
Yup. Clojure != Haskell. In so many ways π
I'm not sure what you're disagreeing about? Are you saying my welcome function is not pure? Or that Haskell does not work as my example. Because I agree with the latter 100%.
Strings and concatenation. That's what I was disagreeing with. Your mental model of how it all works is... off...
I see, I think that was just a bad example. I know that's not how it is implemented at all. It would be ridiculous. I thought it would easier for others to understand the underlying principle, that the IO execution is delayed to the very very end of the program.
Then at the top, in main, you go through these IO operations in the order they were describes and you execute them imperatively
As I understand it, you can not eagerly evaluate something that depends on IO, since that would break purity.
I hope I wasn't rude, I might be completely wrong about all this, just trying to communicate my understanding.
you're cheating. memoize only applies to referentially transparent fns. you want to memoize rand. rand takes no args, is not referentially transparent. can you at least see why the very idea of memoizing it is preposterous?
Welcome can be memoized because if given the same FN, it will always returnt he same FN.
This is the trick in that model, that's how you make welcome pure, in a way where you still feel like you are doing IO inside of it.
@bradford Is starting a REPL using lein REPL slow as well? Thatβs pretty much all Cursive does
@didibus of course. they both return the same fn. what's surprising? you defined welcome as a 2nd order fn.
@cfleming (and @bradford ) I think this is down to Windows... I find starting a REPL in Windows is very slow (starting a REPL in WSL on Windows is much faster). I suspect it's to do with virus checking but I haven't managed to figure out the right settings to tell the malware stuff to not do whatever slows down Java π
For those curious: fixed it. Actually a "feature" of my mobo. It automatically downclocks the Ryzen to really low power. I enabled "Gaming Mode" and now it's INSANE-O fast. @cfleming @seancorfield
Interesting. So there's still hope I can persuade my Windows 10 laptop to start a REPL faster then π
(it's why I work in WSL / Ubuntu nearly all the time now)
@didibus of course you can do this, but that's not really what memoize is for. it's not t really for higher-order memoization, afaik.
@mobileink Ok, I think my intent has been lost. I was only trying to explain a model of programming where you can isolate IO in one place. So in my example, (rand) would be the IO, you can replace that with a (read-line) or a remote request, etc. I have a function welcome which looks like it takes IO and concatenates it on a string so that it returns "Welcome <input-from-io>". But it never actually performs any IO. This is the beauty of the model. So you can code as if you were using IO everywhere, but when your program runs, IO is actually only performed in one place.
wrapping IO in higher order fns does not solve the problem, it just moves it to a different place. this is also true of haskell's IO monads.
Ya, I know. There's no problem to solve though. The requirement is to have side effect. Unless you model the entire world as a program, this is incidental complexity. The only model I know of to try and manage that complexity is to isolate all the IO in one place so it is easier to manage.
this is what the previously mentioned effect systems try to do
like i said i applaud your efforts. but having wrestled with this for years, my advice is: don't get your hopes up. π