Repository: narwhals-dev/narwhals
Pull request #1536 (2024-12-07 22:27:53)

feat: `ListNamespace.len()` for `Expr` and `Series`
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [x] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Checklist

- [x] Code follows style guide (ruff)
- [x] Tests added
- [x] Documented the changes

## If you have comments or can explain your changes, please do so below

Introduces `ListNamespace` and only one method (`.len`) for Series and Expr.


FBruzzesi: @MarcoGorelli test is failing as polars seems to have changed type of `.list.len()` from Int64 to UInt32 (reasonably so). I am always uncertain for what to do in these situation. We could cast to such type for the user in each backend, but I am not sure if we should
MarcoGorelli: > We could cast to such type for the user in each backend, but I am not sure if we should

yeah i hear you, unsigned integers in pandas are particularly surprising too...we can probably keep it consistent for each backend though?

Patch:
diff --git a/docs/api-reference/expr_list.md b/docs/api-reference/expr_list.md
new file mode 100644
index 00000000..d685a6d0
--- /dev/null
+++ b/docs/api-reference/expr_list.md
@@ -0,0 +1,9 @@
+# `narwhals.Expr.list`
+
+::: narwhals.expr.ExprListNamespace
+    handler: python
+    options:
+      members:
+        - len
+      show_source: false
+      show_bases: false
diff --git a/docs/api-reference/series_list.md b/docs/api-reference/series_list.md
new file mode 100644
index 00000000..70ee445e
--- /dev/null
+++ b/docs/api-reference/series_list.md
@@ -0,0 +1,9 @@
+# `narwhals.Series.list`
+
+::: narwhals.series.SeriesListNamespace
+    handler: python
+    options:
+      members:
+        - len
+      show_source: false
+      show_bases: false
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 02930f39..cde4dc2a 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -513,14 +513,18 @@ class ArrowExpr:
     def name(self: Self) -> ArrowExprNameNamespace:
         return ArrowExprNameNamespace(self)
 
+    @property
+    def list(self: Self) -> ArrowExprListNamespace:
+        return ArrowExprListNamespace(self)
+
 
 class ArrowExprCatNamespace:
     def __init__(self: Self, expr: ArrowExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def get_categories(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "cat",
             "get_categories",
         )
@@ -528,89 +532,103 @@ class ArrowExprCatNamespace:
 
 class ArrowExprDateTimeNamespace:
     def __init__(self: Self, expr: ArrowExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def to_string(self: Self, format: str) -> ArrowExpr:  # noqa: A002
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "to_string", format
+            self._compliant_expr, "dt", "to_string", format
         )
 
     def replace_time_zone(self: Self, time_zone: str | None) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "replace_time_zone", time_zone
+            self._compliant_expr, "dt", "replace_time_zone", time_zone
         )
 
     def convert_time_zone(self: Self, time_zone: str) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "convert_time_zone", time_zone
+            self._compliant_expr, "dt", "convert_time_zone", time_zone
         )
 
     def timestamp(self: Self, time_unit: Literal["ns", "us", "ms"] = "us") -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "timestamp", time_unit
+            self._compliant_expr, "dt", "timestamp", time_unit
         )
 
     def date(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "date")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "date")
 
     def year(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "year")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "year")
 
     def month(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "month")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "month")
 
     def day(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "day")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "day")
 
     def hour(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "hour")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "hour")
 
     def minute(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "minute")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "minute")
 
     def second(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "second")
+        return reuse_series_namespace_implementation(self._compliant_expr, "dt", "second")
 
     def millisecond(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "millisecond")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "millisecond"
+        )
 
     def microsecond(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "microsecond")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "microsecond"
+        )
 
     def nanosecond(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "nanosecond")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "nanosecond"
+        )
 
     def ordinal_day(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "ordinal_day")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "ordinal_day"
+        )
 
     def total_minutes(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "total_minutes")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "total_minutes"
+        )
 
     def total_seconds(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "dt", "total_seconds")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "dt", "total_seconds"
+        )
 
     def total_milliseconds(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "total_milliseconds"
+            self._compliant_expr, "dt", "total_milliseconds"
         )
 
     def total_microseconds(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "total_microseconds"
+            self._compliant_expr, "dt", "total_microseconds"
         )
 
     def total_nanoseconds(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "dt", "total_nanoseconds"
+            self._compliant_expr, "dt", "total_nanoseconds"
         )
 
 
 class ArrowExprStringNamespace:
     def __init__(self: Self, expr: ArrowExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def len_chars(self: Self) -> ArrowExpr:
-        return reuse_series_namespace_implementation(self._expr, "str", "len_chars")
+        return reuse_series_namespace_implementation(
+            self._compliant_expr, "str", "len_chars"
+        )
 
     def replace(
         self: Self,
@@ -621,7 +639,7 @@ class ArrowExprStringNamespace:
         n: int,
     ) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "replace",
             pattern,
@@ -638,7 +656,7 @@ class ArrowExprStringNamespace:
         literal: bool,
     ) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "replace_all",
             pattern,
@@ -648,7 +666,7 @@ class ArrowExprStringNamespace:
 
     def strip_chars(self: Self, characters: str | None) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "strip_chars",
             characters,
@@ -656,7 +674,7 @@ class ArrowExprStringNamespace:
 
     def starts_with(self: Self, prefix: str) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "starts_with",
             prefix,
@@ -664,7 +682,7 @@ class ArrowExprStringNamespace:
 
     def ends_with(self: Self, suffix: str) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "ends_with",
             suffix,
@@ -672,17 +690,17 @@ class ArrowExprStringNamespace:
 
     def contains(self, pattern: str, *, literal: bool) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "str", "contains", pattern, literal=literal
+            self._compliant_expr, "str", "contains", pattern, literal=literal
         )
 
     def slice(self: Self, offset: int, length: int | None) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr, "str", "slice", offset, length
+            self._compliant_expr, "str", "slice", offset, length
         )
 
     def to_datetime(self: Self, format: str | None) -> ArrowExpr:  # noqa: A002
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "to_datetime",
             format,
@@ -690,14 +708,14 @@ class ArrowExprStringNamespace:
 
     def to_uppercase(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "to_uppercase",
         )
 
     def to_lowercase(self: Self) -> ArrowExpr:
         return reuse_series_namespace_implementation(
-            self._expr,
+            self._compliant_expr,
             "str",
             "to_lowercase",
         )
