Repository: voxel51/fiftyone
Issue: Dataset head/tail should show Sample, not SampleView

Similar to https://github.com/voxel51/fiftyone/pull/348, I think `dataset.head()` and `dataset.tail()` should show `Sample`, not `SampleView`, as the user is operating on the dataset itself and the full samples are being shown

```py
import fiftyone as fo

dataset = fo.Dataset("test")

dataset.add_sample(fo.Sample(filepath="/path/to/filepath1.gif"))
dataset.add_sample(fo.Sample(filepath="/path/to/filepath2.jpg"))
dataset.add_sample(fo.Sample(filepath="/path/to/filepath3.png"))

print("\\nHEAD:\\n%s" % dataset.head())
print("\\nTAIL:\\n%s" % dataset.tail())
```

```
HEAD:
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd23f',
    'filepath': '/path/to/filepath1.gif',
    'tags': BaseList([]),
    'metadata': None,
}>
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd240',
    'filepath': '/path/to/filepath2.jpg',
    'tags': BaseList([]),
    'metadata': None,
}>
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd241',
    'filepath': '/path/to/filepath3.png',
    'tags': BaseList([]),
    'metadata': None,
}>

TAIL:
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd23f',
    'filepath': '/path/to/filepath1.gif',
    'tags': BaseList([]),
    'metadata': None,
}>
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd240',
    'filepath': '/path/to/filepath2.jpg',
    'tags': BaseList([]),
    'metadata': None,
}>
<SampleView: {
    'dataset_name': 'test',
    'id': '5f2b914a3e92f2d5225fd241',
    'filepath': '/path/to/filepath3.png',
    'tags': BaseList([]),
    'metadata': None,
}>
```


Changed files:
fiftyone/core/dataset.py, fiftyone/core/collections.py

diff --git a/fiftyone/core/collections.py b/fiftyone/core/collections.py
index 900876fba..a39896e18 100644
--- a/fiftyone/core/collections.py
+++ b/fiftyone/core/collections.py
@@ -87,36 +87,6 @@ class SampleCollection(object):
         """
         raise NotImplementedError("Subclass must implement summary()")
 
-    def head(self, num_samples=3):
-        """Returns a string representation of the first few samples in the
-        collection.
-
-        If fewer than ``num_samples`` samples are in the collection, only
-        the available samples are returned.
-
-        Args:
-            num_samples (3): the number of samples
-
-        Returns:
-            a string representation of the samples
-        """
-        return "\\n".join(str(s) for s in self[:num_samples])
-
-    def tail(self, num_samples=3):
-        """Returns a string representation of the last few samples in the
-        collection.
-
-        If fewer than ``num_samples`` samples are in the collection, only
-        the available samples are returned.
-
-        Args:
-            num_samples (3): the number of samples
-
-        Returns:
-            a string representation of the samples
-        """
-        return "\\n".join(str(s) for s in self[-num_samples:])
-
     def first(self):
         """Returns the first sample in the collection.
 
@@ -144,6 +114,34 @@ class SampleCollection(object):
         """
         return self[-1:].first()
 
+    def head(self, num_samples=3):
+        """Returns a list of the first few samples in the collection.
+
+        If fewer than ``num_samples`` samples are in the collection, only
+        the available samples are returned.
+
+        Args:
+            num_samples (3): the number of samples
+
+        Returns:
+            a list of :class:`fiftyone.core.sample.Sample` objects
+        """
+        return [s for s in self[:num_samples]]
+
+    def tail(self, num_samples=3):
+        """Returns a list of the last few samples in the collection.
+
+        If fewer than ``num_samples`` samples are in the collection, only
+        the available samples are returned.
+
+        Args:
+            num_samples (3): the number of samples
+
+        Returns:
+            a list of :class:`fiftyone.core.sample.Sample` objects
+        """
+        return [s for s in self[-num_samples:]]
+
     def iter_samples(self):
         """Returns an iterator over the samples in the collection.
 
diff --git a/fiftyone/core/dataset.py b/fiftyone/core/dataset.py
index e14771dde..63c46db99 100644
--- a/fiftyone/core/dataset.py
+++ b/fiftyone/core/dataset.py
@@ -269,6 +269,34 @@ class Dataset(foc.SampleCollection, metaclass=DatasetSingleton):
 
         return fos.Sample.from_doc(sample_view._doc)
 
+    def head(self, num_samples=3):
+        """Returns a list of the first few samples in the dataset.
+
+        If fewer than ``num_samples`` samples are in the dataset, only the
+        available samples are returned.
+
+        Args:
+            num_samples (3): the number of samples
+
+        Returns:
+            a list of :class:`fiftyone.core.sample.Sample` objects
+        """
+        return [fos.Sample.from_doc(sv._doc) for sv in self[:num_samples]]
+
+    def tail(self, num_samples=3):
+        """Returns a list of the last few samples in the dataset.
+
+        If fewer than ``num_samples`` samples are in the dataset, only the
+        available samples are returned.
+
+        Args:
+            num_samples (3): the number of samples
+
+        Returns:
+            a list of :class:`fiftyone.core.sample.Sample` objects
+        """
+        return [fos.Sample.from_doc(sv._doc) for sv in self[-num_samples:]]
+
     def view(self):
         """Returns a :class:`fiftyone.core.view.DatasetView` containing the
         entire dataset.