Fork me on GitHub
#beginners
<
2017-04-12
>
lsenta13:04:24

Thanks for the link @drewverlee, leanpub is just too slow to be trusted from where I am, I'll pass this one 🙂

kevinbheda15:04:19

using ragetime migrations Want to create a concurrent index , How to ? CREATE INDEX CONCURRENTLY sales_quantity_index ON sales_table (quantity);

goomba19:04:18

perhaps a dumb question... any thoughts on why

(defn nums* 
  ([] (nums* 0))
  ([n] (cons n (lazy-seq (nums* (inc n))))))
works but
(defn nums* 
  ([] (nums* 0))
  ([n] (conj [n] (lazy-seq (nums* (inc n))))))
throws a stack overflow error?

goomba19:04:48

when I do (take 5 (nums*))?

noisesmith19:04:26

conj on a vector is eager

noisesmith19:04:17

there’s no point in that call where lazy-seq can say “OK, I got a collection, I can stop now” - it keeps calling nums* and not getting result because nums* recurs

noisesmith19:04:20

so you blow stack

goomba19:04:55

interesting, but cons is lazy?

goomba19:04:27

okay... good to know. I was under the impression that conj was preferred ... perhaps for no reason

noisesmith20:04:46

cons works nicer with lazy-seqs - conj needs to evaluate the collection arg in order to know what to do

noisesmith20:04:04

if the colleciton arg is then a recusion - well down we go into a recursion

donaldball19:04:26

cons doesn’t change the laziness/realizedness of the seq arg

donaldball19:04:45

conj preserves the type of the collection arg

donaldball19:04:12

I hope that clarifies and is not simply pedantic 😛

goomba19:04:44

so is it more of a conj/cons thing or a vec/list thing?

donaldball19:04:57

cons will always give you a seq where the head is a cons cell (or a list, where the seq is null)

goomba19:04:09

and the rest is TBD?

donaldball19:04:38

conj on a vec appends; conj on a list prepends; it means add a value to the given collection in waves hands the best way you know how

goomba19:04:38

that's fine, that's the level of abstraction I'm at

drewverlee19:04:33

When would you dispatch based on a function rather then a recorded piece of data. That is, when does it make sense to use a protocol vs multimethod? It seems like you could take the mm function out and use it to produce the record.

drewverlee19:04:02

I suppose its a question of weather it helps to capture that new type/protocol?

drewverlee19:04:23

Right i guess the reasons to use protocals are because their faster and possible the communicate something to the user. I suppose in general though i would just use a multimethod so i could avoid having to create a new protocal? Is that the right way to think about it?

didibus20:04:08

@drewverlee Protocols dispatch on the type of the first argument. They also group related functions together. So they're useful when you want to add functionality to a type. They work well with records, since records are one way of creating types in Clojure.

donaldball20:04:17

I reach for a protocol when I have a coherent abstraction, particularly when the production impl has meaningful side effects

didibus22:04:26

Interesting those blog posts about mapping design patterns with Clojure, though I think they might be more confusing to me than anything. I mean the book is called "Design Patterns: Elements of Reusable Object-Oriented Software" They don't really make sense without OOP

didibus22:04:19

Most of the patterns in GoF anyways, what makes them so hard, is all the ceremony of wrapping everything in classes and objects. These constructs have a lot of constraint, so trivial things in FP appear much harder, though they are trivial in practice, how to set them up in code using classes and objects becomes complicated.

drewverlee22:04:14

@didibus The use for those blogs posts is very specific. Primary goals are meant to teach me more about FP, Clojure, OO. Secondary goal is to teach my team mates who really learned a lot from the GoF how those principles relate to FP. I have to bridge the gap with more then handwaving.

didibus22:04:48

Ya, those are good goals. I'm worried they might confuse a newcomer more than anything though.

drewverlee22:04:03

newcomer to clojure?

didibus22:04:22

Someone new to untyped FP

noisesmith22:04:03