@@ -705,10 +723,10 @@ class ArrowExprStringNamespace:
 
 class ArrowExprNameNamespace:
     def __init__(self: Self, expr: ArrowExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def keep(self: Self) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -718,21 +736,21 @@ class ArrowExprNameNamespace:
             )
             raise ValueError(msg)
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), root_names)
+                for series, name in zip(self._compliant_expr._call(df), root_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=root_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def map(self: Self, function: Callable[[str], str]) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -744,21 +762,21 @@ class ArrowExprNameNamespace:
 
         output_names = [function(str(name)) for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def prefix(self: Self, prefix: str) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
         if root_names is None:
             msg = (
                 "Anonymous expressions are not supported in `.name.prefix`.\n"
@@ -768,21 +786,21 @@ class ArrowExprNameNamespace:
             raise ValueError(msg)
 
         output_names = [prefix + str(name) for name in root_names]
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def suffix(self: Self, suffix: str) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
         if root_names is None:
             msg = (
                 "Anonymous expressions are not supported in `.name.suffix`.\n"
@@ -793,21 +811,21 @@ class ArrowExprNameNamespace:
 
         output_names = [str(name) + suffix for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def to_lowercase(self: Self) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -818,21 +836,21 @@ class ArrowExprNameNamespace:
             raise ValueError(msg)
         output_names = [str(name).lower() for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def to_uppercase(self: Self) -> ArrowExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -843,15 +861,27 @@ class ArrowExprNameNamespace:
             raise ValueError(msg)
         output_names = [str(name).upper() for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.alias(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
+        )
+
+
+class ArrowExprListNamespace:
+    def __init__(self: Self, expr: ArrowExpr) -> None:
+        self._expr = expr
+
+    def len(self: Self) -> ArrowExpr:
+        return reuse_series_namespace_implementation(
+            self._expr,
+            "list",
+            "len",
         )
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 51c1b994..0593a141 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -1062,10 +1062,14 @@ class ArrowSeries:
     def str(self: Self) -> ArrowSeriesStringNamespace:
         return ArrowSeriesStringNamespace(self)
 
+    @property
+    def list(self: Self) -> ArrowSeriesListNamespace:
+        return ArrowSeriesListNamespace(self)
+
 
 class ArrowSeriesDateTimeNamespace:
     def __init__(self: Self, series: ArrowSeries) -> None:
-        self._arrow_series = series
+        self._compliant_series = series
 
     def to_string(self: Self, format: str) -> ArrowSeries:  # noqa: A002
         import pyarrow.compute as pc
@@ -1074,8 +1078,8 @@ class ArrowSeriesDateTimeNamespace:
         # the fractional part of the second...:'(
         # https://arrow.apache.org/docs/python/generated/pyarrow.compute.strftime.html
         format = format.replace("%S.%f", "%S").replace("%S%.f", "%S")
-        return self._arrow_series._from_native_series(
-            pc.strftime(self._arrow_series._native_series, format)
+        return self._compliant_series._from_native_series(
+            pc.strftime(self._compliant_series._native_series, format)
         )
 
     def replace_time_zone(self: Self, time_zone: str | None) -> ArrowSeries:
@@ -1083,33 +1087,33 @@ class ArrowSeriesDateTimeNamespace:
 
         if time_zone is not None:
             result = pc.assume_timezone(
-                pc.local_timestamp(self._arrow_series._native_series), time_zone
+                pc.local_timestamp(self._compliant_series._native_series), time_zone
             )
         else:
-            result = pc.local_timestamp(self._arrow_series._native_series)
-        return self._arrow_series._from_native_series(result)
+            result = pc.local_timestamp(self._compliant_series._native_series)
+        return self._compliant_series._from_native_series(result)
 
     def convert_time_zone(self: Self, time_zone: str) -> ArrowSeries:
         import pyarrow as pa
 
-        if self._arrow_series.dtype.time_zone is None:  # type: ignore[attr-defined]
+        if self._compliant_series.dtype.time_zone is None:  # type: ignore[attr-defined]
             result = self.replace_time_zone("UTC")._native_series.cast(
-                pa.timestamp(self._arrow_series._native_series.type.unit, time_zone)
+                pa.timestamp(self._compliant_series._native_series.type.unit, time_zone)
             )
         else:
-            result = self._arrow_series._native_series.cast(
-                pa.timestamp(self._arrow_series._native_series.type.unit, time_zone)
+            result = self._compliant_series._native_series.cast(
+                pa.timestamp(self._compliant_series._native_series.type.unit, time_zone)
             )
 
-        return self._arrow_series._from_native_series(result)
+        return self._compliant_series._from_native_series(result)
 
     def timestamp(self: Self, time_unit: Literal["ns", "us", "ms"] = "us") -> ArrowSeries:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        s = self._arrow_series._native_series
-        dtype = self._arrow_series.dtype
-        dtypes = import_dtypes_module(self._arrow_series._version)
+        s = self._compliant_series._native_series
+        dtype = self._compliant_series.dtype
+        dtypes = import_dtypes_module(self._compliant_series._version)
         if dtype == dtypes.Datetime:
             unit = dtype.time_unit  # type: ignore[attr-defined]
             s_cast = s.cast(pa.int64())
@@ -1155,93 +1159,93 @@ class ArrowSeriesDateTimeNamespace:
         else:
             msg = "Input should be either of Date or Datetime type"
             raise TypeError(msg)
-        return self._arrow_series._from_native_series(result)
+        return self._compliant_series._from_native_series(result)
 
     def date(self: Self) -> ArrowSeries:
         import pyarrow as pa
 
-        return self._arrow_series._from_native_series(
-            self._arrow_series._native_series.cast(pa.date32())
+        return self._compliant_series._from_native_series(
+            self._compliant_series._native_series.cast(pa.date32())
         )
 
     def year(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.year(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.year(self._compliant_series._native_series)
         )
 
     def month(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.month(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.month(self._compliant_series._native_series)
         )
 
     def day(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.day(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.day(self._compliant_series._native_series)
         )
 
     def hour(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.hour(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.hour(self._compliant_series._native_series)
         )
 
     def minute(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.minute(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.minute(self._compliant_series._native_series)
         )
 
     def second(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.second(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.second(self._compliant_series._native_series)
         )
 
     def millisecond(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.millisecond(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.millisecond(self._compliant_series._native_series)
         )
 
     def microsecond(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         result = pc.add(pc.multiply(pc.millisecond(arr), 1000), pc.microsecond(arr))
 
-        return self._arrow_series._from_native_series(result)
+        return self._compliant_series._from_native_series(result)
 
     def nanosecond(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         result = pc.add(
             pc.multiply(self.microsecond()._native_series, 1000), pc.nanosecond(arr)
         )
-        return self._arrow_series._from_native_series(result)
+        return self._compliant_series._from_native_series(result)
 
     def ordinal_day(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.day_of_year(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.day_of_year(self._compliant_series._native_series)
         )
 
     def total_minutes(self: Self) -> ArrowSeries:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         unit = arr.type.unit
 
         unit_to_minutes_factor = {
@@ -1252,7 +1256,7 @@ class ArrowSeriesDateTimeNamespace:
         }
 
         factor = pa.scalar(unit_to_minutes_factor[unit], type=pa.int64())
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.cast(pc.divide(arr, factor), pa.int64())
         )
 
@@ -1260,7 +1264,7 @@ class ArrowSeriesDateTimeNamespace:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         unit = arr.type.unit
 
         unit_to_seconds_factor = {
@@ -1271,7 +1275,7 @@ class ArrowSeriesDateTimeNamespace:
         }
         factor = pa.scalar(unit_to_seconds_factor[unit], type=pa.int64())
 
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.cast(pc.divide(arr, factor), pa.int64())
         )
 
@@ -1279,7 +1283,7 @@ class ArrowSeriesDateTimeNamespace:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         unit = arr.type.unit
 
         unit_to_milli_factor = {
@@ -1292,11 +1296,11 @@ class ArrowSeriesDateTimeNamespace:
         factor = pa.scalar(unit_to_milli_factor[unit], type=pa.int64())
 
         if unit == "s":
-            return self._arrow_series._from_native_series(
+            return self._compliant_series._from_native_series(
                 pc.cast(pc.multiply(arr, factor), pa.int64())
             )
 
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.cast(pc.divide(arr, factor), pa.int64())
         )
 
@@ -1304,7 +1308,7 @@ class ArrowSeriesDateTimeNamespace:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         unit = arr.type.unit
 
         unit_to_micro_factor = {
@@ -1317,10 +1321,10 @@ class ArrowSeriesDateTimeNamespace:
         factor = pa.scalar(unit_to_micro_factor[unit], type=pa.int64())
 
         if unit in {"s", "ms"}:
-            return self._arrow_series._from_native_series(
+            return self._compliant_series._from_native_series(
                 pc.cast(pc.multiply(arr, factor), pa.int64())
             )
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.cast(pc.divide(arr, factor), pa.int64())
         )
 
@@ -1328,7 +1332,7 @@ class ArrowSeriesDateTimeNamespace:
         import pyarrow as pa
         import pyarrow.compute as pc
 
-        arr = self._arrow_series._native_series
+        arr = self._compliant_series._native_series
         unit = arr.type.unit
 
         unit_to_nano_factor = {
@@ -1340,36 +1344,36 @@ class ArrowSeriesDateTimeNamespace:
 
         factor = pa.scalar(unit_to_nano_factor[unit], type=pa.int64())
 
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.cast(pc.multiply(arr, factor), pa.int64())
         )
 
 
 class ArrowSeriesCatNamespace:
     def __init__(self: Self, series: ArrowSeries) -> None:
-        self._arrow_series = series
+        self._compliant_series = series
 
     def get_categories(self: Self) -> ArrowSeries:
         import pyarrow as pa
 
-        ca = self._arrow_series._native_series
+        ca = self._compliant_series._native_series
         # TODO(Unassigned): this looks potentially expensive - is there no better way?
         # https://github.com/narwhals-dev/narwhals/issues/464
         out = pa.chunked_array(
             [pa.concat_arrays([x.dictionary for x in ca.chunks]).unique()]
         )
-        return self._arrow_series._from_native_series(out)
+        return self._compliant_series._from_native_series(out)
 
 
 class ArrowSeriesStringNamespace:
     def __init__(self: Self, series: ArrowSeries) -> None:
-        self._arrow_series = series
+        self._compliant_series = series
 
     def len_chars(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.utf8_length(self._arrow_series._native_series)
+        return self._compliant_series._from_native_series(
+            pc.utf8_length(self._compliant_series._native_series)
         )
 
     def replace(
@@ -1378,9 +1382,9 @@ class ArrowSeriesStringNamespace:
         import pyarrow.compute as pc
 
         method = "replace_substring" if literal else "replace_substring_regex"
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             getattr(pc, method)(
-                self._arrow_series._native_series,
+                self._compliant_series._native_series,
                 pattern=pattern,
                 replacement=value,
                 max_replacements=n,
@@ -1396,9 +1400,9 @@ class ArrowSeriesStringNamespace:
         import pyarrow.compute as pc
 
         whitespace = " \t\n\r\v\f"
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.utf8_trim(
-                self._arrow_series._native_series,
+                self._compliant_series._native_series,
                 characters or whitespace,
             )
         )
@@ -1406,14 +1410,14 @@ class ArrowSeriesStringNamespace:
     def starts_with(self: Self, prefix: str) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.equal(self.slice(0, len(prefix))._native_series, prefix)
         )
 
     def ends_with(self: Self, suffix: str) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.equal(self.slice(-len(suffix), None)._native_series, suffix)
         )
 
@@ -1421,17 +1425,17 @@ class ArrowSeriesStringNamespace:
         import pyarrow.compute as pc
 
         check_func = pc.match_substring if literal else pc.match_substring_regex
-        return self._arrow_series._from_native_series(
-            check_func(self._arrow_series._native_series, pattern)
+        return self._compliant_series._from_native_series(
+            check_func(self._compliant_series._native_series, pattern)
         )
 
     def slice(self: Self, offset: int, length: int | None) -> ArrowSeries:
         import pyarrow.compute as pc
 
         stop = offset + length if length is not None else None
-        return self._arrow_series._from_native_series(
+        return self._compliant_series._from_native_series(
             pc.utf8_slice_codeunits(
-                self._arrow_series._native_series, start=offset, stop=stop
+                self._compliant_series._native_series, start=offset, stop=stop
             ),
         )
 
@@ -1439,22 +1443,35 @@ class ArrowSeriesStringNamespace:
         import pyarrow.compute as pc
 
         if format is None:
-            format = parse_datetime_format(self._arrow_series._native_series)
+            format = parse_datetime_format(self._compliant_series._native_series)
 
-        return self._arrow_series._from_native_series(
-            pc.strptime(self._arrow_series._native_series, format=format, unit="us")
+        return self._compliant_series._from_native_series(
+            pc.strptime(self._compliant_series._native_series, format=format, unit="us")
         )
 
     def to_uppercase(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
-        return self._arrow_series._from_native_series(
-            pc.utf8_upper(self._arrow_series._native_series),
+        return self._compliant_series._from_native_series(
+            pc.utf8_upper(self._compliant_series._native_series),
         )
 
     def to_lowercase(self: Self) -> ArrowSeries:
         import pyarrow.compute as pc
 
+        return self._compliant_series._from_native_series(
+            pc.utf8_lower(self._compliant_series._native_series),
+        )
+
+
+class ArrowSeriesListNamespace:
+    def __init__(self: Self, series: ArrowSeries) -> None:
+        self._arrow_series = series
+
+    def len(self: Self) -> ArrowSeries:
+        import pyarrow as pa  # ignore-banned-import()
+        import pyarrow.compute as pc  # ignore-banned-import()
+
         return self._arrow_series._from_native_series(
-            pc.utf8_lower(self._arrow_series._native_series),
+            pc.cast(pc.list_value_length(self._arrow_series._native_series), pa.uint32())
         )
diff --git a/narwhals/_arrow/utils.py b/narwhals/_arrow/utils.py
index 80cdb1a8..2d237e59 100644
--- a/narwhals/_arrow/utils.py
+++ b/narwhals/_arrow/utils.py
@@ -118,9 +118,13 @@ def narwhals_to_native_dtype(dtype: DType | type[DType], version: Version) -> pa
         return pa.duration(time_unit)
     if isinstance_or_issubclass(dtype, dtypes.Date):
         return pa.date32()
-    if isinstance_or_issubclass(dtype, dtypes.List):  # pragma: no cover
-        msg = "Converting to List dtype is not supported yet"
-        return NotImplementedError(msg)
+    if isinstance_or_issubclass(dtype, dtypes.List):
+        return pa.list_(
+            value_type=narwhals_to_native_dtype(
+                dtype.inner,  # type: ignore[union-attr]
+                version=version,
+            )
+        )
     if isinstance_or_issubclass(dtype, dtypes.Struct):  # pragma: no cover
         msg = "Converting to Struct dtype is not supported yet"
         return NotImplementedError(msg)
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index 76b9cd43..8990f0a8 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -919,10 +919,10 @@ class DaskExpr:
 
 class DaskExprStringNamespace:
     def __init__(self, expr: DaskExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def len_chars(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.str.len(), "len", returns_scalar=False
         )
 
@@ -934,7 +934,7 @@ class DaskExprStringNamespace:
         literal: bool = False,
         n: int = 1,
     ) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, _pattern, _value, _literal, _n: _input.str.replace(
                 _pattern, _value, regex=not _literal, n=_n
             ),
@@ -953,7 +953,7 @@ class DaskExprStringNamespace:
         *,
         literal: bool = False,
     ) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, _pattern, _value, _literal: _input.str.replace(
                 _pattern, _value, n=-1, regex=not _literal
             ),
@@ -965,7 +965,7 @@ class DaskExprStringNamespace:
         )
 
     def strip_chars(self, characters: str | None = None) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, characters: _input.str.strip(characters),
             "strip",
             characters,
@@ -973,7 +973,7 @@ class DaskExprStringNamespace:
         )
 
     def starts_with(self, prefix: str) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, prefix: _input.str.startswith(prefix),
             "starts_with",
             prefix,
@@ -981,7 +981,7 @@ class DaskExprStringNamespace:
         )
 
     def ends_with(self, suffix: str) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, suffix: _input.str.endswith(suffix),
             "ends_with",
             suffix,
@@ -989,7 +989,7 @@ class DaskExprStringNamespace:
         )
 
     def contains(self, pattern: str, *, literal: bool = False) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, pat, regex: _input.str.contains(pat=pat, regex=regex),
             "contains",
             pattern,
@@ -999,7 +999,7 @@ class DaskExprStringNamespace:
 
     def slice(self, offset: int, length: int | None = None) -> DaskExpr:
         stop = offset + length if length else None
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, start, stop: _input.str.slice(start=start, stop=stop),
             "slice",
             offset,
@@ -1010,7 +1010,7 @@ class DaskExprStringNamespace:
     def to_datetime(self: Self, format: str | None) -> DaskExpr:  # noqa: A002
         import dask.dataframe as dd
 
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, fmt: dd.to_datetime(_input, format=fmt),
             "to_datetime",
             format,
@@ -1018,14 +1018,14 @@ class DaskExprStringNamespace:
         )
 
     def to_uppercase(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.str.upper(),
             "to_uppercase",
             returns_scalar=False,
         )
 
     def to_lowercase(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.str.lower(),
             "to_lowercase",
             returns_scalar=False,
@@ -1034,87 +1034,87 @@ class DaskExprStringNamespace:
 
 class DaskExprDateTimeNamespace:
     def __init__(self, expr: DaskExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def date(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.date,
             "date",
             returns_scalar=False,
         )
 
     def year(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.year,
             "year",
             returns_scalar=False,
         )
 
     def month(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.month,
             "month",
             returns_scalar=False,
         )
 
     def day(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.day,
             "day",
             returns_scalar=False,
         )
 
     def hour(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.hour,
             "hour",
             returns_scalar=False,
         )
 
     def minute(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.minute,
             "minute",
             returns_scalar=False,
         )
 
     def second(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.second,
             "second",
             returns_scalar=False,
         )
 
     def millisecond(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.microsecond // 1000,
             "millisecond",
             returns_scalar=False,
         )
 
     def microsecond(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.microsecond,
             "microsecond",
             returns_scalar=False,
         )
 
     def nanosecond(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.microsecond * 1000 + _input.dt.nanosecond,
             "nanosecond",
             returns_scalar=False,
         )
 
     def ordinal_day(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.dayofyear,
             "ordinal_day",
             returns_scalar=False,
         )
 
     def to_string(self, format: str) -> DaskExpr:  # noqa: A002
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, _format: _input.dt.strftime(_format),
             "strftime",
             format.replace("%.f", ".%f"),
@@ -1122,7 +1122,7 @@ class DaskExprDateTimeNamespace:
         )
 
     def replace_time_zone(self, time_zone: str | None) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input, _time_zone: _input.dt.tz_localize(None).dt.tz_localize(
                 _time_zone
             )
@@ -1135,13 +1135,15 @@ class DaskExprDateTimeNamespace:
 
     def convert_time_zone(self, time_zone: str) -> DaskExpr:
         def func(s: dask_expr.Series, time_zone: str) -> dask_expr.Series:
-            dtype = native_to_narwhals_dtype(s, self._expr._version, Implementation.DASK)
+            dtype = native_to_narwhals_dtype(
+                s, self._compliant_expr._version, Implementation.DASK
+            )
             if dtype.time_zone is None:  # type: ignore[attr-defined]
                 return s.dt.tz_localize("UTC").dt.tz_convert(time_zone)
             else:
                 return s.dt.tz_convert(time_zone)
 
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             func,
             "tz_convert",
             time_zone,
@@ -1152,10 +1154,12 @@ class DaskExprDateTimeNamespace:
         def func(
             s: dask_expr.Series, time_unit: Literal["ns", "us", "ms"] = "us"
         ) -> dask_expr.Series:
-            dtype = native_to_narwhals_dtype(s, self._expr._version, Implementation.DASK)
+            dtype = native_to_narwhals_dtype(
+                s, self._compliant_expr._version, Implementation.DASK
+            )
             is_pyarrow_dtype = "pyarrow" in str(dtype)
             mask_na = s.isna()
-            dtypes = import_dtypes_module(self._expr._version)
+            dtypes = import_dtypes_module(self._compliant_expr._version)
             if dtype == dtypes.Date:
                 # Date is only supported in pandas dtypes if pyarrow-backed
                 s_cast = s.astype("Int32[pyarrow]")
@@ -1173,7 +1177,7 @@ class DaskExprDateTimeNamespace:
                 raise TypeError(msg)
             return result.where(~mask_na)
 
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             func,
             "datetime",
             time_unit,
@@ -1181,35 +1185,35 @@ class DaskExprDateTimeNamespace:
         )
 
     def total_minutes(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.total_seconds() // 60,
             "total_minutes",
             returns_scalar=False,
         )
 
     def total_seconds(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.total_seconds() // 1,
             "total_seconds",
             returns_scalar=False,
         )
 
     def total_milliseconds(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.total_seconds() * 1000 // 1,
             "total_milliseconds",
             returns_scalar=False,
         )
 
     def total_microseconds(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.total_seconds() * 1_000_000 // 1,
             "total_microseconds",
             returns_scalar=False,
         )
 
     def total_nanoseconds(self) -> DaskExpr:
-        return self._expr._from_call(
+        return self._compliant_expr._from_call(
             lambda _input: _input.dt.total_seconds() * 1_000_000_000 // 1,
             "total_nanoseconds",
             returns_scalar=False,
@@ -1218,10 +1222,10 @@ class DaskExprDateTimeNamespace:
 
 class DaskExprNameNamespace:
     def __init__(self: Self, expr: DaskExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def keep(self: Self) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -1231,22 +1235,22 @@ class DaskExprNameNamespace:
             )
             raise ValueError(msg)
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), root_names)
+                for series, name in zip(self._compliant_expr._call(df), root_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=root_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def map(self: Self, function: Callable[[str], str]) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -1258,22 +1262,22 @@ class DaskExprNameNamespace:
 
         output_names = [function(str(name)) for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def prefix(self: Self, prefix: str) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
         if root_names is None:
             msg = (
                 "Anonymous expressions are not supported in `.name.prefix`.\n"
@@ -1283,22 +1287,22 @@ class DaskExprNameNamespace:
             raise ValueError(msg)
 
         output_names = [prefix + str(name) for name in root_names]
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def suffix(self: Self, suffix: str) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
         if root_names is None:
             msg = (
                 "Anonymous expressions are not supported in `.name.suffix`.\n"
@@ -1309,22 +1313,22 @@ class DaskExprNameNamespace:
 
         output_names = [str(name) + suffix for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def to_lowercase(self: Self) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -1335,22 +1339,22 @@ class DaskExprNameNamespace:
             raise ValueError(msg)
         output_names = [str(name).lower() for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
 
     def to_uppercase(self: Self) -> DaskExpr:
-        root_names = self._expr._root_names
+        root_names = self._compliant_expr._root_names
 
         if root_names is None:
             msg = (
@@ -1361,16 +1365,16 @@ class DaskExprNameNamespace:
             raise ValueError(msg)
         output_names = [str(name).upper() for name in root_names]
 
-        return self._expr.__class__(
+        return self._compliant_expr.__class__(
             lambda df: [
                 series.rename(name)
-                for series, name in zip(self._expr._call(df), output_names)
+                for series, name in zip(self._compliant_expr._call(df), output_names)
             ],
-            depth=self._expr._depth,
-            function_name=self._expr._function_name,
+            depth=self._compliant_expr._depth,
+            function_name=self._compliant_expr._function_name,
             root_names=root_names,
             output_names=output_names,
-            returns_scalar=self._expr._returns_scalar,
-            backend_version=self._expr._backend_version,
-            version=self._expr._version,
+            returns_scalar=self._compliant_expr._returns_scalar,
+            backend_version=self._compliant_expr._backend_version,
+            version=self._compliant_expr._version,
         )
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 2215c7ef..0ddad326 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
 
 class PandasLikeExpr:
     def __init__(
-        self,
+        self: Self,
         call: Callable[[PandasLikeDataFrame], list[PandasLikeSeries]],
         *,
         depth: int,
@@ -545,6 +545,10 @@ class PandasLikeExpr:
     def name(self: Self) -> PandasLikeExprNameNamespace:
         return PandasLikeExprNameNamespace(self)
 
+    @property
+    def list(self: Self) -> PandasLikeExprListNamespace:
+        return PandasLikeExprListNamespace(self)
+
 
 class PandasLikeExprCatNamespace:
     def __init__(self, expr: PandasLikeExpr) -> None:
@@ -902,3 +906,15 @@ class PandasLikeExprNameNamespace:
             backend_version=self._compliant_expr._backend_version,
             version=self._compliant_expr._version,
         )
+
+
+class PandasLikeExprListNamespace:
+    def __init__(self: Self, expr: PandasLikeExpr) -> None:
+        self._expr = expr
+
+    def len(self: Self) -> PandasLikeExpr:
+        return reuse_series_namespace_implementation(
+            self._expr,
+            "list",
+            "len",
+        )
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 34978ef4..20b5947e 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -864,6 +864,10 @@ class PandasLikeSeries:
     def cat(self) -> PandasLikeSeriesCatNamespace:
         return PandasLikeSeriesCatNamespace(self)
 
+    @property
+    def list(self) -> PandasLikeSeriesListNamespace:
+        return PandasLikeSeriesListNamespace(self)
+
 
 class PandasLikeSeriesCatNamespace:
     def __init__(self, series: PandasLikeSeries) -> None:
@@ -1156,3 +1160,22 @@ class PandasLikeSeriesDateTimeNamespace:
             raise TypeError(msg)
         result[mask_na] = None
         return self._compliant_series._from_native_series(result)
+
+
+class PandasLikeSeriesListNamespace:
+    def __init__(self, series: PandasLikeSeries) -> None:
+        self._compliant_series = series
+
+    def len(self: Self) -> PandasLikeSeries:
+        from narwhals.utils import import_dtypes_module
+
+        native_series = self._compliant_series._native_series
+        native_result = native_series.list.len().rename(native_series.name, copy=False)
+        dtype = narwhals_to_native_dtype(
+            dtype=import_dtypes_module(self._compliant_series._version).UInt32(),
+            starting_dtype=native_result.dtype,
+            implementation=self._compliant_series._implementation,
+            backend_version=self._compliant_series._backend_version,
+            version=self._compliant_series._version,
+        )
+        return self._compliant_series._from_native_series(native_result.astype(dtype))
diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py
index 7ea36083..ce8b8714 100644
--- a/narwhals/_pandas_like/utils.py
+++ b/narwhals/_pandas_like/utils.py
@@ -8,6 +8,9 @@ from typing import Literal
 from typing import Sequence
 from typing import TypeVar
 
+from narwhals._arrow.utils import (
+    narwhals_to_native_dtype as arrow_narwhals_to_native_dtype,
+)
 from narwhals._arrow.utils import (
     native_to_narwhals_dtype as arrow_native_to_narwhals_dtype,
 )
@@ -567,9 +570,29 @@ def narwhals_to_native_dtype(  # noqa: PLR0915
     if isinstance_or_issubclass(dtype, dtypes.Enum):
         msg = "Converting to Enum is not (yet) supported"
         raise NotImplementedError(msg)
-    if isinstance_or_issubclass(dtype, dtypes.List):  # pragma: no cover
-        msg = "Converting to List dtype is not supported yet"
-        return NotImplementedError(msg)
+    if isinstance_or_issubclass(dtype, dtypes.List):
+        if implementation is Implementation.PANDAS and backend_version >= (2, 2):
+            try:
+                import pandas as pd  # ignore-banned-import
+                import pyarrow as pa  # ignore-banned-import
+            except ImportError as exc:  # pragma: no cover
+                msg = f"Unable to convert to {dtype} to to the following exception: {exc.msg}"
+                raise ImportError(msg) from exc
+
+            return pd.ArrowDtype(
+                pa.list_(
+                    value_type=arrow_narwhals_to_native_dtype(
+                        dtype.inner,  # type: ignore[union-attr]
+                        version=version,
+                    )
+                )
+            )
+        else:
+            msg = (
+                "Converting to List dtype is not supported for implementation "
+                f"{implementation} and version {version}."
+            )
+            return NotImplementedError(msg)
     if isinstance_or_issubclass(dtype, dtypes.Struct):  # pragma: no cover
         msg = "Converting to Struct dtype is not supported yet"
         return NotImplementedError(msg)
diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py
index 559ca46b..e5a0a974 100644
--- a/narwhals/_polars/expr.py
+++ b/narwhals/_polars/expr.py
@@ -181,16 +181,20 @@ class PolarsExpr:
     def name(self: Self) -> PolarsExprNameNamespace:
         return PolarsExprNameNamespace(self)
 
+    @property
+    def list(self: Self) -> PolarsExprListNamespace:
+        return PolarsExprListNamespace(self)
+
 
 class PolarsExprDateTimeNamespace:
     def __init__(self: Self, expr: PolarsExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def __getattr__(self: Self, attr: str) -> Callable[[Any], PolarsExpr]:
         def func(*args: Any, **kwargs: Any) -> PolarsExpr:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
-            return self._expr._from_native_expr(
-                getattr(self._expr._native_expr.dt, attr)(*args, **kwargs)
+            return self._compliant_expr._from_native_expr(
+                getattr(self._compliant_expr._native_expr.dt, attr)(*args, **kwargs)
             )
 
         return func
@@ -198,13 +202,13 @@ class PolarsExprDateTimeNamespace:
 
 class PolarsExprStringNamespace:
     def __init__(self: Self, expr: PolarsExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def __getattr__(self: Self, attr: str) -> Callable[[Any], PolarsExpr]:
         def func(*args: Any, **kwargs: Any) -> PolarsExpr:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
-            return self._expr._from_native_expr(
-                getattr(self._expr._native_expr.str, attr)(*args, **kwargs)
+            return self._compliant_expr._from_native_expr(
+                getattr(self._compliant_expr._native_expr.str, attr)(*args, **kwargs)
             )
 
         return func
@@ -212,13 +216,13 @@ class PolarsExprStringNamespace:
 
 class PolarsExprCatNamespace:
     def __init__(self: Self, expr: PolarsExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def __getattr__(self: Self, attr: str) -> Callable[[Any], PolarsExpr]:
         def func(*args: Any, **kwargs: Any) -> PolarsExpr:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
-            return self._expr._from_native_expr(
-                getattr(self._expr._native_expr.cat, attr)(*args, **kwargs)
+            return self._compliant_expr._from_native_expr(
+                getattr(self._compliant_expr._native_expr.cat, attr)(*args, **kwargs)
             )
 
         return func
@@ -226,13 +230,47 @@ class PolarsExprCatNamespace:
 
 class PolarsExprNameNamespace:
     def __init__(self: Self, expr: PolarsExpr) -> None:
-        self._expr = expr
+        self._compliant_expr = expr
 
     def __getattr__(self: Self, attr: str) -> Callable[[Any], PolarsExpr]:
+        def func(*args: Any, **kwargs: Any) -> PolarsExpr:
+            args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
+            return self._compliant_expr._from_native_expr(
+                getattr(self._compliant_expr._native_expr.name, attr)(*args, **kwargs)
+            )
+
+        return func
+
+
+class PolarsExprListNamespace:
+    def __init__(self: Self, expr: PolarsExpr) -> None:
+        self._expr = expr
+
+    def len(self: Self) -> PolarsExpr:
+        native_expr = self._expr._native_expr
+        native_result = native_expr.list.len()
+
+        if self._expr._backend_version < (1, 16):  # pragma: no cover
+            import polars as pl
+
+            native_result: pl.Expr = (  # type: ignore[no-redef]
+                pl.when(~native_expr.is_null()).then(native_result).cast(pl.UInt32())
+            )
+        elif self._expr._backend_version < (1, 17):  # pragma: no cover
+            import polars as pl
+
+            native_result = native_result.cast(pl.UInt32())
+
+        return self._expr._from_native_expr(native_result)
+
+    # TODO(FBruzzesi): Remove `pragma: no cover` once other namespace methods are added
+    def __getattr__(
+        self: Self, attr: str
+    ) -> Callable[[Any], PolarsExpr]:  # pragma: no cover
         def func(*args: Any, **kwargs: Any) -> PolarsExpr:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
             return self._expr._from_native_expr(
-                getattr(self._expr._native_expr.name, attr)(*args, **kwargs)
+                getattr(self._expr._native_expr.list, attr)(*args, **kwargs)
             )
 
         return func
diff --git a/narwhals/_polars/series.py b/narwhals/_polars/series.py
index c960e844..635ef6d1 100644
--- a/narwhals/_polars/series.py
+++ b/narwhals/_polars/series.py
@@ -366,16 +366,20 @@ class PolarsSeries:
     def cat(self: Self) -> PolarsSeriesCatNamespace:
         return PolarsSeriesCatNamespace(self)
 
+    @property
+    def list(self: Self) -> PolarsSeriesListNamespace:
+        return PolarsSeriesListNamespace(self)
+
 
 class PolarsSeriesDateTimeNamespace:
     def __init__(self: Self, series: PolarsSeries) -> None:
-        self._series = series
+        self._compliant_series = series
 
     def __getattr__(self: Self, attr: str) -> Any:
         def func(*args: Any, **kwargs: Any) -> Any:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
-            return self._series._from_native_series(
-                getattr(self._series._native_series.dt, attr)(*args, **kwargs)
+            return self._compliant_series._from_native_series(
+                getattr(self._compliant_series._native_series.dt, attr)(*args, **kwargs)
             )
 
         return func
@@ -383,13 +387,13 @@ class PolarsSeriesDateTimeNamespace:
 
 class PolarsSeriesStringNamespace:
     def __init__(self: Self, series: PolarsSeries) -> None:
-        self._series = series
+        self._compliant_series = series
 
     def __getattr__(self: Self, attr: str) -> Any:
         def func(*args: Any, **kwargs: Any) -> Any:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
-            return self._series._from_native_series(
-                getattr(self._series._native_series.str, attr)(*args, **kwargs)
+            return self._compliant_series._from_native_series(
+                getattr(self._compliant_series._native_series.str, attr)(*args, **kwargs)
             )
 
         return func
@@ -397,13 +401,46 @@ class PolarsSeriesStringNamespace:
 
 class PolarsSeriesCatNamespace:
     def __init__(self: Self, series: PolarsSeries) -> None:
-        self._series = series
+        self._compliant_series = series
 
     def __getattr__(self: Self, attr: str) -> Any:
+        def func(*args: Any, **kwargs: Any) -> Any:
+            args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
+            return self._compliant_series._from_native_series(
+                getattr(self._compliant_series._native_series.cat, attr)(*args, **kwargs)
+            )
+
+        return func
+
+
+class PolarsSeriesListNamespace:
+    def __init__(self: Self, series: PolarsSeries) -> None:
+        self._series = series
+
+    def len(self: Self) -> PolarsSeries:
+        native_series = self._series._native_series
+        native_result = native_series.list.len()
+
+        if self._series._backend_version < (1, 16):  # pragma: no cover
+            import polars as pl
+
+            native_result = pl.select(
+                pl.when(~native_series.is_null()).then(native_result).otherwise(None)
+            )[native_series.name].cast(pl.UInt32())
+
+        elif self._series._backend_version < (1, 17):  # pragma: no cover
+            import polars as pl
+
+            native_result = native_series.cast(pl.UInt32())
+
+        return self._series._from_native_series(native_result)
+
+    # TODO(FBruzzesi): Remove `pragma: no cover` once other namespace methods are added
+    def __getattr__(self: Self, attr: str) -> Any:  # pragma: no cover
         def func(*args: Any, **kwargs: Any) -> Any:
             args, kwargs = extract_args_kwargs(args, kwargs)  # type: ignore[assignment]
             return self._series._from_native_series(
-                getattr(self._series._native_series.cat, attr)(*args, **kwargs)
+                getattr(self._series._native_series.list, attr)(*args, **kwargs)
             )
 
         return func
diff --git a/narwhals/_polars/utils.py b/narwhals/_polars/utils.py
index 97406b64..44f632c9 100644
--- a/narwhals/_polars/utils.py
+++ b/narwhals/_polars/utils.py
@@ -185,9 +185,8 @@ def narwhals_to_native_dtype(dtype: DType | type[DType], version: Version) -> pl
         du_time_unit: Literal["us", "ns", "ms"] = getattr(dtype, "time_unit", "us")
         return pl.Duration(time_unit=du_time_unit)
 
-    if dtype == dtypes.List:  # pragma: no cover
-        msg = "Converting to List dtype is not supported yet"
-        raise NotImplementedError(msg)
+    if dtype == dtypes.List:
+        return pl.List(narwhals_to_native_dtype(dtype.inner, version))  # type: ignore[union-attr]
     if dtype == dtypes.Struct:  # pragma: no cover
         msg = "Converting to Struct dtype is not supported yet"
         raise NotImplementedError(msg)
diff --git a/narwhals/expr.py b/narwhals/expr.py
index 04e2dcd9..fc080470 100644
--- a/narwhals/expr.py
+++ b/narwhals/expr.py
@@ -3447,6 +3447,10 @@ class Expr:
     def name(self: Self) -> ExprNameNamespace[Self]:
         return ExprNameNamespace(self)
 
+    @property
+    def list(self: Self) -> ExprListNamespace[Self]:
+        return ExprListNamespace(self)
+
 
 ExprT = TypeVar("ExprT", bound=Expr)
 
@@ -5603,6 +5607,70 @@ class ExprNameNamespace(Generic[ExprT]):
         )
 
 
+class ExprListNamespace(Generic[ExprT]):
+    def __init__(self: Self, expr: ExprT) -> None:
+        self._expr = expr
+
+    def len(self: Self) -> ExprT:
+        """Return the number of elements in each list.
+
+        Null values count towards the total.
+
+        Returns:
+            A new expression.
+
+        Examples:
+            >>> import narwhals as nw
+            >>> from narwhals.typing import IntoFrameT
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import pyarrow as pa
+            >>> data = {"a": [[1, 2], [3, 4, None], None, []]}
+
+            Let's define a dataframe-agnostic function:
+
+            >>> def agnostic_list_len(df_native: IntoFrameT) -> IntoFrameT:
+            ...     df = nw.from_native(df_native)
+            ...     return df.with_columns(a_len=nw.col("a").list.len()).to_native()
+
+            We can then pass pandas / PyArrow / Polars / any other supported library:
+
+            >>> agnostic_list_len(
+            ...     pd.DataFrame(data).astype({"a": pd.ArrowDtype(pa.list_(pa.int64()))})
+            ... )  # doctest: +SKIP
+                           a  a_len
+            0        [1. 2.]      2
+            1  [ 3.  4. nan]      3
+            2           <NA>   <NA>
+            3             []      0
+
+            >>> agnostic_list_len(pl.DataFrame(data))
+            shape: (4, 2)
+            ┌──────────────┬───────┐
+            │ a            ┆ a_len │
+            │ ---          ┆ ---   │
+            │ list[i64]    ┆ u32   │
+            ╞══════════════╪═══════╡
+            │ [1, 2]       ┆ 2     │
+            │ [3, 4, null] ┆ 3     │
+            │ null         ┆ null  │
+            │ []           ┆ 0     │
+            └──────────────┴───────┘
+
+            >>> agnostic_list_len(pa.table(data))
+            pyarrow.Table
+            a: list<item: int64>
+              child 0, item: int64
+            a_len: uint32
+            ----
+            a: [[[1,2],[3,4,null],null,[]]]
+            a_len: [[2,3,null,0]]
+        """
+        return self._expr.__class__(
+            lambda plx: self._expr._to_compliant_expr(plx).list.len()
+        )
+
+
 def col(*names: str | Iterable[str]) -> Expr:
     """Creates an expression that references one or more columns by their name(s).
 
diff --git a/narwhals/series.py b/narwhals/series.py
index 4ed7f048..57b7d8a0 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -3370,6 +3370,10 @@ class Series(Generic[IntoSeriesT]):
     def cat(self: Self) -> SeriesCatNamespace[Self]:
         return SeriesCatNamespace(self)
 
+    @property
+    def list(self: Self) -> SeriesListNamespace[Self]:
+        return SeriesListNamespace(self)
+
 
 SeriesT = TypeVar("SeriesT", bound=Series[Any])
 
@@ -4962,3 +4966,66 @@ class SeriesDateTimeNamespace(Generic[SeriesT]):
         return self._narwhals_series._from_compliant_series(
             self._narwhals_series._compliant_series.dt.timestamp(time_unit)
         )
+
+
+class SeriesListNamespace(Generic[SeriesT]):
+    def __init__(self: Self, series: SeriesT) -> None:
+        self._narwhals_series = series
+
+    def len(self: Self) -> SeriesT:
+        """Return the number of elements in each list.
+
+        Null values count towards the total.
+
+        Returns:
+            A new series.
+
+        Examples:
+            >>> import narwhals as nw
+            >>> from narwhals.typing import IntoSeriesT
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import pyarrow as pa
+            >>> data = [[1, 2], [3, 4, None], None, []]
+
+            Let's define a dataframe-agnostic function:
+
+            >>> def agnostic_list_len(s_native: IntoSeriesT) -> IntoSeriesT:
+            ...     s = nw.from_native(s_native, series_only=True)
+            ...     return s.list.len().to_native()
+
+            We can then pass pandas / PyArrow / Polars / any other supported library:
+
+            >>> agnostic_list_len(
+            ...     pd.Series(data, dtype=pd.ArrowDtype(pa.list_(pa.int64())))
+            ... )  # doctest: +SKIP
+            0       2
+            1       3
+            2    <NA>
+            3       0
+            dtype: int32[pyarrow]
+
+            >>> agnostic_list_len(pl.Series(data))  # doctest: +NORMALIZE_WHITESPACE
+            shape: (4,)
+            Series: '' [u32]
+            [
+               2
+               3
+               null
+               0
+            ]
+
+            >>> agnostic_list_len(pa.chunked_array([data]))  # doctest: +ELLIPSIS
+            <pyarrow.lib.ChunkedArray object at ...>
+            [
+              [
+                2,
+                3,
+                null,
+                0
+              ]
+            ]
+        """
+        return self._narwhals_series._from_compliant_series(
+            self._narwhals_series._compliant_series.list.len()
+        )
diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py
index 4d82edbc..6c71b73a 100644
--- a/utils/check_api_reference.py
+++ b/utils/check_api_reference.py
@@ -11,7 +11,7 @@ from narwhals.utils import remove_suffix
 
 ret = 0
 
-NAMESPACES = {"dt", "str", "cat", "name"}
+NAMESPACES = {"dt", "str", "cat", "name", "list"}
 EXPR_ONLY_METHODS = {"over", "map_batches"}
 SERIES_ONLY_METHODS = {
     "dtype",


Test patch:
diff --git a/tests/expr_and_series/list/__init__.py b/tests/expr_and_series/list/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expr_and_series/list/len_test.py b/tests/expr_and_series/list/len_test.py
new file mode 100644
index 00000000..06dc0d93
--- /dev/null
+++ b/tests/expr_and_series/list/len_test.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+import pytest
+
+import narwhals.stable.v1 as nw
+from tests.utils import PANDAS_VERSION
+from tests.utils import Constructor
+from tests.utils import ConstructorEager
+from tests.utils import assert_equal_data
+
+data = {"a": [[1, 2], [3, 4, None], None, [], [None]]}
+expected = {"a": [2, 3, None, 0, 1]}
+
+
+def test_len_expr(
+    request: pytest.FixtureRequest,
+    constructor: Constructor,
+) -> None:
+    if any(backend in str(constructor) for backend in ("dask", "modin", "cudf")):
+        request.applymarker(pytest.mark.xfail)
+
+    if "pandas" in str(constructor) and PANDAS_VERSION < (2, 2):
+        request.applymarker(pytest.mark.xfail)
+
+    result = nw.from_native(constructor(data)).select(
+        nw.col("a").cast(nw.List(nw.Int32())).list.len()
+    )
+
+    assert_equal_data(result, expected)
+
+
+def test_len_series(
+    request: pytest.FixtureRequest,
+    constructor_eager: ConstructorEager,
+) -> None:
+    if any(backend in str(constructor_eager) for backend in ("modin", "cudf")):
+        request.applymarker(pytest.mark.xfail)
+
+    if "pandas" in str(constructor_eager) and PANDAS_VERSION < (2, 2):
+        request.applymarker(pytest.mark.xfail)
+
+    df = nw.from_native(constructor_eager(data), eager_only=True)
+
+    result = df["a"].cast(nw.List(nw.Int32())).list.len()
+    assert_equal_data({"a": result}, expected)