Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

This is in reference to the question posted here. As I would judge it, the question there should be closed simply because it seems to ask why the Borland developers made one thing a function and another a procedure (function that returns a void result).

Now the example was given there of some string manipulation functions that exist in Borland Pascal or Delphi:

S2 := Copy(S1,3,2);

Copies into S2 from S1 the 2 characters starting from the 3rd position.

Delete(S,3,2);

Deletes from S the 2 characters starting from the 3rd position.

Now I thought it might be a good discussion on what would be good design decisions regarding devising these things. I picked up a few rules of good design on what a function prototype should look like beyond the obvious (name reflects function, function result should be atomic, no extraneous variables), but are there any others that should govern a situation such as this one or others? I really didn't see a good case in Atwood's code book where it was discussed.

Anyway, are there some good rules on how functions should be designed to work? Barring the specious naming, would it be better to have

S2 := Delete(S1,3,2);

as opposed to

Delete(S,3,2);

? Are there any other useful considerations or examples to discuss of good and bad function design?

share|improve this question
    
I see Pieter B put a bounty on this question (I didn't know you could bounty other people's questions). In accepting an answer, I'll look to his guidance (if he indicates here), since it was his question to begin with on SO. Otherwise, I'll use my judgment. –  Glenn1234 Sep 29 '13 at 0:36
    
One tip is to be consistent about how bounds are specified. Common options are inclusive bounds, half-open bounds and (as in that delete) first-and-size. Half-open bounds (inclusive at the start, exclusive at the end) are popular for reasons that Dijkstra explained fairly well. Most important, though, is consistency. Confusion about bounds is a common source of errors. –  Steve314 Oct 2 '13 at 22:20
    
I was looking for a why to use, rather then an explanation of the difference between the two methods. I'll reward the highest voted answer. –  Pieter B Oct 3 '13 at 9:19

3 Answers 3

up vote 11 down vote accepted
+100

It seems to me that both styles have a place because they don't do the same thing from a semantics standpoint:

S2 := Delete(S1,3,2);

Assignment implies that there is a subroutine that returns something that can in-fact be assigned to a variable.

Delete(S,3,2);

Implies an in-place mutation by the subroutine without any kind of return value, thus making it a procedure, as procedures lack return values.

Generally speaking, functions should be designed so that they can be completely curried and so that they are conceivably pure. Since the second style of "function" design relies entirely on a side effect to generate its effect, I'd argue that you should only ever use the first style.

share|improve this answer
    
To me, there's a name issue with the first case. A procedure/subroutine/action/whatever that mutates a value in place should be named using a verb that describe what it does. A function that returns the value you want without mutating its arguments should be named using a noun (or adjective) that describes the returned result. For arrays/strings, I've always had a soft spot for the old BASIC string function names left, right and mid for extracting substrings. For the remaining part after a deletion, I'm not sure there's a good short name - best I can think of is delete_result. –  Steve314 Oct 2 '13 at 22:13
    
Actually, thinking about it, I mostly apply that rule in imperative languages - not Haskell. –  Steve314 Oct 2 '13 at 22:31

As long as some basic language rules hold, either notation can be translated to the other.

S2 := Delete(S1,3,2);

can be rewritten as

Delete(S2,S1,3,2);

if S2 is declared to be pass-by-reference.

And

Delete(S,3,2);

can be rewritten as

S := Delete(S,3,2);

In most languages I can think of, this is the case. If it's not the case, the argument for notation is clear: use the one that works.

But assuming one can use either, you asked for useful considerations. To me, the two guiding ones should be clarity and efficiency.

On clarity, take the first example above. Personally, from a function, I expect one of two behaviors. Either it returns all of the work it does in S2 (with no pass-by-reference arguments at all), or it returns all of its work in pass-by-reference args and then returns only a code indicating success or failure. To do otherwise -- to manipulate some args as work product and then return some other work product -- makes the code impossible to understand. Now, if there is no question about the success or failure of the function, and if pass-by-reference is more appropriate for the work being done, then procedural syntax is fine. But in my experience, 90+% of the time, I either want a success/failure indication, or don't want to pass by reference, or both.

On efficiency, take the second example. Obviously if S or the operations on it are large and complex, pass-by-reference can be more efficient than working on a copy of data and returning only to assign to the same original variable. And as above, if pass-by-reference is appropriate, and if there is no question about the function/procedure succeeding or failing, then no return is necessary.

share|improve this answer

I'm not sure about other languages, but in Ruby the approach they take is to usually implement both types of method, so that the user can choose which one is best for the situation, but the type that modifies the data (rather than leaving the original data unchanged and returning the changed version) is always suffixed with a '!', so it is clear that the function performs a destructive operation.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.