clojure isn’t untyped, it’s it’s runtime dispatched with a unitype - there’s a difference

didibus22:04:39

Well, yes, but that's arguing semantics. It is based around untyped lambda calculus

noisesmith22:04:41

forth for example is untyped

didibus23:04:27

Hum, ok, maybe there is a distinction I don't understand

noisesmith23:04:51

“In typed lambda calculus, functions can be applied only if they are capable of accepting the given input’s “type” of data.” - clojure does this, at runtime

noisesmith23:04:48

but sorry, I think that’s a tangent that isn’t especially useful in this conversation

didibus23:04:39

Hum, I'm not sure that's true that this is how the untyped vs typed lambdas apply. What does it mean can only be applied? I think it means you constrain yourself from doing so. It is a conscious thing. In Clojure, you don't, you allow it, but it garbage comes in, garbage will come out

drewverlee23:04:42

I appreciate the insight @noisesmith.

noisesmith23:04:53

if the object that receives the method doesn’t implement it, you get a stack trace - the vm enforces the types based on parametric polymorphism, which is an object oriented flavor of typing

didibus23:04:59

Well, ok, as I was writing this, I saw your point. At runtime, it'll throw a type error, so it won't always allow it and see what happens. I guess that does make Clojure a weird hybrid like you said.

noisesmith23:04:32

whereas if you hand a forth word the stack, and it decides to read an int, it will treat the top 16 bits as a signed integer, regardless of who put those bytes there and what their original size was

noisesmith23:04:15

(eg they could be two bytes out of an eight byte float, who knows)

didibus23:04:20

Ya, I normally call that distinction strong vs weak typing. And I consider clojure dynamic strong. I was using untyped to refer to dynamic vs static in this case.

didibus23:04:52

And, back to topic, I was making that distinction because I've found people new to it get confused that often times, you don't create a type for your data, but just use standard data structures with special shapes.

didibus23:04:31

So, for bridge, like @drewverlee has in his blog post, he just modeled it as {:shape :triangle :color :red}

noisesmith23:04:59

right - to me the biggest difference between OO and FP isn’t what you can/can’t do, but which kinds of operations are considered worth your time

noisesmith23:04:50

eg. - how often should you implement new data types? how visible should your data be by default? how strong is the connection between data and the code that uses it?

didibus23:04:47

I've noticed, even in strongly typed FP, people find this surprising. They would create an algeabric data type to model the above data. So I think that makes Clojure unique in this way.

noisesmith23:04:23

other lisps come close, but they have a poverty of good data types

noisesmith23:04:37

(or at least ones that are convenient to use and ubiquitous)

didibus23:04:29

Ya, Clojure has great data structures, which makes this awesome. I think of it as structural vs nominal types. In Clojure, you create structures, and things that work over them. It doesn't matter the name of the structure, as long as it has the structure the function needs. And generally, we restrict this a little, so structures are either sequential or associative. So we'll have types of structures which will be checked at runtime, but appart from that, if the structure is what the function needs, it will work.

didibus23:04:02

You don't have to do this, you can create data types, use records for example. Those are useful when you need identity. And if you want to create a new category of structure, use deftype. At least, this is how I see it.

drewverlee23:04:51

> eg. - how often should you implement new data types? how visible should your data be by default? how strong is the connection between data and the code that uses it? These are exactly the sorts of questions i’m wresting with as i work through these GoF examples. Not in understanding the patterns, but thinking about how they would translate to clojure. Part of the stress is that their the explanations are so lengthy, that when i’m done implementing in them in clojure and have 7 lines of code. I feel like i messed up somewhere, or i’m missing the point.

noisesmith23:04:11

… and then I need to refactor something and suddenly I’m stuck trying to rewrite code where I don’t have any idea what the shape of the data was it was written for unless I actually go and snoop on the running application due to the combination of generic sequence and lookup operations that accept all kinds of inputs and asynchronous and threaded operations that dissociate the function from the entity that created the data that it acts on /gripe

didibus23:04:23

