Fork me on GitHub
#beginners
<
2020-06-08
>
yokoyama.km01:06:33

Which one is more idiomatic: (. System (getProperties)) or (. System getProperties) ? I know they're equivalent and I find the latter more readable. Any opinions? Thank you.

yokoyama.km01:06:33

Which one is more idiomatic: (. System (getProperties)) or (. System getProperties) ? I know they're equivalent and I find the latter more readable. Any opinions? Thank you.

smith.adriane01:06:16

not that you need more options, but for static methods, I use (System/getProperties)

hiredman01:06:12

That is the correct idiomatic form

yokoyama.km02:06:34

I'm aware of this third alternative, but I'm actually thinking in broader scenarios like when using with the .. or doto macros.

smith.adriane02:06:03

I don’t think I’ve ever used the . or .. syntax. if it’s a static call, I use the above. here’s an example of calling a non static method: (.getBytes ^String line "utf-8")

smith.adriane02:06:21

here’s an example with doto:

(doto (new java.util.HashMap) 
  (.put "a" 1) 
  (.put "b" 2))

alexmiller02:06:02

Regarding your original question, no preference

yokoyama.km03:06:20

Thank you, all!

alexander.moskvichev11:06:52

Hi. I'm trying to use a React component that takes a generator function as a parameter. Can I use lazy-sec for it? The component is written in typescript, I use reagent.

alexander.moskvichev11:06:52

Hi. I'm trying to use a React component that takes a generator function as a parameter. Can I use lazy-sec for it? The component is written in typescript, I use reagent.

alexander.moskvichev13:06:12

Well, I've made a try. Got TypeError: gen.next is not a function. Are there any possibilities?

alexander.moskvichev05:06:14

Found a way, using GeneratorFunction constructor, but I'll keep it as a last resort.

nfedyashev12:06:35

How can I become proficient in mocking/stubbing in Clojure? Any deep dive course/book that covers it? From what I see there is no lack of great manuals/courses on core.test or test.check/generative testing but not much info on mocking. I’m trying to understand why is that. Perhaps it is not as helpful/important as in other languages? Or is it as simple as just learning how to use with-redefs/with-redefs-fn?

nfedyashev12:06:35

How can I become proficient in mocking/stubbing in Clojure? Any deep dive course/book that covers it? From what I see there is no lack of great manuals/courses on core.test or test.check/generative testing but not much info on mocking. I’m trying to understand why is that. Perhaps it is not as helpful/important as in other languages? Or is it as simple as just learning how to use with-redefs/with-redefs-fn?

epransky12:06:42

have you seen this lib: https://github.com/alexanderjamesking/spy? docs are pretty good IMO

jkrasnay12:06:47

In my experience, Java devs use mocking to unit test some business logic that calls a stateful resource like a database, often because their code is a big, dependency-injected object graph. Clojure nudges you towards putting your business logic into pure functions that can easily be unit tested without mocking things.

nfedyashev12:06:51

Haven't seen it before. I saw mockery, clj-fakes + simple with-redefs.. It's kind of confusing where to start and what to use for a beginner. Anyway, thanks! I'll check out alexanderjamesking/spy

epransky12:06:39

ive used it and plenty of devs at my workplace use it and it works great

jkrasnay12:06:08

Places in my code where I do access external resources tend to be small functions that delegate the heavy lifting to pure functions. I test these with integration tests.

folcon12:06:38

Personally, I’ve not seen the need for mocking much in my clojure code, as things like databases convert data into clojure data structures as a first step. Everything further in the system just manipulates / works with those, so your mock is just a normal clojure datastructure, perhaps with some particular shape. Actions like sending emails etc are a simple function of datastructure to effect which a function at the edge consumes to do the task, so anything generating those effects you just need to test the produced structure is correct. Eric’s book Grokking Simplicity covers some of this type of thinking really well I think =)…

somedude31413:06:04

Is there a shortcut for getting a value from a string indexed map? ("key" m) doesn't work like keyword indexed map. Is it only (get m "key")?

dpsutton13:06:02

maps are invocable: ({"a" "b"} "a") -> "b"

somedude31413:06:52

Oh that's cool! Thanks.

somedude31413:06:47

