Fork me on GitHub
#clojure
<
2017-04-02
>
tbaldridge00:04:24

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

tbaldridge00:04:48

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.

jkieberk02:04:33

would someone mind explaining why I'm getting: (clojure.core/unquote x) when I run: (apply (fn [x] (quote ~x)) "1")

jkieberk02:04:26

I thought the point of unquote is to resolve whatever is unquoted?

seancorfield02:04:09

It's because (quote ~x) produces the literal ~x, i.e,. (clojure.core/unquote x)

seancorfield02:04:24

(so your anonymous function always returns that literal value)

jkieberk02:04:18

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)

jkieberk02:04:10

oh, picard-facepalm I was confusing with '`

seancorfield02:04:59

Yup. You got it!

qqq05:04:04

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

noisesmith06:04:34

it's not even an optimization, if you are visiting items in something sequential in order

didibus06:04:15

Question: Is it normal for exercise-fn to return examples that don't conform?

qqq06:04:04

@noisesmith : don't oyu save the cost ofa function call ?

bronsa09:04:14

looping over vector/map items is often faster via reduce than loop recur

mozinator209:04:59

anyone here from the ClojureTV youtube channel ?

mozinator209:04:22

It would be really cool if comments on the video's could be turned on

dominicm09:04:44

Also the Clojure/West 2017 playlist has the Mark Engelberg talk twice.

zivanovicb11:04:48

Newbie here. I'm having trouble understanding keywords. If i'm right, they are mostly used in maps like keys in key-value pairs.

zivanovicb11:04:21

#{:a πŸ˜› 2 3}

zivanovicb11:04:50

sets are not key-value pairs

lsenjov11:04:50

You can use backticks for inline code, btw

zivanovicb11:04:11

why would i add these keywords in sets anyway?

lsenjov11:04:15

You can do some funky things with sets, but a common way to use them is a validation tool

lsenjov11:04:52