So, if you don't have a name for your type, you need a multimethod, so you can dispatch on the structural characteristics. If you have a name, you can use protocols.

didibus23:04:59

@noisesmith Haha, yep, that's why Rich is making Spec 😛

noisesmith23:04:06

and then I use plumatic/schema to enforce some data constraints (for documentation reasons if nothing else) and then other people edit the code that generates the data but never update the schemas so now we have stack traces and log messages complaining about the data flowing through but the app works…

didibus23:04:32

@drewverlee I think for you its probably a good learning exercise. Would be for me. But ya, I'd trust your judgement, those patterns are often harder to understand than what they actually provide. And that's mostly because they are really tricks around OOP constraints.

didibus23:04:03

Ya, that is still the biggest trade off of Clojure in my opinion. Its achilles heel.

didibus23:04:23

Would be nice to get more guarantee for that problem. With spec, I guess, if you run the generative tests you'd see that you forgot to update the spec.

noisesmith23:04:26

yeah- it’s not even clojure specific, it’s a consequence of using vanilla data structures - a tradeoff I opt into by using a lispy language

noisesmith23:04:54

oh yeah, tests are a good idea too haha

drewverlee23:04:47

> it’s a consequence of using vanilla data structure what is?

noisesmith23:04:04

the general mess I get into with refactors

noisesmith23:04:48

the code doesn’t tell me much about the structure that was passing through - all that I know is that some very generic data manipulations are being applied

noisesmith23:04:07

of course you can end up with messy code in any language

didibus23:04:06

@drewverlee What I wanted to say, with some of my coworkers I've introduced to Clojure. I often actually have to start by explaining to them what OOP is in reality. I tell them not to confuse the technique of modeling problems as things, and the language constructs of Java, C++, etc. The GoF patterns are often patterns for the constructs. Not modeling techniques. You can have groups of related data in Clojure, you can do that with records. You can also have groups of related functionality, you can do that with protocols. Namespaces can group data and functionality together. Nothing stops you from modeling things this way. But the construct you'll use to implement the model are just different.

drewverlee23:04:42

> The GoF patterns are often patterns for the constructs. Not modeling techniques. thats insightful! I’m willing to bet the authors were aware of the tradeoffs. So far what i have read has been very well thought out. I doubt ill read the thing cover to cover though. Just pick on a few and demonstrate how the concepts flow or dont to clojure. > What I wanted to say, with some of my coworkers I’ve introduced to Clojure. I often actually have to start by explaining to them what OOP is in reality. This is sort of what i’m learning how to do. I mean, i would more accurantly say i’m comparing Java to Clojure (i bet im the first one!). Using the GoF book in that regard probably has diminishing returns, as java has changed a lot.

didibus23:04:22

GoF is a good book. But its a book about how can you model things which do not work well with an OOP model, while still using OOP constructs.

didibus23:04:22

OOP is great if you need to model a group of things and there relationships, as long as those things are real objects in the real world. Like a Family. Mother, Father, Son, Daughter, Uncle. This is a great use case for using an object model. Now, if you want to model a rendering system, you need to shoe horn it inside an OOP model, because there are no objects. There's a big complicated piece of code that can draw, and a language that can describe graphics. In OOP, you need something like bridge to not have it be too bastardized as objects and relations. But in Clojure, you can write a draw function which takes a declarative graphics DSL. Done.

didibus23:04:17

That's why I think you'll find your Clojure alternatives are always very small and simple.

drewverlee23:04:28

@didibus interesting. michaeldrogalis has a talk on information models, where i think you can get back some of the relationships your talking about: https://www.youtube.com/watch?v=kP8wImz-x4w It seemed like it was a way to bring back types, but maybe i was missing the point. I’m not saying thats bad, it’s awesome that so much is optional in clojure.

drewverlee23:04:26

my favorite part of that talk is in the Q&A when i ask a nonsense question related to another talk because my brain was melted.

didibus23:04:33

Cool, I'll give it a watch when I have time.