Fork me on GitHub
#beginners
<
2016-03-02
>
slester05:03:41

Is there a better way of saying (comp #(max 0 %) dec) for decrementing but never going negative?

solicode05:03:28

@slester: Looks fine to me. I might write it like #(max 0 (dec %)), but it’s the same thing essentially.

slester05:03:14

haha, not sure why I wrote it with comp. I guess because I'm using it in update

solicode06:03:14

No problem. I can’t think of anything shorter than that.

slester06:03:29

it doesn't necessarily have to be 'shorter', I just want to make sure I'm writing things in the community-accepted way

slester06:03:41

easier to unlearn things now than try to when I get years of experience

solicode06:03:31

There was nothing wrong with using comp either though.

slester06:03:21

ok! question two. I have a function that I want to be able to deal with both a list of ints and a single int. Is there a nice way of making the int a list?

slester06:03:20

I was just going to reduce over the eventual list, there's no special behavior for the single int

slester06:03:10

Instead of something gross like (let [items (if (list? arg3) arg3 (list arg3))])

solicode06:03:39

@slester: I try to avoid making functions that work on both types like that. Pretty much for the same reasons laid out here: https://stuartsierra.com/2015/06/10/clojure-donts-heisenparameter

slester07:03:03

@solicode: great, excellent link. That's precisely the kind of info I needed simple_smile

mfikes14:03:21

Is there a core library function that implements the following idea (which is fairly close to a common use of cond->, in some sense):

(defn magic
   [v p? f]
   (if (p? v)
     (f v)
     v))
It would let me do: (magic "" empty? (constantly "Default”)) Or (defn abs [x] (magic x neg? -))

seancorfield14:03:49

We ended up writing a condp-> macro for that -- like cond-> but threads through the predicate as well.

seancorfield14:03:26

(condp-> v p1? f1 p2? f2)

mfikes14:03:12

@seancorfield: Ahh… that makes me feel better at least. (It is not just me that keeps encountering this code pattern.)

mfikes14:03:37

I like how you generalized it, and it naturally uses the cond-> idea.

seancorfield14:03:59

We have condp->> too (of course).

mfikes14:03:04

(This has been bugging me. I think I encounter the desire for condp-> once every few days. Maybe it’s just me.)

seancorfield14:03:48

We have maybe one or two uses in 30,000 lines of code...

mfikes14:03:26

@seancorfield: Ahh. Thanks! I might be approaching things wrong then. I need to re-wire that miswired part of my brain. simple_smile

seancorfield14:03:03

It's entirely likely we could use it a lot more than we do -- we just haven't felt the need. And maybe that's a "yet". As we improve the idiomatic level of our code, maybe we'd reach for it more. I don't know.

adamkowalski20:03:07

Is there something similar to Maybe or Either in Clojure? In Haskell we were able to use those types in order to encode possible errors in the actual type system rather then relying on a mechanism such as try catch. For example if you have a divide function, you could accept two numbers and return a “maybe number”, meaning if the denominator is zero you would return “nothing” otherwise you would return “just the (numerator / denominator)” That way other functions that call your divide function will be aware that things might go wrong and must handle (usually using pattern matching) both cases.

seancorfield20:03:31

In general in Clojure you would either return nil (meaning Nothing) or perhaps use a sequence with 0 or 1 elements.

seancorfield20:03:48

The idiom of nil punning makes nil very common as "Nothing".

nkraft20:03:53

I'm not sure how that's a "maybe" number. If the denominator is zero, the function returns nothing, otherwise returns the product? That sounds like a simple if-then with a dubious return. After all, if the function returns nothing how does the caller check for this? Nil works out as a much better return value, and no one ends up waiting forever.

seancorfield20:03:29

(defn divide [x y] (if (zero? y) nil (/ x y)))

seancorfield20:03:23

Since then you can do (if-let [v (divide a b)] (… v …) "No value to do something with")

seancorfield20:03:57

But of course you have to know you’re dealing with a nilable value…

seancorfield20:03:41

Although I’d probably just let the divide-by-zero exception happen and propagate unless I had a good reason not to 😸

grounded_sage21:03:23

is there a way to take the parameter of one function and pass it into the parameters of another function in another namespace?

jonahbenton21:03:49

@adamkowalski: Rather than exposing exceptions to calling code I sometimes use golang's idiom, returning a tuple of [value ok?], which is easy to destructure. But generally speaking there isn't any checking of return value types of Clojure fns ahead of runtime, so there isn't really a way to help (or get help from) the compiler in the way one can with Maybe.

adamkowalski22:03:24

thanks for all the ideas, its given me some things to think about

adamkowalski22:03:47

I do have a few concerns with some of these concepts though

adamkowalski22:03:24

@seancorfield: you suggested returning nil from the function, which makes sense but then every function would need to check if the value that got passed in is nil before doing the next thing, which is something I would like to avoid

seancorfield22:03:56

@adamkowalski: Understood — but since there’s no type system per se, and nil punning is idiomatic, testing for nil (or just plain ol’ handling it) is common practice.

seancorfield22:03:13

Many built-in functions behave in sensible ways when given nil.

adamkowalski22:03:44

right, it seems like the issue is that because everything is dynamic it requires a different mindset then other languages

seancorfield22:03:13

Yes, re: different mindset.

adamkowalski22:03:33

like for example if you used function composition how do you “know” that two functions could work together?

adamkowalski22:03:19

in Haskell if you have a function “f :: b -> c” and “g :: a -> b” you could create function “h” which is the composition of f and g

adamkowalski22:03:29

which would then have the type “h :: a -> c"

adamkowalski22:03:10

but if everything is dynamic this seems like you would need to use perhaps a series of pre and post conditions to ensure that the output of function g matching the input of function f right?

adamkowalski22:03:55

and if g can either return a number, or nil as in the example you provided above, f would need to not only look for a number but must also check for a nil value

seancorfield22:03:27

As I said, nil punning is idiomatic. It’s a very different approach to Haskell (or Scala or…).

adamkowalski22:03:43

hence why in haskell you could have g :: a -> maybe b and f :: b -> maybe c then h uses a kliesli (i probably spelled that wrong) arrow to compose f and g

adamkowalski22:03:53

making a new function which goes from a to maybe c

adamkowalski22:03:57

ok I will look more into nil punning.

seancorfield22:03:54

I’m not saying nil punning is the "right way" to address your numeric division example — I’d rely on exceptions there personally — but instead of Maybe and Monads, Clojure tends more to nil and punning.

jmayaalv22:03:01

adamkowalski this is a good post about nil punning http://www.lispcast.com/nil-punning

adamkowalski22:03:50

thanks for the link, it seems like an interesting read

adamkowalski22:03:35

it seems like it is addressing exactly what I was afraid of haha, I have always had poor experiences with null pointers before in other languages

jonahbenton22:03:52

@adamkowalski: it's exactly right to look at this from a function composition perspective; with Clojure, that turns into adopting a data flow mindset. For data flow of entities (rather than reduction/folding operations on collections) Clojure has a number of "threading" macros- here's some good coverage: http://www.spacjer.com/blog/2015/11/09/lesser-known-clojure-variants-of-threading-macro/

seancorfield22:03:43

some-> allows pipelining of functions / values where nil is an "expected" result so that’s a bit like monadic operations with maybe values in Haskell (if you squint hard).

seancorfield22:03:36

But built-in functions that accept collections pretty much all accept nil and treat it like an empty collection which also allows pipelining. And often the empty collection and nil are viewed as "the same" (consider seq which accepts nil and () and produces nil for both). And the very fact that nil is considered as false helps with a lot of this too.

seancorfield22:03:35

I find I don’t hit NPEs in Clojure very often — certainly not compared to when I was working in Java or Groovy (or even in Scala!).

adamkowalski22:03:26

yeah it seems like all these different threading macros can help out quite a bit to make your code do similar things to what I was talking about earlier

adamkowalski22:03:29

thats what I was hoping for, not necessarily to do the same thing in Clojure that I was used to in Haskell (because then why not just use Haskell), but rather to learn how Clojure developers deal with such problems and solve them in their own unique ways.

adamkowalski22:03:07

but I wonder if there is a way to somehow incorporate transducers into there somehow

adamkowalski22:03:44

because with the cond->> example they show in that link they are essentially checking whether or not they want to map or filter or sum the collection, but they might do all of them or none of them

adamkowalski22:03:15

It seems like it would be better to somehow use a transducer and function composition using a series of when statements

adamkowalski22:03:47

like instead of (defn process-collection [col use-map? use-filter? sum?] (cond->> col use-map? (map (fn [x] (* x 5))) use-filter? (filter (fn [x] (< x 25))) sum? (reduce +))) (defn process-collection [col use-map? use-filter? sum?] (into [] (comp (when use-map? (map (fn [x] (* x 5))) (when use-filter? (filter (fn [x] (< x 25)))) (when sum? (reduce +))) col))

adamkowalski22:03:51

or something like that

adamkowalski22:03:59

actually wow, some-> and some->> literally do everything I want. thanks a lot guys!

dmuylwyk23:03:53

Hello. Anyone available to help me with lein deploy and Artifactory?

seancorfield23:03:28

(as suggested on #C03RZGPG1 : try #C0AB48493 )