84

What's the difference between "to" and "as" method name prefixes like

  • toList(),
  • asList(),
  • etc...

When to use which when designing a method?

Daniel Hári
  • 701
  • 5
  • 7

4 Answers4

123

A toXYZ() function is expected to do a conversion, and to return a new independent object (though immutability allows for optimization, java.lang.String.toString() just returns the object).
As an example, in C++ we have std::bitset::to_ulong() which can easily fail, and a whole plethora of to_string(), all doing a (more or less) complex conversion and allocating memory.

An asXYZ() function on the other hand is expected to return a (potentially different) view of the source, doing minimal work.
As an example, in C++ we have std::as_const() which just returns a constant reference, and the more involved std::forward_as_tuple which also refers to its arguments by reference.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
  • 19
    It turns out that there is some existing precedent for this. In general, As[something] will be akin to a cast, whereas To[something] builds a new object (of a different type) from an existing one. In the case of ToList, it's going to be O(n) and almost certainly more expensive than an "As." See https://stackoverflow.com/questions/17968469/whats-the-differences-between-tolist-asenumerable-asqueryable – Robert Harvey Jul 04 '17 at 18:27
  • 5
    It's part of selecting descriptive names, and even if this specific distinction isn't codified in the coding standard, violating it breaks the [Principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment). – Deduplicator Jul 04 '17 at 18:27
  • 11
    I would describe it as, instinctively, I would think of "to" as "change to" and "as" as "treat as". The former implies work to change the thing, the latter implies it's just a difference in how you work with it. – Latty Jul 05 '17 at 01:44
  • 6
    If it means something, it should mean this. I'd suggest that it often doesn't mean anything though, so I wouldn't rely on it in random third party code (without seeing something in the docs). It would be a good convention to adopt in a code base though. – Ben Jul 05 '17 at 04:41
  • @RobertHarvey: Please make that an answer? – Zaibis Jul 05 '17 at 05:23
  • Not saying this is wrong, but could you cite example languages or frameworks that follow this convention? @RobertHarvey found one for LINQ, but never heard of that. – user949300 Jul 05 '17 at 05:58
  • 4
    This answer is also accurate in C++: for example `std::to_string(x)` creates a new string object, but `std::as_const(x)` creates a reference (a view) of the existing object. – Quuxplusone Jul 05 '17 at 06:31
  • 1
    It seems appropriate that an answer about potential aliasing was written by Deduplicator – user541686 Jul 05 '17 at 10:22
  • This is accurate in C# as well. The base class library follows this convention. – usr Jul 05 '17 at 14:37
  • 1
    @user949300 In Java, Arrays.asList(...) takes an T[] and returns a List, and changes to the array are visible in the list, and changes to the List are visible in the array. It's essentially an adapter. – Joshua Taylor Jul 05 '17 at 15:50
14

Honestly, it may just be naming inconsistency. If look at standard library objects in Smalltalk, for instance, all methods that do "complex conversions" or "return simple representations in another type" are prefixed with as, as in asString, asFloat, asSeconds, and the standard method for converting anything to anything else, as: aClass.

In Ruby, the same kinds of methods are prefixed with to_, as in to_s, to_a, to_h, short for string, array and hash respectively.

Neither standard libraries seem to differentiate between different kinds of conversions, probably because it should be regarded as an implementation detail.

However, in Java we see a lot of mix-up. As you mentioned, there's toString, asList, and so on. I believe these are just a naming inconsistency, because if you try to define a different meaning to each prefix, you'll always find a counter-example somewhere else in the standard library.

So in any case, I'd say the important thing is for you and your team to pick one prefix and use it consistently throughout the code. Consistency is the key, so people are not left to wonder, like you had to.

