On Mar 3, 2009, at 4:53 AM, Chouser wrote:
>
>> (defn roman? [roman-string]
>>   (and (not (empty? roman-string))
>>        (re-matches
>>         #"(?:M{0,3})(?:D?C{0,3}|C[DM])(?:L?X{0,3}|X[LC])(?:V?I{0,3}|I
>> [VX])$"
>>         roman-string)))
>
> The normal idiom in Clojure is (seq x) instead of (not (empty? x)),
> and that works fine for Strings.  Also, since you don't really need
> the specific return value of the test expressions, 'when' might be a
> better choice than 'and'.
>

Thanks for your comments Chris and Michel.

I'll keep the "seq" idiom in mind.

On the other hand, there is a convention in Common Lisp that I think  
is worth observing in Clojure as well. It relates to the distinction  
between "if" and "when". My view is that "if" is an expression, and  
expressions return values. This is true whether the test is true or  
not, so I am not a fan of implicit "else" expressions. Namely, I  
believe that "if" should always contain 3 subexpressions. Moreover,  
the "then" and "else" subexpressions by default consist of single  
expressions. Of course they could be wrapped in "do" forms, but they  
aren't automatically.

Conversely, "when" says "side effect". This is clear for 2 reasons.  
First, there is no "else" clause, so "when" doesn't really work as an  
expression. Furthermore, the forms in the "when" body are evaluated  
in an implicit "do". Since only the final value is returned, any of  
the earlier expressions in the "when" body are useful only for their  
side effects.

For this reason I would argue against using "when" above. The  
"roman?" predicate simply states that a string that isn't empty AND  
that matches this pattern is a Roman numeral.

>> (defn roman-to-arabic-aux [roman-string]
>>   (cond (empty? roman-string) 0
>>         (empty? (rest roman-string)) (value (first roman-string))
>>         (< (value (first roman-string)) (value (second roman- 
>> string)))
>>         (- (roman-to-arabic-aux (rest roman-string))
>>            (value (first roman-string)))
>>         :else
>>         (+ (value (first roman-string))
>>            (roman-to-arabic-aux (rest roman-string)))))
>
> To reduce the clutter here, you might consider using destructuring on
> roman-string to get locals for the  first and rest parts:
>
> user=> (let [[a & b] "fie"] [a b])
> [\f (\i \e)]
>

You are probably right here. I am still thinking in terms of CL's  
DESTRUCTURING-BIND, which yields an error if the pattern doesn't match:
(destructuring-bind ((a b) &rest tail) '() (list a b tail))
This is a problem since I don't know whether or not roman-string is  
empty going into the "cond".

[D'oh! I just figured out how to do it the right way:
(destructuring-bind (&optional a b &rest tail) '( ) (list a b tail))  
=> (NIL NIL NIL)]

However, Clojure just assigns nil to extra variables, so this works:
(defn roman-to-arabic-aux [roman-string]
   (let [[a b & tail] roman-string]
     (cond (empty? roman-string) 0
           (nil? b) (value a)
         (< (value a) (value b))
         (- (roman-to-arabic-aux (cons b tail))
            (value a))
         :else
         (+ (value a)
            (roman-to-arabic-aux (cons b tail)))) ))

But this introduces its own awkwardness as I have to rebuild the tail  
of roman-string (cons b tail) for each recursive call.

Aha! I'm destructuring too much. I could just do this:
(defn roman-to-arabic-aux [roman-string]
   (let [[a b] roman-string]
     (cond (empty? roman-string) 0
           (nil? b) (value a)
         (< (value a) (value b))
         (- (roman-to-arabic-aux (rest roman-string))
            (value a))
         :else
         (+ (value a)
            (roman-to-arabic-aux (rest roman-string)))) ))

>> (def arabic-values '((1000 "M") (900 "CM") (500 "D") (400 "CD")
>>                      (100 "C") (90 "XC") (50 "L") (40 "XL")
>>                      (10 "X") (9 "IX") (5 "V") (4 "IV") (1 "I")))
>
> You might consider using a vector of vectors here.  There's nothing
> particularly wrong with what you've got, but I've been noticing
> recently that almost every list you expressed directly in idiomatic
> Clojure code is a function or macro call.  I think this is part of why
> I've found Clojure code to be less difficult to read than CL.
>
> It doesn't matter much in this case as the quote is clearly visible
> and the literal numbers aren't likely to be confused with function
> calls, but I thought I'd mention it anyway.
>

Good point. I'm still getting used to recursively CDR'ing across  
vectors. I don't have any problems with the readability of the nested  
list structure, but I understand that many Clojure programmers view a  
reduction in parentheses as helpful.

>> (defn arabic-to-roman
>>   ([n] (if (<= 1 n 3999)
>>          (apply str (arabic-to-roman n arabic-values))
>>          (format "%d cannot be converted." n)))
>>   ([n num-list] (cond (empty? num-list) '()
>>                       (zero? n) '()
>>                       :else (let [[[arabic roman] & tail] num-list]
>>                               (if (>= n arabic)
>>                                 (cons roman (arabic-to-roman (- n
>> arabic)
>>                                                              num- 
>> list))
>>                                 (arabic-to-roman n tail)))) ))
>
> You might consider using 'reduce' instead of recursion here.
> Alternatively, it's interesting to note that because of the ease with
> which you're destructuring arabic-values, it would be no more
> difficult if you had a single list (or vector) of alternating numbers
> and strings rather than nesting them.
>

I don't think "reduce" would actually work here. As you see, the test  
(>= n arabic) determines whether we continue working with the  
original num-list or proceed to the tail. I'm not sure of how to get  
that behavior with "reduce".

Aloha,
David Sletten

--~--~---------~--~----~------------~-------~--~----~
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