Fork me on GitHub
#beginners
<
2019-12-30
>
seancorfield00:12:15

@patrick.glind If you don't mind paying for some high quality video tutorials, I'd recommend Eric Normand's http://PurelyFunctional.tv -- especially the REPL-Driven Development course.

seancorfield00:12:40

I think Timothy Baldridge also has a good series of (paid) videos.

❤️ 4
👍 4
Probie01:12:50

Which projects are good examples of idiomatic clojure code to look at?

dpsutton01:12:02

What’s the series on YouTube that mike fikes and others do? I’m forgetting the name now but they do a repl based problem solving for the second half of their broadcasts. Not sure if still active though

dpsutton01:12:47

Eric Norman was involved as well. (Since you bought one of his books)

dpsutton01:12:14

Yes. Thanks @mfikes didn’t want to ping you during the holidays :)

mfikes01:12:32

No prob :)

dpsutton01:12:00

Also not totally clear on the etiquette about when talking about someone but don’t need to ping/bother them

seancorfield01:12:22

@ludvikgalois Not sure what I'd point you to as an answer to that very open-ended question... I'll be interested to see what folks actually suggest.

mfikes01:12:30

Right, I often avoid @ mentions for the same reason—evidently it can trigger on your last name. :thinking_face:

mfikes01:12:23

Every time I mention the Corfield comma, Sean gets pinged. 🥴

nooga01:12:55

I’d suggest Tim’s Clojure Tutorials but their subject matter is pretty niche - like DIY JITs, interpreters, logic inference and query engines etc.

nooga01:12:45

which is why I find them super interesting btw.

dpsutton01:12:53

Heartily agree on that. Tim is super interesting and has a great patient voice as he explains

seancorfield02:12:42

Ah, I've never watched Tim's videos so I didn't know what was in them. Good to know.

Raj03:12:32

Hi! I'm writing a simple parser to read MP3 tags from input stream, using core.async.

(defn- read-frames
  [parse-map contents]
  (let [stream (:stream parse-map)
        fchan  (chan)
        result (go (loop [frames {}]              ;; conj frames to resulting map
                     (if-let [f (<! fchan)]
                       (recur (conj frames f))
                       frames)))]
    (loop [left (:remaining parse-map)
           id   (d/frame-id stream)]
      (if (and (> left 0) id)
        (let [n (d/uint stream)
              _ (d/skip stream 2)
              x (d/ubytes stream n)]              ;; read binary data
          (put! fchan (or (f/frame id x) {}))     ;; convert binary to frames
          (recur (- left (+ n 10))
                 (d/frame-id stream)))
        (close! fchan)))                          ;; close when no more data left
    (assoc-in parse-map [:tag :frames] (<!! result)))) ;; wait for result
Previously I tried using (go ... f/frame ...) instead of (put! ...), and it happened to work slower. I want all (f/frame ...) calls to work concurrently, and in the end they should all conj the result into a map, which will get returned at the end. Can you suggest any improvements to this?

naxels09:12:52

Thank you everyone for your suggestions! I did find Eric's videos however are too much for my budget right now, I will check the others

Bobbi Towers10:12:15

Sounds to me like you might get a lot out of these https://www.youtube.com/channel/UCLsiVY-kWVH1EqgEtZiREJw

👍 4
Ludwig10:12:44

hi everyone! , is there an open source project that heavily uses core.async? I would like to study how it is put in use.

mafcocinco15:12:24

question about clj-time and specifically timezone shifting: If I shift a date-time object using the offset functions like this: (time/to-time-zone (time/now) (time/time-zone-for-offset -6)) and then print the value using str, the value printed contains the correct date. That is, if the offset shift spanned midnight, the earlier date will print post-offset. This all seems correct. However, if I call clj-time.format/unparse on the date-time object using a format string which results in just a date, no time (for example yyyyMMdd) I only get the date post-midnight. I.e., it appears to be unshifted. I'm wondering if I'm doing something wrong or of this is just a misunderstanding on my part about how clj-time is supposed to work.

mafcocinco15:12:01