MichelHenrich
  • 6,225
  • 1
  • 27
  • 29
  • 1
    I don't believe in: picking one style for a whole team, rather pick one style for similar cases in a module, consistently. – Daniel Hári Jul 05 '17 at 06:54
  • 13
    In Java, `toString` creates a new string fully disconnected from the object (and there's no other way because of immutability). The same holds e.g., for `Collection#toArray`, while `Arrays#asList` returns a view of the array, which is bidirectionally connected (mutating the array changes the list and vice versa). So it's fairly consistent, though there may be exceptions. *Picking one prefix would be wrong.* If there was `Arrays#toList`, then I'd expect it to create a new list with a new underlying array. – maaartinus Jul 05 '17 at 08:35
  • I think Smalltalk just made a mistake there, they didn't understand the convention. It's a difficult convention to understand (and even notice) because it is so abstract and it's not particularly impactful. Even the very act of asking this question requires some insight. – usr Jul 05 '17 at 14:35
  • 3
    @usr Smalltalk might have been designed before it become a convention – Bergi Jul 05 '17 at 15:45
7

While there's already an accepted answer, it seems to focus on C++, while the question is tagged with java. In Java, the first example that comes to mind for this kind of thing is Arrays.asList, which returns, essentially, an view of an array, wrapped up in a list. The underlying array and the list are still connected though; changes to the array are reflected in the list, and vice versa. However, the array returned by the list's toArray method is independent from the original array and from the list:

String[] wordArray = {"one", "fine", "day"};
List<String> wordList = Arrays.asList(wordArray);

// changes to the array are visible in the list
System.out.println(wordList); // prints "[one, fine, day]"
wordArray[1] = "horrible";
System.out.println(wordList); // prints "[one, horrible, day]"

// changes to the list are visible in the array
wordList.set(1, "beautiful");
System.out.println(wordArray[1]); // prints "beautiful"

// but changes to the list or array don't affect the 
// result from the list's toArray method.
String[] moreWords = wordList.toArray(new String[] {});
wordList.set(0, "the");
wordArray[1] = "best";
for (int i=0; i<3; i++) {
  System.out.println(moreWords[i]); // prints "one", "beautiful", and "day"
}

All that said, there's no guarantee that every library developer follows this convention, so you still need to check the documentation to find out whether this is the behavior you'll get from unknown code.

The other place that I've seen as...() methods used frequently is in downcasting types to sub-types. E.g., if you have an enumerated set of subtypes, then you might end up with code like:

  /**
   * Every Node is either an ANode or a BNode.
   */
  interface Node {

    /**
     * Returns this Node as an ANode.
     * 
     * @return this node
     */
    default ANode asANode() {
      if (this instanceof ANode) {
        return (ANode) this;
      }
      else {
        throw new UnsupportedOperationException();
      }
      // Or, in Java8 style, perhaps:
      // return Optional.of(this)
      //     .filter(ANode.class::isInstance)
      //     .map(ANode.class::cast)
      //     .orElseThrow(UnsupportedOperationException::new);
    }

    /**
     * Returns this Node as a BNode.
     * 
     * @return this node
     */
    default BNode asBNode() {
      if (this instanceof BNode) {
        return (BNode) this;
      }
      else {
        throw new UnsupportedOperationException();
      }
    }
  }
Joshua Taylor
  • 1,610
  • 11
  • 11
2

The difference I noticed (just now by thinking about it) is

  • To typically converts to a different reference type (a somewhat complex object)
  • As typically returns a simple value type

So we see AsInteger and AsString and we see ToArray and ToStringList.

To implies a conversion, which makes sense (it is a movement, a process). As implies a representation, a way of expressing the original object.

Another way of lookjng at this:

  • To is an operation, so you would use it for methods.
  • As is a simple representation, so you would use it for properties.

And then there is "prior art" (or legacy) to deal with. Before languages were fully OO from the ground up you would have library functions like StrToInt() and IntToStr(). They performed conversions, they were operations so it made sense to call them SomethingToSomethingelse(). After all, To is more active than As. I am particularly thinking about Delphi here.

When C# was designed with the ambition to go OO all the way, it made sense to have a method on the now integer object that would convert the integer to a string. Although we also have a Convert class, converting to string is so common that it was made a virtual method on object. The designers may have figured that ToString would be more familiar to people from the old paradigm and perhaps this is why we got a virtual method ToString() and not a virtual property AsString.

Martin Maat
  • 18,218
  • 3
  • 30
  • 57
  • 3
    What about `toString()`? – Deduplicator Jul 04 '17 at 19:57
  • You got me there. I think AsString would have been more appropriate. Ibhave some additionao thoughts, I will update my answer. – Martin Maat Jul 04 '17 at 20:11
  • I don't think this answer really matces the conventions in C#. Eg. ToList() and AsEnumerable() both return complex objects, the difference is ToList() always returns a new object while AsEnumerable() returns a view or adapter over the original object. – JacquesB Jul 06 '17 at 13:32
  • @JaquesB Apparently different ideas have been applied here and there. Delphi has AsInteger and AsString properties on TField objects (which encapsulate database fields). I may have been biased by this. But then again, the question is tagged with Java, not with either C# or Delphi. – Martin Maat Jul 06 '17 at 14:31