Instead of (or (= :a %) (= :b %) (= :c %)), you can instead use (#{:a :b :c} %)

lsenjov11:04:00

That's one possible use of them

lsenjov11:04:34

Keywords in general though, are just values

lsenjov11:04:01

Symbols, which are words etc without the colon in the front, are evaluated to their value

lsenjov11:04:35

So defn is a symbol that evaluates to a macro, + is a symbol that evaluates to a function etc

lsenjov11:04:02

Keywords aren't evaluated, and they're also descriptive, so they're especially good in maps

lsenjov11:04:34

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

zivanovicb11:04:01

I haven't got to symbols and macros yet though

lsenjov11:04:11

You don't need to, I'm just saying how they differ

zivanovicb11:04:41

If I get you they are some kind of values, not value holders, like variables in other languages, right?

mobileink18:04:48

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?

lsenjov11:04:53

Something like that, yes

lsenjov11:04:02

Symbols are basically variables

lsenjov11:04:14

(apart from the fact they almost never change)

lsenjov11:04:33

Keywords are values, yes

zivanovicb11:04:45

Hm okay I'll get into it soon

zivanovicb11:04:49

So far I've been playing with

zivanovicb11:04:57

(def name "Value")

zivanovicb11:04:03

and to me it looked something like variable

lsenjov11:04:08

When you start getting into it more though, you'll start using a map

lsenjov11:04:17

And let's say the map represents a person

lsenjov11:04:53

A basic map might be {:name {:first-name "John" :last-name "Smith"} :age 25 :role :user}

lsenjov11:04:32

Just want to point out that maps can be complex, and keywords can also be values

lsenjov11:04:47

:role might be :user or :admin or something else

lsenjov11:04:39

Does that make sense as to why you might use keywords?

zivanovicb11:04:19

I wasn't thinking about them as values

zivanovicb11:04:35

so you helped a lot, thanks!

lxsameer14:04:04

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 ?

schmee14:04:44

does anyone know of a good way to do RPC with clojure?

schmee14:04:02

anyone have experience using Finagle with Clojure?

tbaldridge14:04:14

@lxsameer the first. More parameters often equals more flexibility.

lmergen15:04:07

you could use component/mount/integrant for maintaining the core.async channel state

lmergen15:04:32

but whether that’s justified depends on your overall project

mobileink15:04:51

β€œ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?

mobileink15:04:33

by contrast this throws a duplicate key exception: (def m {::h/foo 0 ::h/foo 9})

ghadi15:04:04

sounds like a bug @mobileink . Do you have a JIRA account to file it?

mobileink16:04:45

@ghadi turns out I do.

bronsa16:04:22

i've attached a patch and fixed it in tools.reader

mobileink18:04:42

bronsa: 30 minute turnaround! amazing.

ghadi17:04:06

@bronsa gotta fix EdnReader too

bronsa17:04:52

oh i forgot ednreader supported that

emccue17:04:11

Is it possible for a macro to automagically replace tail calls with recur?

emccue17:04:22

Is that type of introspection possible?

emccue17:04:37

(assuming no special context trickery)

tbaldridge17:04:08

@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

emccue17:04:42

It's a specific use case, not for general functions

emccue17:04:08

In school we use a theorem proving software called ACL2s, which uses a barebones common lisp

emccue17:04:21

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

emccue17:04:25

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

noisesmith17:04:26

does it only assume self-call optimization, or does it assume generalized tail call? for the latter you'll need trampoline

noisesmith17:04:48

trampoline might be a simpler fix for converting the self calls too? (maybe)

emccue18:04:08

I'd be happy with self call

emccue18:04:43

Though a generalized solution wouldn't be unwelcome

emccue18:04:09

I have no clue if it gaurentees general tail calls

noisesmith18:04:43

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

noisesmith18:04:16

I'm imagining it would need to do a tree walk on the body form, and detect tails that are self calls

noisesmith18:04:27

you could probably adapt tree-seq to return a series of tails

qqq18:04:33

for cursive users: are you using intellij community or intellij ultimate ?

elena.poot18:04:50

ultimate, but I've never tried community, we already had ultimate before starting with clojure

tbaldridge18:04:49

I use both, sine my client had an ultimate license, haven't noticed much difference

juhoteperi19:04:00

SQL tooling is lot better in ultimate and only ultimate has less/sass syntax highlighting

tdantas19:04:43

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

noisesmith19:04:50

make something (usually a function) that does the side effect without any data logic

mobileink19:04:44

noisesmith: what does "without any data logic" mean?

noisesmith19:04:13

then, all your logic and data manipulation can be separate definitions that don't have side effects at all

noisesmith19:04:00

this is an ideal - it gets tricky if you need the result of a side effect to drive further logic, for example...

noisesmith19:04:31

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

tdantas19:04:09

> 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

noisesmith19:04:20

or just the data it needs

tdantas19:04:46

gotcha ! ( still with the encapsulate OO principle on my veins )

noisesmith19:04:10

oliv it's not super different from the idea of separation of concerns

noisesmith19:04:43

eg. if your object both constructs a csv structure and outputs to a file, it's less reusable

tdantas19:04:44

but on my service layer, we receive the β€œcollaborator - DAO” and we use it

tdantas19:04:13

the service layer encapsulate the business logic and orchestrate with the DAO

tdantas19:04:28

the concerns are isolated ( DAO and business logic )

noisesmith19:04:48

right - I'm not making a direct identity / equality there - just saying there's a similar type of design

noisesmith19:04:55

(at a higher level of abstraction)

tdantas19:04:06

yep, i got your ideia and was helpful

didibus19:04:47

It would be the same thing, just in your DAO, you also seperate flow functions from IO read functions and IO write functions

tdantas19:04:06

sorry @didibus , what you mean by flow function ? didn’t get !

tdantas19:04:23

are you talking about cqrs isnide my DAO ?

tdantas19:04:31

query and command ?

madstap19:04:02

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.

noisesmith19:04:40

yes, this is exactly it

mobileink20:04:49

except: a function that does nothing except read or write io would be useless.

noisesmith19:04:59

mobileink no transformation of data

didibus19:04:10

The control flow, like if cond.

mobileink19:04:48

how can you have a side effect that does not involve data?

noisesmith19:04:22

it involves data that already existed - produced by a pure function

noisesmith19:04:40

if it gets data back, we don't touch it in the input function, we transform it using a pure function

noisesmith19:04:13

impure isn't just i/o of course, this also applies to using impure apis that have implicit state

mobileink19:04:26

i'm not following. simple example?

noisesmith19:04:23

instead of (defn make-csv [a-hashmap a-filename] ...) you have (defn to-csv-string [a-hashmap]...) to-bytes and (defn [write-file] bytes)

noisesmith19:04:36

the data transfomations don't mix with the io

mobileink19:04:58

it's easy to write an "add" fn that updates a counter, for example.

seancorfield20:04:42

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

noisesmith20:04:54

that's what I am talking about, yes

noisesmith20:04:30

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

tdantas20:04:36

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 outside

tdantas20:04:45

the service layer ^^

noisesmith20:04:01

yeah- having to be impure at the outer edge is common

tdantas20:04:14

nice to heard that

noisesmith20:04:36

so really I should have introduced the three categories - only impure, only pure, and the glue that makes your app work at the "top"

didibus20:04:26

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.

noisesmith20:04:33

and it's impossible to do this perfectly - it's a guideline

mobileink20:04:45

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?

noisesmith20:04:59

something like that, yes

mobileink20:04:49

@didibus : i.e., not isolation but control.

noisesmith20:04:09

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)

