David Weiser
David Weiser

Reputation: 5205

Using a method defined within another method

I am working on the solution for this koan, which states:

Write a function which replicates each element of a sequence a variable number of times.

To do this, I want to:

  1. Make a method which takes a sequence and the number of times to repeat each element.
  2. Define a local method within this method which duplicates a value, v, n times into a sequence.

With that in mind, I wrote the following method:

(fn dupSeqX [aseq x]
  (fn dupx [v x]
    (if (= x 1) (list v) (concat v (dupx v (- x 1))))
  )
  (reverse (reduce #(concat %1 (dupx %2 x)) '() aseq)))

When running this code, I get the following error:

java.security.PrivilegedActionException: java.lang.Exception: Unable to resolve symbol: dupx in this context (NO_SOURCE_FILE:0)

How do I go about creating a local method that will allow me to finish this koan?

Is there a "clojure-esque" way of doing this that I'm not aware of?

Upvotes: 2

Views: 203

Answers (2)

nickik
nickik

Reputation: 5881

First of all, we don't talk about methods: we talk about functions. There is something in clojure that you could call a method but it's different from a function. If you stop using the OO lingo you will lose the OO style of thinking too.

What you are trying to do is possible. You basiclly want to create a new function with the name dupx in the dupseqx function. What you are doing right now is creating a function and then throwing it away (you don't do anything with the return value and only the last form in a function gets returned). Since a function is just like any other value, you can use the same mecanism like with any other value: create a local "variable". What's the mechanism for this? It's local binding and it works like this (The name in the fn is just so that you can call it from itself; it doesn't need to be the same as the let-bound name):

(let [dupx (fn dupx [v x] 
             (if (= x 1) 
                  (list v) 
                  (cons v (dupx v (dec x)))))]
  (dupx 5 3))

Notice that I corrected some other things.

A shorter form of this (fixing the double name ugliness):

(letfn [(dupx [v x] (if (= x 1)
                       (list v) 
                       (cons v (dupx v (dec x)))))]
  (dupx 5 3))

Ok in everything between "(let [...]" and the matching ")" we now have a dupx function.

So now the rest of your code works:

(fn dupSeqX [aseq x]
  (letfn [(dupx [v x] (if (= x 1) (list v) (cons v (dupx v (dec x)))))]
    (reverse (reduce #(concat %1 (dupx %2 x)) '() aseq))))

This code can be made a little more idiomatic:

  • Coding Guidelines: name parameter coll instead of aseq
  • Coding Guidelines: DoNotUseCamalCase do-it-like-this
  • Recursion when you don't need it is bad for performence and big numbers.
  • You are reinventing the wheel. This is good to learn coding but not good if you wnat to get to know the language and the standard lib.

How did I go about writing this?

First the basic fn. coll is the standard for naming function that expect sequences.

(fn [coll times]  )

If you read this "each element of a sequence" your brain has to go MAP.

(fn [coll times] 
   (map (fn ....) coll))

"replicates each ... " is basically a description of what you have to put into the map function. We can use repeat (your dubx function but with some extra goodies like that it's lazy).

(fn [coll times] 
   (map (fn [val] (repeat times val)) coll))

There is one problem left (from the koan). It wants one seq back, not a sequence in a sequence for each element. This means we have to concat the result together.

 (fn [coll times] 
   (apply concat (map (fn [val] (repeat times val)) coll)))

You will often see the (apply concat (map ....)) pattern. There is a better function for that in the standard library, called mapcat, and I'll make the inner function into the short syntax.

 (fn [coll times] 
   (mapcat #(repeat times %) coll))

Hope that helps!

Upvotes: 8

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91577

Clojure has a lot or really great functions to help you get in the habbit of "thinking in seqs". When you find your self writing something that iterates through a list think "can i map this?", and when you find your self doing anything else to a list check out this list of useful seq functions.

in this case that inner function happens to be build in so you get lucky :) often you will need to write it your self and store it in a let though if you make it a proper function then you may find use for it elsewhere in your code.

heres a hint to get started

(flatten (map #(repeat 3 %) [1 2 3 4]))
(1 1 1 2 2 2 3 3 3 4 4 4)

ps: #(repeat 3 %) is shorthand for (fn [n] (repeat 3 n))

Upvotes: 2

Related Questions