Related, I can not use the various local functions in this case as I'm working with multiple timezones and there really is not a notion of local in this context.

mafcocinco15:12:51

Okay, answered my own question. Didn't see this in the docs, but clearly shown in the code. Looks like there are functions to wrap formatters with timezone information, among other things.

jakuzure16:12:08

hi, I'm having some trouble trying to use crux with lmdb. I tried to use rocksdb before, but for some reason that didn't work at all (no locks or something) and it blew up the data directory to 200 MB immediately. now with lmdb it seems to work, but it feels like the process is hanging(?). right now my code only submits a few docs and then outputs all of them, lein run works within 20 seconds or so, but then the process just idles. lein uberjar ran for 2 hours and produced no results

time lein uberjar
Compiling rocksdb.core
Dec 30, 2019 3:04:33 PM clojure.tools.logging$eval361$fn__364 invoke
INFO: unknown
Dec 30, 2019 3:04:33 PM clojure.tools.logging$eval361$fn__364 invoke
INFO: Using libgcrypt for ID hashing.
^C
real    115m3.416s
user    2m54.682s
sys     0m18.303s

Gulli16:12:25

I'm looking at both the main-figwheel and shadow-cljs tutorials, both advocate using CLI tools instead of lein/boot. CLI tools doesn't seem to provide you with a project structure though. Not that it's a big issue, but being totally new to it - it feels like you are missing out on 'good' practices when it comes to the project layout etc. Is there a boiler-plate project structure for shadow-cljs or figwheel?

Gulli16:12:29

I also plan to use Reagent

Gulli16:12:56

So would I structure it somewhat like a react app?

mikerod16:12:51

@glfinn83 I still like using lein

👍 8
eval202017:12:37

@glfinn83 for shadow-cljs there’s https://github.com/filipesilva/create-cljs-app, inspired by create-react-app

👍 4
Gulli20:12:54

I'm definitely going to look into this. Have you been using this in your projects?

eval202020:12:00

Haven’t used it, though bookmarked it when looking for it recently. Went with one of the example-projects instead: https://github.com/thheller/shadow-cljs#examples

Raj17:12:48

What's the best way to convert a seq of vectors (like ([k1 v1] [k2 v2] [k3 v3] ...)) into a map?

dpsutton17:12:04

(into {} coll-of-kv-pairs)

👍 4
athomasoriginal17:12:00

Quick Q: Does anyone know the origin of the Hiccup DSL? I feel like it’s James Reeves, but I just want to be sure i’m not missing someone. Thanks!

bartuka17:12:11

I have a question about protocol and defmethods. I have a map that represents a type [for exemple, a payment type] which has some specifics keys in it. Cool. However, I want to create a protocol and extend it to work with this specific map. I thought about using a defprotocol at first, and I worked my way up to define the interface signatures, however when I started to implement the methods itself I tried to use a defrecord but for this situation I would have to wrap my entire map in this defrecord. It does not look right to me. I should use a multimethod and dispatch on some key of my original map or there are better alternatives other than wrapping the data?

bfabry18:12:50

if you don't want the specific features or speed of a defrecord, use a multimethod

Alex Miller (Clojure team)18:12:35

