Fork me on GitHub
#beginners
<
2020-02-29
>
hindol06:02:42

What is the way to create a memoized fn in let/letfn? Must I use def?

andy.fingerhut06:02:40

You can take any (fn ...) form and surround it with (memoize (fn ...)) , and I believe that should memoize it.

andy.fingerhut06:02:31

If you prefer a different memoizing library other than the function Clojure.core/memoize , e.g. core.memoize, they should have a function that takes a function to be memoized as an argument, and returns a memoized version of that function. That should work regardless of whether that function has a name or not.

hindol06:02:22

The function is recursive and without a def, clj-kondo is complaining. I took its word but now that I try it, it actually works!

😞 4
hindol06:02:33

That was a stupid thing to ask, sorry.

andy.fingerhut06:02:56

No worries. You may want to create an issue for clj-kondo if you think it is complaining about something that it should not.

hindol07:02:30

Actually, clj-kondo is right. The following does not work,

(let [f (memoize
         (fn [x]
           (if (pos? x)
             (+ 1 (f (dec x)))
             0)))]
  (f 10))

;; => Unable to resolve symbol: f in this context

hindol07:02:26

Previously, I was testing things with a running REPL, and it may have taken the symbol from the previous eval and worked. A freshly booted REPL would have thrown error.

borkdude07:02:05

It works when you put the symbol f in front of the arg vec

hindol07:02:43

But will it still call the memoized version recursively?

dpsutton07:02:11

As written you would never call f with a value it had already computer

dpsutton07:02:06

(let [f (memoize
          (fn f [x]
            (prn "computing: " x)
            (if (neg? x)
              0
              (+ (f (- x 1)) (f (- x 2))))))]
  (f 3))

hindol07:02:51

That did not work for me. This does memoization properly,

(def f
  (memoize
   (fn [x]
     (prn "Called with" x)
     (if (pos? x)
       (+ 1 (f (dec x)))
       0))))
This doesn't,
(def f
  (memoize
   (fn f [x] ; Notice the "f"
     (prn "Called with" x)
     (if (pos? x)
       (+ 1 (f (dec x)))
       0))))

dpsutton07:02:35

my example was showing it does not memoize correctly. a hack is the following:

(let [g (memoize
          (fn g [f x]
            (prn "computing: " x)
            (if (neg? x)
              0
              (+ x (f f (- x 1)) (f f (- x 2))))))]
  (g g 3))

dpsutton07:02:00

does your goal include the function must be definable in a let statement?

hindol07:02:51

Kind of. It was a one-off function that I did not want to put in the namespace. But I'll settle for def.

penryu08:02:39

reworking 4clojure problems is a great way of being reminded how much clojure.core has grown since 1.4

penryu08:02:30

Today's reminder: as->

penryu08:02:07

...and update 😕

andy.fingerhut19:02:40

I suspect penryu is commenting that 4clojure still uses Clojure 1.4 to evaluate code you type into it for solving problems, thus anything added to Clojure's core library after that is not available when solving those problems.

👍 4
penryu19:02:39

Yeah, I resumed 4clojure from several years ago. Was surprised when it a solution would work just fine locally, but fail with resolution error in web.

seancorfield19:02:33

@penryu You can always run a Clojure 1.4 REPL locally for developing your solutions 🙂

penryu19:02:18

@seancorfield Yeah, I almost did, too. But I think I like being reminded of the differences.

seancorfield19:02:28

True, that can be a good learning process. Easy to run 1.4 via the CLI (if you have my dot-clojure file in place):

$ clj -A:1.4
Downloading: org/clojure/clojure/1.4.0/clojure-1.4.0.jar from central
Clojure 1.4.0
user=> (clojure-version)
"1.4.0"
user=>

penryu23:02:57

I have incorporated several entries from your ~/.clojure/deps.edn. Thanks for sharing it!

seancorfield20:02:07

As a library author/maintainer, it's sometimes very frustrating when you have to support an older version of Clojure and can't use some new cool language feature! At least Clojure developers migrate to newer versions fairly quickly.

seancorfield20:02:37

Almost 90% of developers are already using 1.10, only 25% are still using 1.9, 10% still using 1.8. Only 1.4% are using earlier versions.

penryu21:02:09