why they made it work both ways for keyword indexed maps though (`(:key m)` (m :key))?

somedude31413:06:47

why they made it work both ways for keyword indexed maps though (`(:key m)` (m :key))?

dboitnot13:06:19

They're both super-useful. For instance, it's very handy to be able to pass a keyword to a higher-order function like map.

dpsutton13:06:33

its too convenient for you?

dpsutton13:06:33

its too convenient for you?

somedude31413:06:53

lol just trying to figure out which one to use when I have string keys in the same function... perhaps I should aim for consistency in my case

dpsutton13:06:03

haha fair. i'm sure several people always use (get m k) for this reason

dpsutton13:06:40

note that if your map is nil (nil :key) will throw, if your key is nil (k m) will throw. But in both cases (get m k) will do just fine

somedude31413:06:45

I see. Thanks for the clarifications.

dpsutton13:06:18

its simply what should happen if a keyword is in the first position (:foo ...) It could either throw an error as not a function, or it look up

jimka.issy14:06:58

I'm trying to write a (defrecord ...) , can someone tell me where the docstrings to? I don't see it in the https://clojuredocs.org/clojure.core/defrecord. I suspect I can include a dostring for the defrecord itself and also one docstring per field ?

jimka.issy14:06:58

I'm trying to write a (defrecord ...) , can someone tell me where the docstrings to? I don't see it in the https://clojuredocs.org/clojure.core/defrecord. I suspect I can include a dostring for the defrecord itself and also one docstring per field ?

andy.fingerhut14:06:06

I don't have a complete list handy, but not all constructs in Clojure enable providing doc strings.

dpsutton14:06:47

i believe this is one of those. emit-defrecord doesn't mention anything about docstrings

andy.fingerhut18:06:18

You can alter the metadata on one or both of the record constructor functions that defrecord creates, modifying its doc string.

jimka.issy09:06:59

@. sorry I didn't understand your comment.

andy.fingerhut12:06:08

Demonstration of my meaning in a REPL session:

user=> (defrecord MyRec1 [a b])
user.MyRec1
user=> (->MyRec1 1 2)
#user.MyRec1{:a 1, :b 2}
user=> (doc ->MyRec1)
-------------------------
user/->MyRec1
([a b])
  Positional factory function for class user.MyRec1.