noisesmith20:04:34

just test if my pure stuff generates the data that api wants

mobileink20:04:44

i tend to agree. problem is clojure is full of side-effects! like def and defn. πŸ˜‰

noisesmith20:04:05

well, for starters, never call those from inside other code

noisesmith20:04:16

and never put side effects in a def body

noisesmith20:04:46

@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

mobileink20:04:35

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!

mars0i20:04:43

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

noisesmith20:04:40

@mars0i - if the mutation happens inside your local scope, and no caller can hear it, did it make a sound?

mobileink20:04:26

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

didibus20:04:49

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

didibus20:04:44

Get name and set-msg are not pure, in the sense they don't map deterministic input to output.

didibus20:04:59

I mean get-name and set-msg

didibus20:04:17

Only welcome is pure here

bradford20:04:45

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/'

mars0i20:04:54

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.

noisesmith20:04:21

@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

noisesmith20:04:23

(or reuse it with different input or output sources)

didibus20:04:00

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

noisesmith20:04:24

right, you might want to change those individually, makes sense

didibus20:04:37

Even better if greet takes read and write fns as arguments

seancorfield20:04:53

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.

seancorfield20:04:32

The readme explains why I no longer use it.

noisesmith20:04:38

fascinating - yeah, eventually things get trickier if you nest and compose things

seancorfield20:04:58

But it was an extremely educational experiment πŸ™‚

didibus20:04:56

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.

mobileink20:04:54

problem is we have no model for side-effectful computing. compositon really only works for turing/lambda/etc. models.

noisesmith20:04:27

there are monads, but those don't come naturally in clojure - and then proper effect systems, which I don't get at all

mobileink20:04:33

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.

didibus20:04:08

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

olfal20:04:11

Does anyone here have used http-kit extensively for handling web sockets before? I have some questions regarding its scalability πŸ™‚

noisesmith20:04:38

@olfol yes, I use http-kit web sockets in production

noisesmith20:04:00

(though I am strongly considering switching to aleph as it is better maintained)

olfal20:04:24

Cool πŸ™‚ I’ll pm you! I don’t want to flood this channel

noisesmith20:04:49

threads are fine too

mobileink21:04:42

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

didibus21:04:00

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

mobileink21:04:51

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.

didibus21:04:49

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

mobileink21:04:43

i'll quibble about whether it concatenates "computations". IO, strictly speaking, is not a computation, at least not on the turing/lambda model.

mobileink21:04:52

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?

didibus21:04:45

Well, in the IO case, it concatenates instruction to the computer

mobileink21:04:16

that could change every time, no?

didibus21:04:33

Think of it like this:

