Fork me on GitHub
#beginners
<
2017-11-18
>
derpocious02:11:21

Does anyone here use proto repl with atom? I can't figure out how to turn it on. (derp)

gdeer8104:11:58

@derpocious yes, how are you trying to turn it on?

seancorfield06:11:15

@derpocious Did you follow Jason Gilman's "opinionated setup"? That's a really good place to start (although I recommend disabling the three auto-refresh options that it says enable -- at least until you're building everything with Stuart Sierra's Component 🙂 )

derpocious18:11:20

I go to package -> proto repl -> start repl

derpocious18:11:33

but the repl is always empty

athomasoriginal18:11:54

RE: Naming Conventions After reading through Elements of Clojure's chapter on naming and several other blog posts, I am working on a small app that I am hoping will clarify how names could work. The meat of this is a little longer, so I am going to start a thread with the code examples. Any input would be amazing 🙂

athomasoriginal18:11:10

(defn event
  "Return a data type of `event`."
  [name start-time end-time]
  {
    :name name
    :time (event-time start-time end-time)})

(defn- event-time
  "Return a data type of `event-time`.
  x is the `start-time` and `y` is the `end-time`.  Both of these are `time` units
  which must come in this format:  hour + .25 increments.  For example, 9 9.25
  9.50 9.75 are all valid `time` units."
  [x y]
  [start-time end-time])

(defn get-event-name
  "Return `name` from the `event` provided.
  Must only be passed a data type of `event`."
  [event]
  (get event :name))

(defn get-event-time
  "Return `time` from an `event`.
  `time` is a vector which includes `start-time` and the `end-time`."
  [event]
  (get event :time))

(defn get-start-time
  "Return `start-time` from an `event`."
  [event]
  ((get-time event) 0))


(defn get-start-time
  "Return `end-time` from an `event`."
  [event]
  ((get-time event) 1))
The questions I am left asking are: 1. should get-event-time even be there. The name is really just describing the implementation. Do I even need this helper? 2. Maybe question could be solved if I don't use the ver get e.g. rename it to event-name - problem here is that clashes with the function event-name. 3. I could change event-name to something like start-time+end-time and than I could have a getter function function called event-time Clarify point: I would expect people to use the this module like this: event/event, event/get-event-name etc. I could shorten these to event/name - which is intersting because if I am looking at event/name it looks like it is creating a data tpye and pulling something from another data type. As you can see this is a very simple example, and I want to use this as a template for sensible defaults so I can spend less time, when possible thinking on these things.

noisesmith18:11:36

generally we shouldn't have getters in clojure

noisesmith18:11:07

you can make exceptions with things that describe mutable or side effecting stuff, but I don't think that's the case here

athomasoriginal18:11:14

That will clear this up quite a bit then. So the recommended approach would be to just do ((get-time event) 0)) whenever it is needed and not make a function responsible for it

noisesmith18:11:26

or store the event time as a hash-map instead of a vector, so the key would be something intelligible

athomasoriginal18:11:07

What would the reasoning be for avoiding getters - as a default - because I imagine there are scenarios where they could be useful - not this one of course

noisesmith18:11:43

getters add complexity by tying code to the implementation and domain

noisesmith18:11:52

you can't use get-in or update-in with getters

athomasoriginal18:11:00

Shoot, thats really true. Never thought of it like that.

Lucas Barbosa18:11:03

but @noisesmith, isn’t creating constructors, selectors and domain-operations something good?

Lucas Barbosa18:11:16

to establish an abstraction barrier

noisesmith18:11:15

idiomatic clojure avoids these things in most cases. the book tkjone is talking about, "elements of clojure" addresses these sorts of design trade offs in later chapters

noisesmith18:11:49

it's the lisp way to prefer 1 data type and 100 functions, instead of 10 data types and 10 functions for each

Lucas Barbosa18:11:17

so should I deliberately access any piece of data in a map created somewhere every time I need it?

Lucas Barbosa18:11:38

like (:key map)

noisesmith18:11:44

that's how most clojure is written, and how most clojure libraries are meant to be used

noisesmith18:11:58

there are places where abstraction boundaries help a lot!

Lucas Barbosa18:11:22

I see… it is hurting me to thing about the time when the implementation of that data structure changes

noisesmith18:11:24

but you probably want that to be inside the implementation, and still return vanilla clojure data

noisesmith18:11:50

@U6UNHRZ99 if you return it to a client in clojure, it's not an implemention detail, it's the domain range of your function

noisesmith18:11:12

you can change how you generate it, or add keys that were not there previously

noisesmith18:11:25