nil
user=> (alter-meta! #'->MyRec1 assoc :doc "My preferred doc string")
{:arglists ([a b]), :doc "My preferred doc string", :line 1, :column 1, :file "NO_SOURCE_PATH", :name ->MyRec1, :ns #object[clojure.lang.Namespace 0x75d0911a "user"]}
user=> (doc ->MyRec1)
-------------------------
user/->MyRec1
([a b])
  My preferred doc string
nil

andy.fingerhut12:06:56

You can change the :doc key of the metadata map of any Var in this way, changing its doc string.

andy.fingerhut12:06:23

That gives you one doc string per record, but not one per field.

jimka.issy14:06:44

I'm reading chapter 13 of https://www.braveclojure.com the hard copy. It mentions three ways to instantiate a record. MyRecord. , ->MyRecord, and map->MyRecord. However, the https://clojuredocs.org/clojure.core/defrecord mentions the second two and recommends (in the examples) writing your own what looks like a factory function make-MyRecord . I using the MyRecord. notation recommended against?

jimka.issy14:06:44

I'm reading chapter 13 of https://www.braveclojure.com the hard copy. It mentions three ways to instantiate a record. MyRecord. , ->MyRecord, and map->MyRecord. However, the https://clojuredocs.org/clojure.core/defrecord mentions the second two and recommends (in the examples) writing your own what looks like a factory function make-MyRecord . I using the MyRecord. notation recommended against?

andy.fingerhut14:06:53

I believe some people really do not like to use any kinds of APIs that rely on positional arguments when the number of those goes over 2 or 3, due to difficulty in people remembering the purpose of that many positions. The MyRecord. notation would also require an import statement if you wanted to avoid using the fully qualified name when called from other Clojure namespaces.

alexmiller14:06:40

The MyRecord. notation is doing Java interop on the implementation details of records - it is preferred to use the generated constructor functions

jimka.issy14:06:55

In my experimentation (map->MyRecord ...) seems very readable as I'm not relying on positional arguments.

jimka.issy15:06:48

Once I've defined a record MyRecord how can I ask whether a given object is a MyRecord ?

alexmiller15:06:07

(instance? MyRecord obj)

jimka.issy15:06:10

Ok, so defrecord doesn't define a trival type predicate for me? That's fine.

alexmiller15:06:22

no, although we've talked about that

jimka.issy15:06:41

easy for the user to write.

jimka.issy14:06:24

I don't understand the use model of extend-type . The model in  https://slack-redir.net/link?url=https%3A%2F%2Fwww.braveclojure.com is that for each type, you define all the functions specializing on it per protocol. It seems more logical, in my case to turn it 90 degrees.

jimka.issy14:06:24

I don't understand the use model of extend-type . The model in  https://slack-redir.net/link?url=https%3A%2F%2Fwww.braveclojure.com is that for each type, you define all the functions specializing on it per protocol. It seems more logical, in my case to turn it 90 degrees.

jimka.issy14:06:42

I.e. for each function in my application, define what that function does for type1 and for type2, next to each other in the code. I.e., I've defined a record, I'd like to define what the function does for MyRecord, and then what the function does for clojure.lang.Sequential . Do I need to create 10 different protocols each with a single function?

dpsutton14:06:47

that sounds close to extend-protocol

jimka.issy14:06:22

Or perhaps I don't need a protocol at all, just 10 multi methods which all share the same dispatch function. i.e., to distinguish between MyRecord and clojure.lang.Sequential

alexmiller14:06:07

you have n protocols and m types - extend does 1:1, extend-protocol extends one protocol to many types. extend-type extends many protocols to one type (they are nary in different dimensions)

jimka.issy14:06:28

One of the purposes why I'm experimenting with defining a record is because previously I had represented my data as an array of maps. But an array of maps is a sequence. clojure.lan.Sequential and several times it spent a long time debugging because i was running the sequence code on the array of maps, rather than recognizing it as its own type.

alexmiller14:06:04

I'm assuming you don't really mean "array" (like Java array)

alexmiller14:06:34

but really sequence or other sequential collection

alexmiller14:06:36

basically I'm not sure I understand what you're saying. creating a record to represent a sequential collection seems like a bad idea

jimka.issy14:06:39

alex, when you say "extends one protocol" do you mean a-la-fois extends all 10 functions of the protocol for a type? Or can each call to extend-type just mention one of the functions? I.e. make 10 calls to extend-type in different places/files in my code?

alexmiller14:06:11

each time you extend, you are replacing anything that has been extended for that type before, so you can't extend one method here and one method elsewhere

jimka.issy14:06:17

Alex the fact that it is a sequence is accidental. I'm representing a finite state machine as an array of states, and each transition tells the index of the destination state. Semantically a state machine is not a sequence, it only happens to be because my choice of implementation.

jimka.issy14:06:06

so the second extend is not really an extend, but an unextend-then-extend.

alexmiller14:06:20

yes (for either extend-type or extend-protocol)

jimka.issy14:06:47

Perhaps I need to use multimethods, as I can place the method definitions anywhere in the code in any order and in any file.

alexmiller14:06:11

if you want that, that is more the multimethod model

jimka.issy14:06:13

As far as I understand now, I only have two interesting types. MyRecord and everything else.

jimka.issy14:06:08

perhaps 3 cases. MyRecord, sequences, and everthing else is an error, but that 3rd case can be handled directly in the dispatch function.

jimka.issy14:06:47

protocols don't seem very lispy, at least at first glance.

jimka.issy14:06:10

well i'm only a 10 minute protocols-expert at this point in time.

alexmiller15:06:56

they are designed to tap into what's super optimized on the jvm, so they are definitely coming from a different perspective (type-based first arg dispatch)

mostlychris14:06:18

Need to collab with a team that has never seen clojure. Best resource to get them started so they can at least read and understand the code? (versus developing new features)

dpsutton15:06:08

if you post this on reddit, i bet you will get some answers. i think yogthos is active there and he has lots of experience ramping up new-to-clojure coworkers

jimka.issy15:06:24

Is there a lien channel on clojurians?

dpsutton15:06:41

channels auto complete for me. i can hit cmd-k lein and it shows me the matching channels

jimka.issy15:06:17

apparently there are many more channels than the ones appearing in my reader.

anicholslcsw57816:06:40

Has anyone seen a version of the animal guessing game written in Clojure?

ericihli16:06:49

How would I do what it looks like I intend the following macro to do?

(defmacro pprint
  [& args]
  `(do
     (map #(println % ": " ~%) '~args)))
That errors with an "Unable to resolve symbol" on the ~%.

alexmiller16:06:09

I think you'd want ~'% in both places, but maybe better to use the fn form

alexmiller16:06:06

and you probably don't want a ' on '~args

alexmiller16:06:48

and in general, I'd try to avoid map for side effects - run! prob preferred here

alexmiller16:06:20

are you trying to print both the name of the arg and the value of it?

ericihli16:06:27

Yes. Didn't know about run!. Thanks.

ericihli17:06:18

What do you mean about having ~'% in both places? How would that print 2 different things?

ericihli17:06:26

Also, without the ' on '~args, it expands to (arg1 arg2) and blows up because it tries to call the function arg1 rather than treating it like a list of data to iterate over.

ericihli17:06:00

I thought this would be closer, but it still doesn't work. It expands to something that looks correct.

(defmacro pprint
  [& args]
  `(do
     (run! #(println % ": " (eval %)) '~args)))

(let [a 1 b 2]
  (macroexpand `(pprint a b)))
;; => (do
;;     (clojure.core/run!
;;      (fn*
;;       [p1__31102__31103__auto__]
;;       (clojure.core/println
;;        p1__31102__31103__auto__
;;        ": "
;;        (clojure.core/eval p1__31102__31103__auto__)))
;;      '(ezmonic.moniq/a ezmonic.moniq/b)))

(let [a 1 b 2]
  (pprint a b))
;; => 1. Caused by java.lang.RuntimeException
;;    Unable to resolve symbol: a in this context

alexmiller17:06:31

you need to think more carefully about what happens at compile time vs runtime

ericihli17:06:43

Is the runtime a in (let [a 1] ,,,) not equal to ezmonic.moniq/a?

alexmiller17:06:09

no, you don't want that to be qualified

alexmiller17:06:44

at compile time you can capture the local names of the symbols (~args), and you want to emit code with those (unqualified) symbols and that then evaluates those symbols at runtime

kcheung33117:06:39

I think you can use Unquote splicing here

[[email protected]]

chase-lambert17:06:38

Does anyone have a good tutorial explaining how to use try/catch for error handling? Specifically in Clojure, of course, but I don't have much experience using it in any language either.

chase-lambert17:06:38

Does anyone have a good tutorial explaining how to use try/catch for error handling? Specifically in Clojure, of course, but I don't have much experience using it in any language either.

chase-lambert17:06:14

As an example, I am prompting a user to enter a number. If it is not a number I just want to loop back up to the start of the function and prompt again until they input it correctly.

chase-lambert17:06:36

Something like

(defn my-fn [] 
  (let [input (read-line)] 
    (try   
      (Long/parseLong input) 
        (catch Exception e (my-fn)

chase-lambert17:06:23

But I am so unclear on the syntax and semantics of such a thing. Unfortunately someone helped me on this before but I lost the code and can't search back for it.

smith.adriane17:06:24

exceptions are kind of annoying to work with. however, some java calls require using them. I still prefer to try to work with values as much as possible. I would probably write my-fn like:

(defn my-fn [] 
  (let [input (read-line)
        num (try   
              (Long/parseLong input) 
              (catch NumberFormatException e
                nil))]
    (if num
      num
      (recur))))

chase-lambert17:06:47

Ahh, I think that is what I had before. Thank you! So how would you test for a valid number input "your way" using values instead of exceptions?

smith.adriane17:06:04

same as above. basically, making the try/catch as small as possible

smith.adriane17:06:18

ideally, Long/parseLong would return a number and whether or the parse succeeded rather than throwing an exception

chase-lambert17:06:19

ok, ok. And for other problems instead of the exact error like NumberFormatException can I use Exception as a catch-all for any errors.

smith.adriane17:06:09

typically, you don’t want to catch Exception

smith.adriane17:06:05

you usually want to be specific as possible. it’s unlikely that your code will know how to generically recover from an unknown exception

smith.adriane17:06:38

often, it’s better to abort than to blindly continue like nothing had happened

chase-lambert17:06:44

Ahh ok. I guess I was asking because in my laziness I usually just re-prompt the user if anything isn't to my liking. haha

chase-lambert17:06:55

I'm currently doing Harvard's CS50 using Clojure and the guidance for many exercises is something like "If user doesn't give you a valid number, just re-prompt until they do"

smith.adriane17:06:58

well, like everything, there’s a always an exception. that’s actually a pretty good example of where catching Exception and just printing the error makes sense

chase-lambert17:06:26

Good stuff. Thank you so much!

alexmiller17:06:22

@ericihli something like this would print in map form if that worked

(defmacro print-args [& args] `(println (zipmap '[[email protected]] [[email protected]])))

alexmiller17:06:22

@ericihli something like this would print in map form if that worked

(defmacro print-args [& args] `(println (zipmap '[[email protected]] [[email protected]])))

ericihli18:06:05

Thanks. I kept fiddling with it and learned a lot. I had forgotten that syntax quoting expanded things to fully qualified symbols and regular quoting didn't.

jimka.issy08:06:31

Hi Alex, I tried to modify your macro, and I don't understand the macro expansion error. Can you help?

(defmacro print-args [& args] `(map (fn [expr value] (println [expr value]))  '[[email protected]] [[email protected]]))
When I try to evaluate the following:
(print-args (+ 1 10) (+ 2 20) (+ 3 30 300))
it expands (at least according to cider) as I expect to:
(map
  (fn [expr value] (println [expr value]))
  '[(+ 1 10) (+ 2 20) (+ 3 30 300)]
  [(+ 1 10) (+ 2 20) (+ 3 30 300)])
But I get the error:
Syntax error macroexpanding clojure.core/fn at (clojure-rte:localhost:49674(clj)*:270:19).
(clojure-rte.core/expr clojure-rte.core/value) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
clojure-rte.core/expr - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

jimka.issy08:06:27

I can evaluate the expression which is the printed macro expansion:

(map
  (fn [expr value] (println [expr value]))
  '[(+ 1 10) (+ 2 20) (+ 3 30 300)]
  [(+ 1 10) (+ 2 20) (+ 3 30 300)])
So I'm guessing there is a problem with one of the symbols is different than what I think it is????

jimka.issy08:06:38

Perhaps I need a dorun around the map? But I don't think that is the problem of the macro expansion error

neo255119:06:16

Are there any guide on using web workers with ClojureScript? Like is it possible to connect a repl to a web worker?

neo255119:06:16

Are there any guide on using web workers with ClojureScript? Like is it possible to connect a repl to a web worker?

folcon19:06:31

There's a couple of examples: https://github.com/jtkDvlp/cljs-workers I've not had a chance to try it yet, but it might be useful :)...

jimka.issy19:06:23

It looks to me like the repl does not call print-method to print records. Could this be the case? Is that a bug?

alexmiller20:06:04

there no special exclusion of anything like that so I doubt that is the actual story

alexmiller20:06:45

there may be default printers for IRecord or eventually everything falls into Object

alexmiller20:06:45

there may be default printers for IRecord or eventually everything falls into Object

jimka.issy08:06:46

why do I say that. OK probably I'm just very confused. But here is what I see. When I define a record, and a print-method for it, then simply allocate the record in the repl it prints as a hash map, rather than printing with my print-method.

jimka.issy08:06:48

clojure-rte.core> (defrecord Foo [x])
clojure_rte.core.Foo
clojure-rte.core> (defmethod print-method Foo [v w]  (.write w (format "#<Foo x=%s>" (:x v))))

#multifn[print-method 0x7665aecc]
clojure-rte.core> (map->Foo {:x 100})
{:x 100}
clojure-rte.core> 

jimka.issy08:06:07

I'd expect`#<Foo x=100>` to be printed

matejvasek11:06:14

this is odd, it works for me using nrepl

matejvasek11:06:15

you could try also implement print-dup

alexmiller12:06:30

works as expected for me in clj too

matejvasek12:06:16

could repl call pprint instead of print for whatever reason?

jimka.issy13:06:43

for those for whom it works, are you using cider? is it possible that cider has its own printer which subverts print-method?

matejvasek13:06:40

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))

jimka.issy13:06:52

@ as neither print-method nor print-dup are documented, which one SHOULD I implement if I do not intend the printed representation to be readable by the Clojure reader?

alexmiller13:06:08

cider definitely has tweaks to the printer (I am not using cider)

alexmiller13:06:45

I'd say print-method

matejvasek13:06:51

does explicit print (print (map->Foo {:x 100})) works?

matejvasek13:06:54

also try clojure.pprint/simple-dispatch -- this should work for pprint

jimka.issy13:06:03

clojure-rte.core> (pprint (map->Foo {:x 100}))
{:x 100}
nil
clojure-rte.core> (print (map->Foo {:x 100}))
#<Foo x=100>nil
clojure-rte.core> 
pprint doesn't work, but print does. Is that normal?

matejvasek13:06:10

I am no expert on this. It seem you have to implement different multimethod for pprint:

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))

matejvasek13:06:45

> to be readable by the Clojure reader are you trying to create custom tagged literal?

jimka.issy13:06:13

No, I'm not trying to create a tagged literal. Re pprint If I implement the clojure.pprint/simple-dispatch function, I probably should implement the 1-ary and 2-ary versions.

jimka.issy13:06:04

BTW how can I define multiple arity defmethods?

jimka.issy13:06:30

Here is how pprint seems to be defined

(defn pprint 
  "Pretty print object to the optional output writer. If the writer is not provided, 
print the object to the currently bound value of *out*."
  {:added "1.2"}
  ([object] (pprint object *out*)) 
  ([object writer]
     (with-pretty-writer writer
       (binding [*print-pretty* true]
         (binding-map (if (or (not (= *print-base* 10)) *print-radix*) {#'pr pr-with-base} {}) 
           (write-out object)))
       (if (not (= 0 (get-column *out*)))
         (prn)))))

jimka.issy13:06:53

The https://clojuredocs.org/clojure.pprint/simple-dispatch of simple-dispatch is not very enlightening.

matejvasek14:06:03

before diving into anything, try run :

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))
does it fix printing for you?

matejvasek14:06:39

simple-dispatch takes single argument -- the object to print

matejvasek14:06:33

or maybe you could try set cider printer fn

jimka.issy14:06:24

I tested this with clj, and it works as advertised. the repl uses print-method , so it seems cider is confused.

jimka.issy14:06:07

if simple-dispatch takes a single argument, how can it print to a file if pprint is called with a 2nd argument?

matejvasek14:06:08

I am not using cider so I can't help

matejvasek14:06:27

(setq cider-repl-use-pretty-printing nil) I just found this does it change anyting?

jimka.issy14:06:33

re: cider, yes I've opened a thread on the cider channel ...

matejvasek14:06:34

my guess is that it binds *out* dynamic var

jimka.issy14:06:36

setting the emacs variable cider-real-use-pretty-printing works for this case. But this is a bit scary.

jimka.issy14:06:45

I'll experiment further and discuss in the cider Chanel.

jimka.issy14:06:48

thanks for the advice.

alexmiller20:06:11

maybe unroll to why you say that

ericihli20:06:35

(peek (map identity (range 20))) That raises. Can't peek into a lazy seq, right? Why does (peek (doall (map identity (range 20)))) also fail for the same reason? What am I missing about doall?

alexmiller20:06:03

what are you missing about peek?

alexmiller20:06:25

peek works on instances of IPersistentStack, which are lists, vectors, and queues (not seqs)

smith.adriane20:06:19

doall will force evaluation, but it won’t convert a seq into something besides a seq

alexmiller20:06:21

if you just want to look at the first thing in a seq, use first

ericihli20:06:15

Ah. I see. I thought the problem was that the lazy thing wasn't realized. Not that realizing a lazy seq still gave you a seq and peek didn'k work on seqs.

alexmiller20:06:24

reading that back, I meant that question with a 😀 not sure if that came across!

alexmiller20:06:38

generally, I would not use peek/pop unless you're actually using a collection as a stack