On Jan 14, 10:10 am, Chouser <chou...@gmail.com> wrote:
> I also think it's unhelpful for codebases to stray further from the
> builtin functions than needed, because it makes that code harder to
> read as well.  So I will consider each of these more carefully.

Thanks for your detailed response!  To keep this manageable, I'll cut
out the parts where I don't think comments are needed.


> > (defn combinations "Take a seq of seqs and return a lazy seq of
> > ordered combinations (pick 1 from each seq)"
> >  [seqs]
> >  (if (empty? seqs) '(())
> >    (forcat [item (first seqs)]
> >      (map #(cons item %) (combinations (rest seqs))))))
...
> Almost identical to clojure.contrib.lazy-seqs/combinations
> (require '[clojure.contrib.lazy-seqs :as ls])

Oops, I forgot to check lazy-seqs before writing this one.  Ignore
this.


> > (defn maximal-elements [f s]
> >  "Return a seq of elements of s maximizing (f elt)."
> >  (when (seq s)
> >    (loop [max-elts (first s),
> >           max-val (f (first s)),
> >           rest-elts (rest s)]
> >      (if (empty? rest-elts)
> >          max-elts
> >        (let [next-val (f (first rest-elts))]
> >          (cond (< next-val max-val) (recur max-elts max-val (rest rest-
> > elts))
> >                (= next-val max-val) (recur (cons (first rest-elts) 
> > max-elts) max-
> > val (rest rest-elts))
> >                (> next-val max-val) (recur [(first rest-elts)] next-val 
> > (rest rest-
> > elts))))))))
>
> I'm having a hard time imagining when maximal-elements would be
> useful.  What have you used it for?  Looks like a good candidate for
> application code. :-)

I've used it for picking the best successors in a search algorithm,
particularly as (first (maximal-elements ...)) or (random-element
(maximal-elements ...)).

But, I can imagine it would be useful in many situations, i.e. things
like.
user> (maximal-elements second {:bob 10 :lisa 20 :peter 20})
([:peter 20] [:lisa 20])

There don't seem to be any other utils that pull out the "best" element
(s) of a seq, so there seems to be a role for this.  But, I've only
needed it once so far, so maybe it's not so important.

> > (import '(java.util HashSet))
> > (defn distinct-elts? "Are all of the elements of this sequence
> > distinct?  Works on infinite sequences with repititions, making it
> > useful for, e.g., detecting cycles in graphs."
> >  [s]
> >  (let [hs (HashSet.)]
> >    (loop [s (seq s)]
> >      (cond (empty? s)                    true
> >            (.contains hs (first s))        false
> >            :else (do (.add hs (first s)) (recur (rest s)))))))
>
> Is there any reason the builtin 'distinct?' couldn't handle these
> cases as well?  What does "elts" stand for?

I suppose this is the same as (apply distinct? s).  I was under the
impression that apply always evaluated the entire arg-seq, but I just
realized that this isn't the case -- I guess it evaluates everything
but the "& rest" params, which can remain a lazy seq?  So, for
instance, this works

user> (apply distinct? (interleave (iterate inc 0) (iterate inc 10)))
false

which was the whole point of distinct-elts? for me.  So, ignore this
one too.


> > (defn concat-elts "Lazily concatenate elements of a seq of seqs." [s]
> >  (when (seq s) (lazy-cat (first s) (concat-elts (rest s)))))
>
> > (defn lazy-mapcat "Like mapcat but is lazy like map" [f s]
> >  (concat-elts (map f s)))
>
> > (defn map-when "Like map but discards logically false entries"
> >  [fn & seqs]
> >  (filter identity (apply map fn seqs)))
>
> Sufficient laziness is another conversation entirely. :-)

Yes.  I would be happy to drop concat-elts and lazy-mapcat, if the
built-in concat (which seems to be used by mapcat) was changed to be
less eager (in the apply sense just mentioned above).

user> (take 0 (apply concat (report-seq "" [[1] [2] [3] [4] [5]])))
(first  [1] )
(rest after  [1] )
(first  [2] )
(rest after  [2] )
(first  [3] )
(rest after  [3] )
nil

This can end up being a real drag in situations like (apply concat
(map #(recurive-call ...) ...)), since it may result in an exponential
amount of extra work being done.  Any chance of this being changed?
(It should be possible to get rid of the last 4 evaluations here,
without getting into the territory of our last conversation about
laziness :))

> > (defn chunk "Lazily break s into chunks of length n (or less, for the
> > final chunk)."
> >  [n s]
> >  (when (seq s)
> >    (lazy-cons (take n s)
> >               (chunk n (drop n s)))))
>
> This is so close to 'partition', that I think I'd prefer there be only
> one, or at least have related names.

OK, I agree the name should be closer to partition (which I'd
forgotten about), but I think this one is at least as useful as
partition  (You can recover partition from this one by (take-when #(=
(count %) n) (chunk n s))) but not vice-versa.)   Any suggestions on a
name?


> > (defn mevery? "Like every but takes multiple seq args like map."
> >  ([f & seqs]
> >     (or (some empty? seqs)
> >         (and (apply f (map first seqs))
> >              (recur f (map rest seqs))))))
>
> Is that the same as this?
>
> (defn my-mevery? [f & seqs]
>   (every? identity (apply map f seqs)))

Yes, I believe so ... that's a much nicer way of writing it.

> Someone suggested all the functions that currently take a predicate
> and one seq ought to take multiple seqs and work like 'map'.  I
> suppose this would include: every? any? some etc.  Seems pretty
> convenient, and I've not heard arguments against it.  But without
> that, I think I'd rather use 'identity' and 'apply map' than have an
> m-version of all the seq functions.

Yes, I would of course prefer that the built-in versions take multiple
seqs and work like map.
If not, I guess I agree with you that it's better not to create m-
versions of everything.

> > (defn map-map "Like map, but expects f to return pairs/map entries
> > that are combined to make a map return value."
> >  [f & maps] (reduce #(conj %1 %2) {} (apply map f maps)))
>
> As discussed, perhaps (into {} (map f ...)) is good enough.

Agreed.

> > (defmacro lazy-get "Like get but lazy about evaluating the default
> > value"
> >  [m k d]
> >  `(if-let [pair# (find ~m ~k)]
> >       (second pair#)
> >     ~d))
>
> I've never needed this.  How expensive is your default value?

I haven't needed it yet, but I've used it in Lisp several times.  The
typical use-case is where I am working with maps where values are big
structs tailored to the key, and nil values are allowed.  Anyway, the
main purpose of this was for safe-get, which I definitely want to work
with nil values.


> > (defn safe-get "Like get but throws an exception if not found"
> >  [m k]
> >  (lazy-get m k (throw (IllegalArgumentException. (format "Key %s not
> > found" k)))))
>
> Ah, I see now.  Unless you're storing nils in your map, this is the
> same as (or (m k) (throw ...))
>
> I think I'd rather this be a feature of the map itself, rather than
> depend on each 'get' to be a 'safe-get'.  Perhaps if 'get' finds
> nothing and no default value is given, it could check the map's
> metadata for a :default-value or :throw-unless-found item.

I think I'd be equally happy with "safe maps" as you suggest as using
safe-get.  But, one or the other is definitely useful ... I've been
bitten several times by misspelled keys, which can create difficult-to-
find bugs.


> > (defn merge-agree "Like merge but returns nil if there are
> > inconsistent key/value pairs."
> >  ([] {})
> >  ([map] map)
> >  ([m1 m2 & maps]
> >     (when (every? (fn [[k v]] (= v (get m1 k v))) m2)
> >       (apply merge-agree (merge m1 m2) maps))))
>
> Maybe if it accepted the = function as a arg (like merge-with) it
> would be a little more general?

Well, then which value would you use (if two values are my-equal but
not =)?

I suppose this should also take a default value like get, so one can
distinguish empty-map from inconsistent-maps.  I've only found one use
for this one so far, so maybe others can chime in if they think they'd
find it useful.

> > (defn merge-reduce "Combines maps, reducing sets of values with same
> > key. Assumes nil value = not present.  The first map entry must be a
> > real map, but the remaining arguments can be seqs of map entries/key-
> > value pairs."
> >  ([f ] {})
> >  ([f m1 & maps]
> >     (reduce (fn [m [k v]]
> >               (if-let [v2 (get m k)]
> >                   (assoc m k (f v2 v))
> >                 (assoc m k v)))
> >             m1
> >             (concat-elts maps))))
>
> As discussed, this is like the builtin merge-with.

Right.  However, now that I think of it some more, I think it's
actually really useful to be able to pass seqs of pairs or map entries
rather than maps.

This lets me do things like

user> (let [accounts {:bob 2, :annie 3}
            deposits [[:bob 10] [:annie 3] [:chouser 100] [:bob 5]]
            withdrawls [[:bob -2]]]
        (merge-reduce + accounts deposits withdrawls))
{:chouser 100, :bob 15, :annie 6}

With merge-with, I think I would have to say
(apply merge-with + accounts (map (fn [[k v]] (hash-map k v)) (concat
deposits withdrawls)))

which is considerably uglier and less clear IMO.  Any chance of
changing merge-with in this way?
(Note this is actually similar to clojure.set union/intersection/
difference, which only require that the first element be a set and the
other can be a seq.  And, while I'm on this tangent, any reason those
functions don't accept arbitrary numbers of arguments?)


> > (defn prln "Print all arguments and return the first argument"
> >  [& args] (do (println (apply print-str args)) (first args)))
>
> You use this for debugging?

Yep.  Useful to wrap it around an expression, like CL's "print".

> > (defn xor [& args]
> >  (odd? (count (filter identity args))))
>
> Never needed it.

I've only used it once, but especially if you've got some expensive
predicates (xor (p1 ...) (p2 ...)) is much nicer than
(let [p1? (p1 ...)
      p2? (p2 ...)]
   (or (and p1? (not p2?)) (and (not p1?) p2?)))


> > (defmacro forcat
> >  "Like for, but concatenates the results."
> >  [& args] `(concat-elts (for ~...@args)))
>
> Maybe instead of adding 'forcat' for symmetry with 'mapcat', we should
> ask for 'mapcat' to be removed.

Hah ... I use mapcat and forcat pretty frequently, although I guess I
wouldn't be devastated to say (apply concat (map/for ..)) instead.
But, I do kinda think it should be both or neither.

-Jason
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to