Clojure 1.4 is one of the 1.4%, and still the one 4Clojure! (sorry, couldn't resist)

8
seancorfield22:02:18

We got started at work on an alpha build of 1.3 and in nine years there have been almost no breaking changes which is a great accolade for Clojure's stability! The worst cases have been new core functions added that collided with some of our own functions -- which was always easy to fix.

penryu22:02:58

Same! When I came back to Clojure after 4-5 years, everything I'd written worked without a change. Yes, at most an extra :as in the require.

seancorfield20:02:14

That at least means libraries can drop support for older versions relatively quickly.

Albert M20:02:09

Would anyone be interested in starting a study/accountability group? I'm a javascript/react developer but looking to build up a Clojure portfolio. We would meet a minimum of once a week, either in person but most likely online.

👍 4
hindol21:02:26

What is an accountability group?

penryu21:02:06

sounds like a group reinforced study group, where members hold each other accountable for doing the homework?

Albert M21:02:01

I like that name group reinforced study group

Hojat13:03:29

I'm in, I'm a JavaScript/react developer too And I'm totally lost in clojure/script world Let's just do it

Albert M15:03:43

@UQRGVS3MG awesome! Lets do it! I'll dm you.

penryu21:02:42

@seancorfield I also considered running the 4clojure app from git repo, after bumping the project.clj to 1.10. But I decided, if I solve it one way locally, using the 4clojure test cases as clojure.test cases, then have to solve it again when I find out update isn't available, it's an extra layer of special restrictions to learn from.

practicalli-john23:02:33

When getting a value from a Clojure hash-map, Is it possible to use a function as the not-found value and only have that function called if the key in the hash-map is not found?

(get {:a 1 :b 2} :b (throw (Throwable.))
The throw expression is evaluate and result returned even though there is a :b key in the hash-map. I assume this is similar to passing an expression as an argument to a function definition, it is evaluated before the rest of the function definition. I could of course wrap the above in a let and put a condition on the result that would trigger the throw expression. Any suggestions are appreciate.

Johannes Lötzsch23:02:30

(or (:b {:a 1 :b :2}) (throw (Throwable.))

practicalli-john23:02:48

🙂 thats so obvious now I see it. Thank you

andy.fingerhut23:02:17

Which works as long as the value you get out of the map cannot be nil nor false . If it can be, you'll need something else, perhaps using find or contains?

penryu00:03:25

I was just thinking about this, and using contains?. But it feels like a simpler/more idiomatic solution might be a non-falsy not-found value, as in:

(case ({:a 1 :b false :c nil} v :not-found)
  :not-found (throw (Throwable.))
  false ...
  nil ...
  (handle-inexplicit-but-present-value))
How can this be improved?

andy.fingerhut00:03:16

Depends upon what you consider an improvement, e.g. number of characters to type, performance, whether it is reasonable to define a new macro for it, etc.

andy.fingerhut00:03:52

For conditional execution of code, there definitely needs to be a macro involved, either a built in one like if case cond or , or one you define yourself in terms of one of those.

penryu00:03:19

Right. I guess if you just want to know if it's present in a map before dispatching the value off elsewhere, contains? has the most clarity.

penryu00:03:58

so a "simpler" (than my) solution might just be:

(if (contains? m v) (do-stuff (m v))
  (throw (Throwable.))

andy.fingerhut00:03:46

If you are trying to create something reusable that is optimized for performance here, I suspect using clojure.core/find is worth experimenting with, as a single call to that function returns a distinguishable value if the key is not present, like contains? does, but it also returns the key/value pair if the key is present.

andy.fingerhut00:03:13

Using contains? followed by another call to get the corresponding value if the key is present, it takes both calls to get the value when the key is present.

andy.fingerhut00:03:31

Using find you can check both of those things with a single lookup in the map

penryu00:03:50

Yeah, performance was why I initially though of case. I'll look at find now.

andy.fingerhut00:03:52

I have not measured performance of find vs. the contains? followed by regular map lookup to see which is faster myself, though.

practicalli-john09:03:33

In my case, if the map doesn't have a key, or the key has a value other than a number, then an error should be thrown. So or seems good. I want to have the number as the result, or throw an error.

penryu11:03:31

As @U0CMVHBL2 mentioned, or works as long as the values of the map are never nil or false.

penryu11:03:34

If the values might be falsy, you can safely combine or with find (also as Andy mentioned above), since map entries (vector tuples) are never falsy.

andy.fingerhut19:03:06

with the down side that unless you wrap it in a macro to help do some of this stuff, using or with find will not return only the value corresponding with the key, but the key/value pair.