(defn welcome [io] (str "(str \"Welcome \" io)")
(defn main [] (eval  (welcome "(read-line)")))

didibus21:04:30

Now you see how it can be memoized?

mobileink21:04:31

maybe we have different ideas about memoization. (read-line) will presumably produce different results, usually. what do you want to memoize?

didibus21:04:34

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.

didibus21:04:56

In my example welcome takes a string. So it maps the input string "(read-line)" to the output string "(str \"Welcome \" (read-line))"

didibus21:04:48

You can memoized this. Eve tu time the argument is "(read-line)" the output is the same. This is deterministic.

mobileink21:04:53

ok, i see your point.

didibus21:04:32

Once we eval, all bets are off. And we can not memoize main.

mobileink21:04:41

wait, no i don't. 😒

didibus21:04:23

Let me work out my example a bit better

mobileink21:04:31

won't your welcome always return

"(str \"Welcome \" io)"

bradford21:04:00

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

bradford21:04:26

I'm running a new Ryzen 8-core with 32G ram, as well

qqq21:04:55

@bradford: is that with an SSD or a tape drive ?

bradford21:04:10

A new Samsung 960 EVO SSD!

qqq21:04:50

@bradford: I was going to try cursive; but after your msg, I'm going to stick with emacs for a bit more

bradford21:04:02

@qqq Cursive is fantastic, I'm mostly puzzled re: windows. It's not the editor itself that's as slow as compiling/repl

didibus21:04:19

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.

mobileink21:04:03

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

didibus21:04:57

@mobileink You can only memoize pure functions. So I'm using it as a way to prove that welcome is pure

mobileink21:04:19

write the defn of "welcome" again?

didibus21:04:55

(defn welcome [io]
  (str "(str \"Welcome \" " io ")"))

(def mem-welcome (memoize welcome))

(defn main []
  (load-string (mem-welcome "(rand)")))

(main)

mobileink21:04:59

your first try just wrote a string, i believe.

didibus21:04:47

Ya, I'm still leaning slack

didibus21:04:57

Ok, apology about messing the channel, its not possible for me to run this here. But try it in a REPL

didibus21:04:41

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.

mobileink21:04:50

i guess i'm a little puzzled by (str "(str ...)")

mobileink22:04:42

did you forget to defn memoize?

didibus22:04:18

memoize is a core function

seancorfield22:04:11

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

didibus22:04:34

Well yes, but that's essentially lazy evaluation.

seancorfield22:04:51

No, it's really not.

mobileink22:04:30

maybe i should head back to #beginners πŸ˜‰

didibus22:04:41

I'm constructing a big giant string as a cheap way of building up computation which is ran only from within main.

seancorfield22:04:07

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.

didibus22:04:01

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.

didibus22:04:12

My data structure in this case in String

seancorfield22:04:27

That's... a ridiculous argument, I'm afraid.

seancorfield22:04:04

A string is not structured. Not beyond having a sequence of characters. There's no "code structure" in that.

didibus22:04:23

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.

mobileink22:04:23

i think you missed the part about "referentially transparent" in memoize. "rand" is not rt, so it is not a candidate for memoize.0

seancorfield22:04:20

Haskell is not "interpreting" anything.

seancorfield22:04:17

I have almost no words to respond to what you're saying. It's just daft πŸ™‚

didibus22:04:20

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

didibus22:04:51

The run-time haskell program will interpret the IO Monad actions

didibus22:04:04

That's how Haskell is able to do IO

seancorfield22:04:16

I don't think you understand how Haskell works...

didibus22:04:27

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.

seancorfield22:04:04

It doesn't "work" because it's not even vaguely doing what an actual program would do.

seancorfield22:04:14

This is a pointless discussion.

didibus22:04:24

What do you mean?

seancorfield22:04:03

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.

mobileink22:04:08

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

seancorfield22:04:21

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.

didibus22:04:33

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

seancorfield22:04:28

Right, now you have actual code, based on thunks.

didibus22:04:43

In my perspective, this is the same code pretty much, in the sense that welcome concatenates computation.

seancorfield22:04:52

The evaluation process is very different with that.

seancorfield22:04:29

That's a very odd perspective, to describe it as "concatenates" computations...

seancorfield22:04:38

Haskell can have arguments evaluated eagerly (as well as lazily). You can't really think of it as a "concatenation" model.

didibus22:04:55

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?

seancorfield22:04:59

IO is just a type.

didibus22:04:45

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

didibus22:04:35

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.

mobileink22:04:39

didibus: sorry dude, i think you may have missed the boat, but i'll give you bonus pts for trying.

didibus22:04:36

Did you try it in a REPL?

didibus22:04:47

I'm pretty sure about what I'm doing

didibus22:04:20

(defn welcome [io]
   (fn [] (str "Welcome " (io))))
This is a pure function

didibus22:04:34

No side effect in it

didibus22:04:55

So you can memoize it, which I do here:

(def mem-welcome (memoize welcome))

didibus22:04:23

Now if you give it the rand function as input, it will always return an equal output.

didibus22:04:35

(= (mem-welcome rand) (mem-welcome rand))
Returns true always

didibus22:04:06

The function returned by welcome is not pure

didibus22:04:31

I've encapsulated the rand call inside the function returned by welcome

didibus22:04:37

So that welcome stays pure

seancorfield22:04:44

Since you want to tie this to Haskell, understand that if you want welcome to accept an impure function, then welcome also becomes impure.

seancorfield22:04:12

If you want welcome to be pure, you would have to declare a non-IO argument type and couldn't pass rand to it.

seancorfield22:04:45

A function is only pure if all the things it calls are pure.

didibus22:04:49

Haskell doesn't consider functions that take IO as pure? I thought it did.

didibus22:04:01

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

seancorfield22:04:46

If welcome calls io, and io is not pure, then welcome isn't pure.

didibus22:04:20

Ya, but my welcome implementation does not call rand. It simply creates a function that can call rand.

didibus22:04:37

It maps functions to functions

seancorfield22:04:56

Right, but we're talking at cross-purposes.

seancorfield22:04:20

I give up. I should have stopped trying to have this conversation with you half an hour ago...

didibus22:04:21

Yea, I would not do this in Clojure.

seancorfield22:04:08

Yup. Clojure != Haskell. In so many ways πŸ™‚

didibus22:04:32

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

seancorfield22:04:08

Strings and concatenation. That's what I was disagreeing with. Your mental model of how it all works is... off...

didibus22:04:04

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.

didibus22:04:28

And that this was an easy way to understand how you can implement such a thing.

didibus22:04:45

Not how you should, but just that it is possible

didibus22:04:17

Then at the top, in main, you go through these IO operations in the order they were describes and you execute them imperatively

didibus22:04:29

As I understand it, you can not eagerly evaluate something that depends on IO, since that would break purity.

didibus22:04:45

I hope I wasn't rude, I might be completely wrong about all this, just trying to communicate my understanding.

mobileink22:04:31

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?

didibus22:04:09

rand is not being memoized

didibus22:04:26

I understand that rand makes no sense to memoize. I am memoizing welcome

didibus22:04:54

Welcome can be memoized because if given the same FN, it will always returnt he same FN.

didibus22:04:34

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.

didibus22:04:36

This returns true: (= (mem-welcome rand) (mem-welcome rand))

cfleming22:04:43

@bradford Is starting a REPL using lein REPL slow as well? That’s pretty much all Cursive does

mobileink22:04:56

@didibus of course. they both return the same fn. what's surprising? you defined welcome as a 2nd order fn.

seancorfield22:04:18

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

bradford22:04:07

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

seancorfield22:04:58

Interesting. So there's still hope I can persuade my Windows 10 laptop to start a REPL faster then πŸ™‚

seancorfield22:04:17

(it's why I work in WSL / Ubuntu nearly all the time now)

mobileink22:04:56

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

didibus22:04:57

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

mobileink22:04:17

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.

didibus22:04:34

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.

noisesmith22:04:33

this is what the previously mentioned effect systems try to do

mobileink22:04:34

like i said i applaud your efforts. but having wrestled with this for years, my advice is: don't get your hopes up. πŸ˜‰

didibus22:04:14

Haha, ya I know, I was explaining effect systems and the IO Monad.

didibus22:04:19

As I understand them

didibus22:04:34

But I got everyone more confused in the process I believe

didibus22:04:56

Personally, I don't think IO is so bad to sprinkle in a few places if you try to keep most logic that don't need it inside pure functions. So I actually find Clojure's model more practical and it has never caused me maintenance issues.

didibus23:04:37

I might be wrong, but I think all effect systems, which IO Monad is one implementation of, is actually just doing that at a high level.