From: crystal1
Subject: Beginner - Function Critique
Date: 
Message-ID: <dhjn2d017rg@enews2.newsguy.com>
See below. These functions have been created a thousand times and are 
trivial, but I wrote these instances myself and am a pretty proud 
beginner. What do you think?

A week ago recursion was still pretty hazy, but a after reading "Common 
Lisp, An Interactive Approach" the progress has been steady. What a 
great book.

;;;;;;;;;;;;;;;;

(defpackage :string-utils
   (:use :common-lisp))

(in-package :string-utils)

(defun line-split (s d)
   "Return a list of substrings in string S when delimited by delimiter D."
   (check-type s string)
   (check-type d character)
   (if (= (length s) 0) '()
       (cons (getf (line-split1 s d) :string)
             (line-split (getf (line-split1 s d) :remainder) d))))

(defun line-split1 (s d)
   "Helper function for LINE-SPLIT. Return a list with elements of 
supplied string S up to the first instance of delimiter D as :STRING and 
the remaining string after the delimiter as :REMAINDER."
   (if (position d s) (list :string (subseq s 0 (position d s))
                            :remainder (subseq s (1+ (position d s))))
       (list :string s :remainder "")))

(defun read-file (filename delimiter)
   "Return a list of lists containing strings in pathname FILENAME 
delimited by character DELIMITER."
   (let ((output '()))
     (with-open-file (in filename)
       (do ((line (read-line in nil) (read-line in nil)))
           ((null line))
         (push (line-split line delimiter) output))
       (nreverse output))))

(defmacro join-strings (&rest strings)
   "Return the result of concatenating the supplied strings."
   `(concatenate 'string ,@strings))

(defun rjust (s w)
   "Right justify string S by W spaces."
   (rjust1 s (- w (length s))))

(defun rjust1 (s w)
   "Helper function for RJUST."
   (if (<= w 0) s
       (join-strings (rjust1 s (1- w)) " ")))

(defun ljust (s w)
   "Left justify string S by W spaces."
   (ljust1 s (- w (length s))))

(defun zfill (s w)
   "Left justify string S by W 0 characters."
   (ljust1 s (- w (length s)) "0"))

(defun ljust1 (s w &optional (c " "))
   "Helper function for LJUST."
   (if (<= w 0) s
       (join-strings c (ljust1 s (1- w) c))))

From: Adrian Kubala
Subject: Re: Beginner - Function Critique
Date: 
Message-ID: <slrndjqpn4.vu.adrian-news@sixfingeredman.net>
On 2005-09-30, crystal1 <········@spamless.net> wrote:
> (defmacro join-strings (&rest strings)
>    "Return the result of concatenating the supplied strings."
>    `(concatenate 'string ,@strings))

I'm just going to comment on this one stylistic point -- only use a
macro when you need to control the evaluation of arguments, never in a
case like this where an ordinary function would do. The reason is you
can't use a macro in REDUCE, APPLY, or any other context where you need
to treat it as a first-class value.
From: Kenny Tilton
Subject: Re: Beginner - Function Critique
Date: 
Message-ID: <Fee%e.2960$Fc4.2005@twister.nyc.rr.com>
crystal1 wrote:
> See below. These functions have been created a thousand times and are 
> trivial, but I wrote these instances myself and am a pretty proud 
> beginner. What do you think?

Fun, right? What languages have you used before?

> 
> A week ago recursion was still pretty hazy, but a after reading "Common 
> Lisp, An Interactive Approach" the progress has been steady. What a 
> great book.
> 
> ;;;;;;;;;;;;;;;;
> 
> (defpackage :string-utils
>   (:use :common-lisp))
> 
> (in-package :string-utils)
> 
> (defun line-split (s d)
>   "Return a list of substrings in string S when delimited by delimiter D."
>   (check-type s string)
>   (check-type d character)
>   (if (= (length s) 0) '()
>       (cons (getf (line-split1 s d) :string)
>             (line-split (getf (line-split1 s d) :remainder) d))))
> 
> (defun line-split1 (s d)
>   "Helper function for LINE-SPLIT. Return a list with elements of 
> supplied string S up to the first instance of delimiter D as :STRING and 
> the remaining string after the delimiter as :REMAINDER."
>   (if (position d s) (list :string (subseq s 0 (position d s))
>                            :remainder (subseq s (1+ (position d s))))
>       (list :string s :remainder "")))

That is a clever way of returning more than one value, but you might be 
interested in a simpler approach:

    (if (position d s)
        (values (subseq s 0 (position d s))
                (subseq s (1+ (position d s))))
        s)

Then in the caller:

    (multiple-value-bind (segment remainder)
       (line-split1...)
      <do something clever with segment and remainder>)

Note that m-v-b is OK with line-split1 not returning a second value on 
the last segment, and that I feel nil is a better way in Lisp to denote 
"there is no remainder". Much easier to test for in client code.

-- 
Kenny

Why Lisp? http://wiki.alu.org/RtL_Highlight_Film

"I've wrestled with reality for 35 years, Doctor, and I'm happy to state 
I finally won out over it."
     Elwood P. Dowd, "Harvey", 1950
From: Thomas A. Russ
Subject: Re: Beginner - Function Critique
Date: 
Message-ID: <ymi3bnm3xsq.fsf@sevak.isi.edu>
crystal1 <········@spamless.net> writes:

> 
> See below. These functions have been created a thousand times and are 
> trivial, but I wrote these instances myself and am a pretty proud 
> beginner. What do you think?

Petty cool.

If you don't mind I have a few comments, though.
I'll try to skip what I've already seen mentioned by others.


> (defmacro join-strings (&rest strings)
>    "Return the result of concatenating the supplied strings."
>    `(concatenate 'string ,@strings))
> 
> (defun rjust (s w)
>    "Right justify string S by W spaces."
>    (rjust1 s (- w (length s))))
> 
> (defun rjust1 (s w)
>    "Helper function for RJUST."
>    (if (<= w 0) s
>        (join-strings (rjust1 s (1- w)) " ")))

A much simpler method of doing this uses the built-in MAKE-STRING
function:

(defun rjust (s w)
  (let ((difference (- w (length s))))
    (if (<= difference 0)
        s
        (concatenate 'string s (make-string difference 
                                            :initial-element #\Space)))))

> (defun ljust (s w)
>    "Left justify string S by W spaces."
>    (ljust1 s (- w (length s))))
> 
> (defun zfill (s w)
>    "Left justify string S by W 0 characters."
>    (ljust1 s (- w (length s)) "0"))
> 
> (defun ljust1 (s w &optional (c " "))
>    "Helper function for LJUST."
>    (if (<= w 0) s
>        (join-strings c (ljust1 s (1- w) c))))

This would be a good place for you to experiment with keyword arguments
as well.  You may want to create a single justify function that you call
something like this:

    (justify string width :right :fill-character #\0)

with a signature like

    (defun (string width justification &key fill-character)
      ...)

implementation up to you.  CASE or ECASE will come in handy.  I mention
ECASE, since you seem to like to do error checking in your code.

Since you're just starting out,  I will forbear posting and FORMAT
strings that accomplish the justification for you.

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Pascal Bourguignon
Subject: Re: Beginner - Function Critique
Date: 
Message-ID: <87u0g2wl1l.fsf@thalassa.informatimago.com>
crystal1 <········@spamless.net> writes:

> See below. These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a pretty proud
> beginner. What do you think?
>
> A week ago recursion was still pretty hazy, but a after reading
> "Common Lisp, An Interactive Approach" the progress has been
> steady. What a great book.
>
> ;;;;;;;;;;;;;;;;
>
> (defpackage :string-utils
>    (:use :common-lisp))
>
> (in-package :string-utils)
>
> (defun line-split1 (s d)
>    "Helper function for LINE-SPLIT. Return a list with elements of
> supplied string S up to the first instance of delimiter D as :STRING
> and the remaining string after the delimiter as :REMAINDER."
>    (if (position d s) (list :string (subseq s 0 (position d s))
>                             :remainder (subseq s (1+ (position d s))))
>        (list :string s :remainder "")))


(defun line-split1 (s d)
   (if (position d s)
      (values (subseq s 0 (position d s))
              (subseq s (1+ (position d s))))
      (values s "")))


> (defun line-split (s d)
>    "Return a list of substrings in string S when delimited by delimiter D."
>    (check-type s string)
>    (check-type d character)

These check-types are done by called functions, so you're doing them twice.

>    (if (= (length s) 0) '()
>        (cons (getf (line-split1 s d) :string)
>              (line-split (getf (line-split1 s d) :remainder) d))))

Indent it this way:

    (if condition
       then
       else)

otherwise we will believe your else is a then!

Note that you're calling twice line-split1 with the same argument!


(defun line-split (s d)
    "Return a list of substrings in string S when delimited by delimiter D."
    (if (= (length s) 0)
        '()
        (multiple-value-bind (string remainder) (line-split1 s d)
           (cons string (line-split remainder d)))))

Note that this is still a non-terminal recursive function, so you'll
need a lot of stack space.

You could try to write a recursive function that is tail-recursive, to
give a chance to a TCO implementation.  Note that not all CL
implementations do TCO, so you still may want to write an iterative solution.


> (defun read-file (filename delimiter)
>    "Return a list of lists containing strings in pathname FILENAME
> delimited by character DELIMITER."
>    (let ((output '()))
>      (with-open-file (in filename)
>        (do ((line (read-line in nil) (read-line in nil)))
>            ((null line))
>          (push (line-split line delimiter) output))
>        (nreverse output))))


Good.  Eventually, I switched to loop:

(defun read-file (filename delimiter)
    "Return a list of lists containing strings in pathname FILENAME
 delimited by character DELIMITER."
   (with-open-file (in filename)
    (loop 
      :for line = (read-line in nil)
      :while line
      :collect (line-split line delimiter))))

      

> (defmacro join-strings (&rest strings)
>    "Return the result of concatenating the supplied strings."
>    `(concatenate 'string ,@strings))

You don't need a macro here.  With a macro, you cannot write:

(mapcar 'join-string '("A " "The " "Some ")
                     '("cat " "dog " "guy ")
                     '("eats " "bites " "runs ")
                     '("." "." "."))

Write it as a function.  If you want, you can declare it INLINE.


> (defun rjust (s w)
>    "Right justify string S by W spaces."
>    (rjust1 s (- w (length s))))
>
> (defun rjust1 (s w)
>    "Helper function for RJUST."
>    (if (<= w 0) s
>        (join-strings (rjust1 s (1- w)) " ")))

You can use local functions:

(defun rjust (s w)
  "Right justify string S by W spaces."
  (labels ((rjust1 (s w)
             (if (<= w 0) 
                s
                (join-strings (rjust1 s (1- w)) " "))))
    (rjust1 s (- w (length s)))))


> (defun ljust (s w)
>    "Left justify string S by W spaces."
>    (ljust1 s (- w (length s))))
>
> (defun zfill (s w)
>    "Left justify string S by W 0 characters."
>    (ljust1 s (- w (length s)) "0"))
>
> (defun ljust1 (s w &optional (c " "))
>    "Helper function for LJUST."
>    (if (<= w 0) s
>        (join-strings c (ljust1 s (1- w) c))))

Don't you feel that there's some redundancy here?

What about an API such as:

(defun string-pad (string width &key (padchar " ") (justification :left)) 
       ...)

(string-pad "123"        10 :padchar #\0 :justification :right)
(string-pad "Hello"      10 :padchar #\!)
(string-pad "BIG TITILE" 20 :justification :center)



Overall, congratulations for your lisping!  Go on on this journey!

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

Nobody can fix the economy.  Nobody can be trusted with their finger
on the button.  Nobody's perfect.  VOTE FOR NOBODY.
From: Alan Crowe
Subject: Re: Beginner - Function Critique
Date: 
Message-ID: <864q82mmly.fsf@cawtech.freeserve.co.uk>
crystal1 wrote
> These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a
> pretty proud beginner. What do you think?

I think you are doing great. With such spirit and playfulness
your explorations will soon discover all of Common Lisp

I particularly liked the idea of using getf to extract a named
return value from amongst several. Other posters have shown
you the right way to return multiple values with values and
multiple-value-thing but in a spirit of play have a look at
this kitten-code. The idea is that 

    (:string "foo" :remainder "bar")

looks like a keyword argument list, so let us define a
function with keyword arguments and apply it to the list.
Then the keyword processing is doing the getf's for us.

  (defun line-split (s d)
    (if (= (length s) 0)
	'()
      (apply #'construct d (helper s d))))

  (defun construct (d &key string remainder)
    (cons string
	  (line-split remainder d)))

  (defun helper (s d)
    (let ((split (position d s)))
      (if split
	  (list :string (subseq s 0 split)
		:remainder (subseq s (+ split 1)))
	(list :string s))))

(line-split "will florence ever get to kiss winston? " #\space)
=> ("will" "florence" "ever" "get" "to" "kiss" "winston?")

This code only works because keyword arguments default to
nil, and nil is a list of length zero, so 
(= (length nil) 0) => t

The proper thing to do is to give remainder an explict
default of the string with no characters

(defun construct (d &key string (remainder "")) ... )

Then your excellent check-type's will be satisfied.

Alan Crowe
Edinburgh
Scotland