Probably a good candidate to extend via metadata (see https://clojure.org/reference/protocols)

Alex Miller (Clojure team)18:12:12

Just add the metadata to the map that implements the protocol

bartuka18:12:05

I sorta get it. I am looking for other examples to become more clear.

bartuka18:12:16

(def john-doe (with-meta 
                {:name "John Doe"
                 :language "us"} 
                {`clojure.core.protocols/datafy (fn [x] (assoc x :type 'Person))}))

(datafy john-doe)
{:name "John Doe", :language "us", :type Person}

bartuka18:12:46

the idea here is to have the implementation somewhere else and attach it on the fly, right?

Alex Miller (Clojure team)19:12:23

Yeah, you attach the implementation to instances, not classes

didibus19:12:09

The only caveat is that sequence functions don't preserve meta, so if you do any tranformation on the map and put it back in a map after, that new map won't have the meta anymore. So you need to manually preserve it in such case.

bartuka19:12:04

thanks, learned something new today. I think to my scenario the defmulti fits better

didibus19:12:57

This caveat applies to records as well, in that any sequence function will turn it into a seq, losing the type. So you need to explicitly make it a record of that type again yourself. With record, ots even worse actually, because a dissoc will also lose the type. So got to be careful.

bartuka19:12:29

in the interface there is no way to enforce the return value of an implementation, right? idk if I'm overthinking but this should be desirable?

didibus19:12:56

Multi-method that dispatches on a key/value of the map doesn't have these problems. Granted that you always put that k/v back on your resulting transformed maps

didibus19:12:28

Correct, can't do that :p

didibus19:12:36

Untyped language hehe

didibus19:12:52

You can provide a Spec for it though, to document the intent

didibus19:12:15

But its up to the implementer to make sure they respect it

bartuka19:12:30

hehe I suspected rsrs

bartuka19:12:44

how can I provide a spec?

bartuka19:12:58

one more edge that I don't know if I'm doing it right is about the client that will use my interfaces. I would like for the user to require only one namespace but it seems that when the implementation lies in another file, I have to require both the interface and the implementation. But how the client would know where the implementation lives?

didibus19:12:26

You would s/def a multi-spec for your multi-method.

bartuka19:12:58

[sorry, bunch of question prior to holidays hehehe.. very intriguing subject.. I am trying to grok this style of programming for a while now ^^]

didibus20:12:24

You would either put them both in the same namespace. Or have one namespace require the other. And then the client that requires that namespace will have the impls namespace brought in transitively

bartuka20:12:46

just to be clear. I have 1) namespace with the interface and 2) namespace with the implementation which already imports 1)

bartuka20:12:55

you propose to create a 3) to require both?

didibus20:12:03

If you want the clients to only need to import one namespace

didibus20:12:27

But it gets tricky

didibus20:12:46

I think in your case you're better off just moving the defmethods inside the same namespace as the defmulti

bartuka20:12:47

sounds interesting, I've done that on Python a lot.. init files with bunch of imports lol

bartuka20:12:30

this option is not that good, right now I have three different types that are extending the interface

bartuka20:12:32

great, this looks like the perfect fit for the problem

didibus20:12:23

I can't remember if it works with mutlimethods though. Should be easy to try though.

bartuka20:12:06

I'll try it later, thanks

bartuka20:12:00

do you have more material to recommend about oop in fn-style [idk if I can say this hahaha]?

didibus20:12:18

Otherwise sometimes people just do it manually. So you have this single namespace with all functions and vars you want to expose. And you have them use your impl code under the hood.

bartuka20:12:32

I re-read a book I had about CLOS and was very interesting to see the amount of similarities with clojure system

didibus20:12:57

That would include having a defn that calls your multi-method inside itself.

bartuka20:12:21

do you have any example of that?

didibus20:12:49

Hum.. Can't think of any off the top of my head that's open source.

didibus20:12:58

But I would not call it OOP

didibus20:12:23

I think you should think of it as Polymorphism, and dynamic dispatch

didibus20:12:03

What this does is, use the same function name for more than one behavior

didibus20:12:54

But now that you're using the same name, how do you decide what behavior to choose? Where there are more than one? You do so by inspecting the runtime values

didibus20:12:46

In other words. You could have a talk-dog and talk-cat function. One takes a dog map and another a cat map. No need for polymorphism at all. But if you want to abstract talk from cat/dog. Say because you have another function and it could take a dog or a cat as input, and it wants to make it talk no matter what the animal is

didibus20:12:21

With talk-dog and talk-cat. That function needs to have a cond or case expression.

didibus20:12:52

`(case (:type animal) :dog (talk-dog animal) :cat (talk-cat animal))`

didibus20:12:32

But now if you have many functions who operate on animals. And all your type of animals share a lot of functions, talk, walk, sit, etc.

didibus20:12:09

You'll have a lot of redundancy, where you're going to have case expressions everywhere

didibus20:12:19

What you want is to delegate to the functions themselves, talk, walk, sit, etc. The responsibility of having the case expression and figuring out what to do based on the type of animal

didibus20:12:57

And that's what multi method does 😛

bartuka20:12:09

Yeah, got it!!

didibus20:12:44

But you could also just move that case inside each of them, though if you do that, they are closed for extension. Adding another animal can only be done if you have access to the source code, since you need to add one more case to case. Multimethod allows open extension, clients can add new cases to it even without access to the source code.

didibus20:12:19

Anyways, my point being, there's nothing here that's OOP. There's no objects, no classes, no inheritance, no grouping of data and operations together

didibus20:12:26

You can have a non polymorphic OOP language

didibus20:12:02

It just so happens that popular OOP languages also support some forms of polymorphism in addition to their OOP support

noisesmith20:12:07

if you compare to smalltalk (the paradiigmatic OOP language), and the designer's definition of OO, what multimethod does is closer to being OOP than what Java does

didibus20:12:12

I never used smalltalk, but from my understanding its not so much polymorphism, its more about Actors

noisesmith20:12:56

in smalltalk a method is a message (implemented as a string iirc), which an object dispatches on

noisesmith20:12:12

conceptually described as an actor, but it's a polymorphic dispatch, just like multemethods

noisesmith20:12:41

but it is true, in smalltalk the method impl is stored on the object keyed on the method name and in clojure it's stored on the multimethod and keyed on the class of the object (dispatch checks class then superclasses until it finds a match)

didibus20:12:54

Hum... I guess you could see it that way. The object dispatches based on the message. Just like on Java an object dispatches based on the type.

didibus20:12:55

I still don't think that was the point of smalltalk. At least from all the articles about what the real point of smalltalk OOP was. It seems it was more about the message being a string

noisesmith20:12:10

java objects don't dispatch on type, methods are stored on the object

noisesmith20:12:49

the connection I'm trying to draw is that regardless of the location of storage, a link is stored between a piece of data and an operation, such that code that is written in terms of the operation can figure out at runtime what to do with that data

noisesmith20:12:04

and to me that's the interesting part of OO. I'm getting off topic here but I really think C++, Java, and all their descendants severely missed the point

didibus03:12:29

Hum. I'm not actually super familiar with the underlying dispatch mechanism. Doesn't Java walk up the class hierarchy to find a method that matches?

didibus03:12:55

I think I don't totally follow. To me, polymorphism is orthogonal to OOP, even though you can intertwine them. Such as choosing to use the class hierarchy as the driver for the polymorphic dispatch

didibus03:12:02

But you don't need classes. You can decide to dispatch based on value, arity, some configuration, etc.

didibus03:12:15

Even when I said dispatch on type, in Java types and Classes are also intertwined. But types also have nothing to do with OOP. Haskell hapilly has types and dispatches on them without having any classes for example

didibus03:12:05

I don't know enough about smalltalk. And the smalltalk OOP might be a whole different beast and maybe that one is much more related to polymorphism. But that's just semantics. I'm talking about class based OOP, your classic C++/Java/C#/Python OOP

didibus03:12:11

Where I might agree with you, I guess is that if I ask myself what an object is, its really just a set of fields and methods. And so you can think of it as the method to execute is based on the object you are calling the method with

didibus03:12:48

And ignoring the class hierarchy and interfaces, you can think of it as object based polymorphism. But that is only true if by convention you have two objects where you choose to put a function of the same interface and name

didibus04:12:29

I guess I see an object as a self referential collection of mutable fields and methods. Which I'm not sure how to implement in Clojure. But it be something like: `{:name "Shadow" :talk (fn [] (print "Woof, I am " (:name this)))}`

didibus04:12:17

Assuming this map to be mutable

didibus04:12:31

And a language designed around this particular structure as its foundational building block where all data and operations are organized using these objects to be an OOP language

didibus04:12:00

So at this point, there's not polymorphism involved really

didibus04:12:09

Now you could say if I also created: `{:name "Marble" :talk (fn [] (print "Meow, I am " (:name this)))}`

didibus04:12:41

Well I just added polymorphism

didibus04:12:06

Now if I do: ((:talk dog)) this will be a polymorphic dispatch based on the kind of object I have and the specific talk fn it contains

didibus05:12:27

So I guess I see them as orthogonal, even though you can leverage objects for polymorphism, it doesn't seem you have too

didibus05:12:07

But from what you said I gather small talk objects are different to this

didibus06:12:29

Also, sorry, definitely doing some thinking allowed here

noisesmith20:12:46

but the concepts terms have been likely been abused far past the point of being useful at this point

noisesmith20:12:30

and to go back to @iagwanderson’s point above, it's not accidental that clojure has features that resemble CLOS

bartuka20:12:04

I have to reread the multimethod in clojure, but we also have the concepts of before-method, primary method, and after-methods?

noisesmith20:12:31

no, we don't

sova-soars-the-sora20:12:46

hi everyone, did we ever come to consensus on what's the best data structure for something that resembles a ranked list or ranked map?

enforser21:12:02

Not sure if I missed some previous discussion, but I'd think if you care about order you'd want either a list or vector over a map. Use a list of you are going to be re-ordering elements frequently, and don't care about random access. I'm under the impression vectors give you O(1) look ups of elements by index, but need to be pre-allocated so are not ideal if you will frequently be adding/removing/shifting elements around. Lists give you O(n) access, but can more easily modify elements they contain.

noisesmith20:12:52

CLOS is in fact how polymorphism was introduced to lisp

sova-soars-the-sora20:12:59

(where entries move up and or down)

bartuka21:12:37

I've been using this dispatch on a project very timidly, but I already got the benefit of changing a implementation more than two times and not changing the interface. I very recently started at clojure and I didn't find many people advocating and showing examples of more intense usage of interfaces. Idk maybe I have missed something along the way. Do you guys think this subject is stressed enough in the community?

didibus07:12:04

Changing an implementation without changing the interface does not necessarily require this kind of dispatch or polymorphism. A simple function will do

didibus07:12:03

There a times where multi-methods are great, I'd say those are when you want users to be able to extend the behavior on their own. Or when you want more than one active implementation of the same interface

didibus07:12:03

And protocols can make sense when you need the same, but over a coherent set of functions.

didibus07:12:34

Its normally most useful for creating pretty generic abstractions. Like fundamental language constructs

didibus07:12:13

Such as the collection interfaces, or the print multi method, etc.

didibus07:12:50

I've rarely used them for application code though

didibus07:12:12

For example Manifold is a good example use of protocols to create a very generic abstraction https://github.com/ztellman/manifold/blob/master/src/manifold/deferred.clj#L42

noisesmith21:12:24

we usually get the benefits implicitly by using clojure's built-in datatypes and using functions that dispatch based on interfaces

👍 4
noisesmith21:12:42

most of the time that's enough, and it leads to simple flexible code

bfabry21:12:13

90% of my use of polymorphism in java is to stub/mock things out for tests. clojure has with-redefs so I don't need it for that

bfabry21:12:39

that said, component definitely puts some focus on interfaces, at least for services

jakuzure21:12:47

anybody got an idea why crux + lmdb runs fine in my repl, but takes forever to finish the process when it's run using lein run (or has never finishes when doing lein uberjar)? I'm just outputting all 4 values and then close the node, is there something I'm missing?

bfabry21:12:16

you're starting a node at compilation time

bfabry21:12:35

well, file read time I guess. "compilation" is probably the wrong thing to say

noisesmith21:12:00

compilation makes sense to me: clojure compiles every form before running it, and runs every form when loading a file

noisesmith21:12:18

it's possiible to read a file without compiling / running, but uberjar doesn't do that

noisesmith21:12:42

(and it's only useful fo preprocessing / analysis - you just get lists of symbols and such)

jakuzure22:12:23

I'm just planning to use it as a db to store some data for a small project, ideally I can just give somebody the .jar, they run it and it starts the node and closes it after it wrote the necessary things to it, should take like 10 seconds max. but I'm guessing this is not the right approach then, other recommendations for what I should be using for this case?

noisesmith22:12:03

you shouldn't define the db at the top level of the file - you can have a function that makes the db, and run it inside your main method

jakuzure22:12:23

I see, I thought I'm just defining the db at that point and it's not run yet

jakuzure22:12:32

or evaluated rather

noisesmith22:12:13

right, that's a common misconception - side effects inside def run when the file is evaluated, and with standard uberjar with aot, that includes jar packaging time

noisesmith22:12:20

the reason that lein run takes so long is another question - one thing to try is adding a call to (shutdown-agents) at the end of your main right before exit (it closes down clojure's auto-scaling thread pools so the vm can exit, but should only be done at exit, it's a one way switch)

jakuzure22:12:52

will try that, thanks

jakuzure22:12:10

could you also help me to define a function that makes the db, as you mentioned before?

bfabry22:12:42

my recommendation:

(defn start-db []
  (crux/start-node ...))

(defn -main [& args]
  (let [db (start-db)]
    (function-that-uses-db db)))

4
🙂 4
noisesmith22:12:54

there are libraries like stuartsierra/component and integrant that simplify the above pattern but in early stages that's exactly what you want

jakuzure22:12:06

thanks! I think I'm confused about the "def ^crux.api.ICruxAPI" part, just copied that from the getting started guide

bfabry22:12:59

that is attaching metadata to the var that is storing the started db

noisesmith22:12:13

that's something that works in a simple example - it defines the db as a global, and the ^type notation tells the compiler what the type contained in the var will be (not really needed here at all)

bfabry22:12:39

oh it's a typehint. noisesmith is right. not sure how I forgot that. so metadata on the var that describes the type of the object. and yeah shouldn't be needed while sticking to the clojure api for crux

noisesmith22:12:24

both descriptions are correct - the compiler uses the metadata

noisesmith22:12:19

in this case I suspect the type hint is actually there for the human reader who understands how typehints are used (it's telling you the class of the object returned in that call)

jakuzure22:12:38

after wrapping start-db in another function and adding (shutdown-agents) to the end of -main I still get the same behaviour when trying lein run

bfabry22:12:01

maybe add (.close db) as well. crux may have its own thread pool

noisesmith22:12:07

you can use Control-\ when running in a terminal to see the stack traces of all running threads - one of them likely exposes what's blocked

🆒 8
noisesmith22:12:35

the jstack command does the same, but takes a PID and runs in a different window

jakuzure22:12:40

I already had (.close db) in my main, that's the weird thing

noisesmith21:12:41

for the uberjar question: this is a common pitfall for newcomers, clojure doesn't have a "compile" mode, any code inside def is run when compiling

uknys23:12:47

how can we define union types with spec ?

mruzekw23:12:41

There’s actually a good example on https://clojure.org/guides/spec

(s/def ::query string?)
(s/def ::request (s/keys :req [::query]))
(s/def ::result (s/coll-of string? :gen-max 3))
(s/def ::error int?)
(s/def ::response (s/or :ok (s/keys :req [::result])
                    :err (s/keys :req [::error])))

uknys23:12:50

(s/def ::response (s/or :ok (s/keys :req [int?])
                        :err (s/keys :req [string?])))

(s/conform ::response {:err "toto"})
;; => [:ok {:err "toto"}]
Am I missing something ? because the answer seems wrong from the repl

didibus07:12:50

req takes the key names as fully qualified keys which will use the spec of the same key to validate the value of the map for that key

didibus07:12:25

Since your map is using an unqualified key :err you need to use :req-un

didibus07:12:20

`(s/def ::ok int?) (s/def ::err string?) (s/def ::response (s/or` `:ok (s/keys :req-un [::ok]) :err (s/keys :req-un [::err])) (s/conform ::response {:err "toto"}) [:err {:err "toto"}]`

mruzekw23:12:28

> Creates and returns a map validating spec. :req and :opt are both > vectors of namespaced-qualified keywords.

mruzekw23:12:46

So you can’t check against directly against a predicate with s/keys

mruzekw23:12:19

You must define a separate spec for the keys you’re checking and validate/conform/explain from there

mruzekw23:12:48

@dimitri.tavan If you use the specs from the docs, and call this. It will be correct

user=> (s/conform ::response {::error 404})
[:err #:user{:error 404}]