but once you return it, they own it

Lucas Barbosa18:11:58

Didn’t think about it that way

Lucas Barbosa18:11:08

makes sense :thinking_face:

noisesmith18:11:13

zach tellman is more articulate about these design considerations than I am, I recommend finding his recent talks on youtube where he presents the material from the later chapters of his book

Lucas Barbosa18:11:59

I will watch some now

athomasoriginal18:11:18

RE: Defrecords I was reading through B&T https://www.braveclojure.com/multimethods-records-protocols/ and want to confirm that if I have code like this:

(defn event
  "Return a data type of `event`."
  [name start-time end-time]
  {
    :name name
    :time { :start-time end-time
            :end-time start-time}})
Where an event is a data type in the domain I am working on that using a defrecord is the better choice as opposed to just building a map like I am doing above. CB&T would suggest this to be the case.

noisesmith18:11:07

why would you use a record? just because it's a domain type isn't enough

athomasoriginal18:11:07

Thats pretty much the only reason I can think of right now for the above scenario - really, trying to understand when they are used

noisesmith18:11:15

typically a record is used when you need an immutable map and you also need to implement a protocol

noisesmith18:11:33

(or interface, or more than one)

athomasoriginal18:11:39

> when you need an immutable map I understand the protocol part, but what do you mean by the above? Isn't most everything immutable?

noisesmith18:11:35

hash maps don't implement your protocols / interfaces

noisesmith18:11:04

if by "everything" you mean all instances of () {} #{} {} [] - sure

noisesmith18:11:13

it's not many types actually, we just re-use them a lot

noisesmith18:11:12

if we didn't have defrecord, and you needed to make something implementing someone else's interface or protocol, and also wanted to use clojure's immutable data functions, you would have to do a lot of work to implement something that fit all those requirements

jcburley21:11:43

I'm stumped trying to get the same result from (pr (str g)) that I get from (pr g), where g is a record. From the docs, I'd have expected (str g) to expand to the contents of the record, but I get "tic_tac_toe.game.Game@54627eec" instead. And I'm having a heck of a time trying to find the source for pr and friends, to figure out what they invoke to get those contents. Any pointers?

jcburley21:11:11

(It would be nice if e.g. https://clojuredocs.org/search?q=pr actually explained how stringizing works....)

jcburley21:11:23

The only real reason I want str here is to avoid spaces in between arguments. I could use printf, but that has the same issue -- %s expands to the obscure object id, not the contents, of the object.

noisesmith21:11:51

pr tries to print in a form that is readable

noisesmith21:11:03

so if you give it a string, it prints something that will read as a string

noisesmith21:11:32

if you want to combine without spaces, you can use pr-str with format

noisesmith21:11:37

+user=> (format "%s%s" (pr-str {}) (pr-str "hello"))
"{}\"hello\""
or use str instead of pr-str to get the normal stringify (or equivalently let it implicitly use toString which is what str inokes)
+user=> (format "%s%s" (str {}) (str "hello"))
"{}hello"
+user=> (format "%s%s" {} "hello")
"{}hello"

jcburley22:11:31

@noisesmith -- ah, now I understand that pr-str is what I want, not str. They do different things for an instance of a record; pr-str prints the contents, str does not. Thanks!

jcburley22:11:41

Next q: is there a canonical way to handle frequently used pre/post-conditions so a) I don't have to keep writing out (say) four postconditions, and b) I still get the helpful message, upon violation, as to exactly which postcondition failed?

jcburley22:11:00

(I tried the macro approach, but of course that doesn't work, since a macro invocation can return only a single Sexpr....)

noisesmith22:11:50

you can fix that with a data structure, the bigger problem is that macros only see data that is directly in the source, it can't know anything about the runtime data

jcburley22:11:40

@noisesmith -- do you have an example? I don't understand....

noisesmith22:11:30

macros don't know anything about your data if it isn't literally in the source code (or put there by another macro first)

noisesmith22:11:40

they run while compiling

jcburley22:11:14

Still not clear. What I'm looking for is a way to convert something like this, that occurs multiple times in my source code: :post [(cond1 ...) (cond2 ...) (cond3 ...) (cond4 ...)] to: :post [(common-code ...)] Now, if common-code is a function that returns the (and ...)of cond1 through cond2, it should work. But if the postcondition fails, the diagnostic will then only tell me that common-code returned false -- whereas, before, it'd tell me which of condN returned false. Does that make any sense?

jcburley22:11:25

(Typo: meant "...returns the (and ...) of cond1 through cond4.)

jcburley22:11:30

I know Lisp macros (well, at least Clojure macros) can only return a single sexpr -- so I'm just wondering whether there's any better way to tackle this (rather minor) issue.

noisesmith22:11:45

OK - to make that work as a macro, you need to either use do (which gives you the same problem the common-code function had) or make a macro that generates an fn or defn form

jcburley22:11:50

(Maybe I'll get around to studying the Clojure source code for things like :post and come up with something myself?)

noisesmith22:11:54

because you are generating input to a macro

jcburley22:11:35

Right, I assume some macro is getting :post and processing it somehow.

noisesmith22:11:54

yes, the fn macro

noisesmith22:11:06

which is a doozy

jcburley22:11:13

I can imagine!!

noisesmith22:11:20

(defmacro fn
  "params => positional-params* , or positional-params* & next-param
  positional-param => binding-form
  next-param => binding-form
  name => symbol

  Defines a function"
  {:added "1.0", :special-form true,
   :forms '[(fn name? [params* ] exprs*) (fn name? ([params* ] exprs*)+)]}
  [& sigs]
    (let [name (if (symbol? (first sigs)) (first sigs) nil)
          sigs (if name (next sigs) sigs)
          sigs (if (vector? (first sigs)) 
                 (list sigs) 
                 (if (seq? (first sigs))
                   sigs
                   ;; Assume single arity syntax
                   (throw (IllegalArgumentException. 
                            (if (seq sigs)
                              (str "Parameter declaration " 
                                   (first sigs)
                                   " should be a vector")
                              (str "Parameter declaration missing"))))))
          psig (fn* [sig]
                 ;; Ensure correct type before destructuring sig
                 (when (not (seq? sig))
                   (throw (IllegalArgumentException.
                            (str "Invalid signature " sig
                                 " should be a list"))))
                 (let [[params & body] sig
                       _ (when (not (vector? params))
                           (throw (IllegalArgumentException. 
                                    (if (seq? (first sigs))
                                      (str "Parameter declaration " params
                                           " should be a vector")
                                      (str "Invalid signature " sig
                                           " should be a list")))))
                       conds (when (and (next body) (map? (first body))) 
                                           (first body))
                       body (if conds (next body) body)
                       conds (or conds (meta params))
                       pre (:pre conds)
                       post (:post conds)                       
                       body (if post
                              `((let [~'% ~(if (< 1 (count body)) 
                                            `(do ~@body) 
                                            (first body))]
                                 ~@(map (fn* [c] `(assert ~c)) post)
                                 ~'%))
                              body)
                       body (if pre
                              (concat (map (fn* [c] `(assert ~c)) pre) 
                                      body)
                              body)]
                   (maybe-destructured params body)))
          new-sigs (map psig sigs)]
      (with-meta
        (if name
          (list* 'fn* name new-sigs)
          (cons 'fn* new-sigs))
        (meta &form))))
nil

jcburley22:11:22

So it's "the fn macro" in more than one sense of "fn". 😉

jcburley22:11:42

Oh, well, that looks straightforward enough! 😉

jcburley22:11:12

Where does the code for that live, BTW? Now that I'm on my Windows 7 machine, instead of my Mac, I can't easily go to the source code (my CIDER environment tries to invoke unzip, which isn't installed ATM).

noisesmith22:11:46

you can use clojure.repl/source to see it, and you can look at (System/getProperty "java.class.path") to see the jars you are using - the clojure source is in one of them

noisesmith23:11:07

a jar is a zip file with a specific set of contents, which is why cider wants to use unzip

noisesmith23:11:06

a more helpful trick:

+user=> ( "clojure/core.clj")
#object[java.net.URL 0x47a5b70d "jar:file:/home/justin/bin/bench!/clojure/core.clj"]
the resource prints the path to the jar plus the path inside the jar, if it can find the resource

noisesmith23:11:34

(I make an uberjar called "bench" for raw benchmarking containing clojure.jar plus my most commonly used libraries)

jcburley23:11:48

Okay, thanks! I had actually made a note about (clojure.repl/source map) awhile back, while reading "The Joy of Closure", and forgot I'd done that....

noisesmith23:11:01

unless you interfere, clojure will refer clojure.repl in your starting namespace, so (source foo) will work

jcburley23:11:57

Okay. BTW, I can list the source for pr, but that source code calls pr-on, and this doesn't seem to work:

> (clojure.repl/source pr-on)
Source not found
;; => nil
>

noisesmith23:11:49

it's private, it can't be seen unqualified from your ns

noisesmith23:11:56

try (source clojure.core/pr-on)

jcburley23:11:24

Ah, of course, thanks again!