You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by we...@apache.org on 2018/10/31 21:32:29 UTC

[arrow] branch master updated: ARROW-3640: [Go] implement Tensors

This is an automated email from the ASF dual-hosted git repository.

wesm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new 2cdc4d4  ARROW-3640: [Go] implement Tensors
2cdc4d4 is described below

commit 2cdc4d4119ac611e33a80e849217779f77e8ebb0
Author: Sebastien Binet <bi...@cern.ch>
AuthorDate: Wed Oct 31 17:31:45 2018 -0400

    ARROW-3640: [Go] implement Tensors
    
    Author: Sebastien Binet <bi...@cern.ch>
    
    Closes #2860 from sbinet/issue-3640 and squashes the following commits:
    
    e88c21994 <Sebastien Binet> ARROW-3640:  implement Tensors
    7f7ad787e <Sebastien Binet>  make numeric types implement FixedWidthDataType
---
 go/arrow/array/data.go                    |   8 +-
 go/arrow/datatype_numeric.gen.go          |  50 +-
 go/arrow/datatype_numeric.gen.go.tmpl     |   3 +-
 go/arrow/datatype_numeric.gen.go.tmpldata |  42 +-
 go/arrow/doc.go                           |   2 +-
 go/arrow/example_test.go                  |  87 +++
 go/arrow/tensor/numeric.gen.go            | 277 +++++++++
 go/arrow/tensor/numeric.gen.go.tmpl       |  55 ++
 go/arrow/tensor/numeric.gen_test.go       | 980 ++++++++++++++++++++++++++++++
 go/arrow/tensor/numeric.gen_test.go.tmpl  | 126 ++++
 go/arrow/tensor/tensor.go                 | 239 ++++++++
 go/arrow/tensor/tensor_test.go            | 162 +++++
 12 files changed, 1995 insertions(+), 36 deletions(-)

diff --git a/go/arrow/array/data.go b/go/arrow/array/data.go
index f4dea17..58b1648 100644
--- a/go/arrow/array/data.go
+++ b/go/arrow/array/data.go
@@ -85,9 +85,11 @@ func (d *Data) Release() {
 	}
 }
 
-func (d *Data) DataType() arrow.DataType { return d.dtype }
-func (d *Data) NullN() int               { return d.nulls }
-func (d *Data) Len() int                 { return d.length }
+func (d *Data) DataType() arrow.DataType  { return d.dtype }
+func (d *Data) NullN() int                { return d.nulls }
+func (d *Data) Len() int                  { return d.length }
+func (d *Data) Offset() int               { return d.offset }
+func (d *Data) Buffers() []*memory.Buffer { return d.buffers }
 
 // NewSliceData returns a new slice that shares backing data with the input.
 // The returned Data slice starts at i and extends j-i elements, such as:
diff --git a/go/arrow/datatype_numeric.gen.go b/go/arrow/datatype_numeric.gen.go
index 30537be..2ec4c40 100644
--- a/go/arrow/datatype_numeric.gen.go
+++ b/go/arrow/datatype_numeric.gen.go
@@ -20,53 +20,63 @@ package arrow
 
 type Int8Type struct{}
 
-func (t *Int8Type) ID() Type     { return INT8 }
-func (t *Int8Type) Name() string { return "int8" }
+func (t *Int8Type) ID() Type      { return INT8 }
+func (t *Int8Type) Name() string  { return "int8" }
+func (t *Int8Type) BitWidth() int { return 8 }
 
 type Int16Type struct{}
 
-func (t *Int16Type) ID() Type     { return INT16 }
-func (t *Int16Type) Name() string { return "int16" }
+func (t *Int16Type) ID() Type      { return INT16 }
+func (t *Int16Type) Name() string  { return "int16" }
+func (t *Int16Type) BitWidth() int { return 16 }
 
 type Int32Type struct{}
 
-func (t *Int32Type) ID() Type     { return INT32 }
-func (t *Int32Type) Name() string { return "int32" }
+func (t *Int32Type) ID() Type      { return INT32 }
+func (t *Int32Type) Name() string  { return "int32" }
+func (t *Int32Type) BitWidth() int { return 32 }
 
 type Int64Type struct{}
 
-func (t *Int64Type) ID() Type     { return INT64 }
-func (t *Int64Type) Name() string { return "int64" }
+func (t *Int64Type) ID() Type      { return INT64 }
+func (t *Int64Type) Name() string  { return "int64" }
+func (t *Int64Type) BitWidth() int { return 64 }
 
 type Uint8Type struct{}
 
-func (t *Uint8Type) ID() Type     { return UINT8 }
-func (t *Uint8Type) Name() string { return "uint8" }
+func (t *Uint8Type) ID() Type      { return UINT8 }
+func (t *Uint8Type) Name() string  { return "uint8" }
+func (t *Uint8Type) BitWidth() int { return 8 }
 
 type Uint16Type struct{}
 
-func (t *Uint16Type) ID() Type     { return UINT16 }
-func (t *Uint16Type) Name() string { return "uint16" }
+func (t *Uint16Type) ID() Type      { return UINT16 }
+func (t *Uint16Type) Name() string  { return "uint16" }
+func (t *Uint16Type) BitWidth() int { return 16 }
 
 type Uint32Type struct{}
 
-func (t *Uint32Type) ID() Type     { return UINT32 }
-func (t *Uint32Type) Name() string { return "uint32" }
+func (t *Uint32Type) ID() Type      { return UINT32 }
+func (t *Uint32Type) Name() string  { return "uint32" }
+func (t *Uint32Type) BitWidth() int { return 32 }
 
 type Uint64Type struct{}
 
-func (t *Uint64Type) ID() Type     { return UINT64 }
-func (t *Uint64Type) Name() string { return "uint64" }
+func (t *Uint64Type) ID() Type      { return UINT64 }
+func (t *Uint64Type) Name() string  { return "uint64" }
+func (t *Uint64Type) BitWidth() int { return 64 }
 
 type Float32Type struct{}
 
-func (t *Float32Type) ID() Type     { return FLOAT32 }
-func (t *Float32Type) Name() string { return "float32" }
+func (t *Float32Type) ID() Type      { return FLOAT32 }
+func (t *Float32Type) Name() string  { return "float32" }
+func (t *Float32Type) BitWidth() int { return 32 }
 
 type Float64Type struct{}
 
-func (t *Float64Type) ID() Type     { return FLOAT64 }
-func (t *Float64Type) Name() string { return "float64" }
+func (t *Float64Type) ID() Type      { return FLOAT64 }
+func (t *Float64Type) Name() string  { return "float64" }
+func (t *Float64Type) BitWidth() int { return 64 }
 
 var (
 	PrimitiveTypes = struct {
diff --git a/go/arrow/datatype_numeric.gen.go.tmpl b/go/arrow/datatype_numeric.gen.go.tmpl
index 582c895..9458964 100644
--- a/go/arrow/datatype_numeric.gen.go.tmpl
+++ b/go/arrow/datatype_numeric.gen.go.tmpl
@@ -21,6 +21,7 @@ type {{.Name}}Type struct {}
 
 func (t *{{.Name}}Type) ID() Type { return {{.Name|upper}} }
 func (t *{{.Name}}Type) Name() string { return "{{.Name|lower}}" }
+func (t *{{.Name}}Type) BitWidth() int { return {{.Size}} }
 
 
 {{end}}
@@ -35,4 +36,4 @@ var (
                 {{.Name}}: &{{.Name}}Type{},
 {{- end}}
         }
-)
\ No newline at end of file
+)
diff --git a/go/arrow/datatype_numeric.gen.go.tmpldata b/go/arrow/datatype_numeric.gen.go.tmpldata
index 1f40fd5..415b51b 100644
--- a/go/arrow/datatype_numeric.gen.go.tmpldata
+++ b/go/arrow/datatype_numeric.gen.go.tmpldata
@@ -1,32 +1,52 @@
 [
   {
-    "Name": "Int8"
+    "Name": "Int8",
+    "Type": "int8",
+    "Size": 8
   },
   {
-    "Name": "Int16"
+    "Name": "Int16",
+    "Type": "int16",
+    "Size": 16
   },
   {
-    "Name": "Int32"
+    "Name": "Int32",
+    "Type": "int32",
+    "Size": 32
   },
   {
-    "Name": "Int64"
+    "Name": "Int64",
+    "Type": "int64",
+    "Size": 64
   },
   {
-    "Name": "Uint8"
+    "Name": "Uint8",
+    "Type": "uint8",
+    "Size": 8
   },
   {
-    "Name": "Uint16"
+    "Name": "Uint16",
+    "Type": "uint16",
+    "Size": 16
   },
   {
-    "Name": "Uint32"
+    "Name": "Uint32",
+    "Type": "uint32",
+    "Size": 32
   },
   {
-    "Name": "Uint64"
+    "Name": "Uint64",
+    "Type": "uint64",
+    "Size": 64
   },
   {
-    "Name": "Float32"
+    "Name": "Float32",
+    "Type": "float32",
+    "Size": 32
   },
   {
-    "Name": "Float64"
+    "Name": "Float64",
+    "Type": "float64",
+    "Size": 64
   }
-]
\ No newline at end of file
+]
diff --git a/go/arrow/doc.go b/go/arrow/doc.go
index 6ee0c2a..ebd9c8e 100644
--- a/go/arrow/doc.go
+++ b/go/arrow/doc.go
@@ -32,7 +32,7 @@ array is valid (not null). If the array has no null entries, it is possible to o
 package arrow
 
 //go:generate go run _tools/tmpl/main.go -i -data=numeric.tmpldata type_traits_numeric.gen.go.tmpl array/numeric.gen.go.tmpl array/numericbuilder.gen.go.tmpl array/bufferbuilder_numeric.gen.go.tmpl
-//go:generate go run _tools/tmpl/main.go -i -data=datatype_numeric.gen.go.tmpldata datatype_numeric.gen.go.tmpl
+//go:generate go run _tools/tmpl/main.go -i -data=datatype_numeric.gen.go.tmpldata datatype_numeric.gen.go.tmpl tensor/numeric.gen.go.tmpl tensor/numeric.gen_test.go.tmpl
 
 // stringer
 //go:generate stringer -type=Type
diff --git a/go/arrow/example_test.go b/go/arrow/example_test.go
index ff77c6b..936d8d8 100644
--- a/go/arrow/example_test.go
+++ b/go/arrow/example_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/apache/arrow/go/arrow"
 	"github.com/apache/arrow/go/arrow/array"
 	"github.com/apache/arrow/go/arrow/memory"
+	"github.com/apache/arrow/go/arrow/tensor"
 )
 
 // This example demonstrates how to build an array of int64 values using a builder and Append.
@@ -320,3 +321,89 @@ func Example_float64Slice() {
 	// array = [1 2 3 -1 4 5]
 	// slice = [3 -1 4]
 }
+
+func Example_float64Tensor2x5() {
+	pool := memory.NewGoAllocator()
+
+	b := array.NewFloat64Builder(pool)
+	defer b.Release()
+
+	raw := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	b.AppendValues(raw, nil)
+
+	arr := b.NewFloat64Array()
+	defer arr.Release()
+
+	f64 := tensor.NewFloat64(arr.Data(), []int64{2, 5}, nil, []string{"x", "y"})
+	defer f64.Release()
+
+	for _, i := range [][]int64{
+		[]int64{0, 0},
+		[]int64{0, 1},
+		[]int64{0, 2},
+		[]int64{0, 3},
+		[]int64{0, 4},
+		[]int64{1, 0},
+		[]int64{1, 1},
+		[]int64{1, 2},
+		[]int64{1, 3},
+		[]int64{1, 4},
+	} {
+		fmt.Printf("arr%v = %v\n", i, f64.Value(i))
+	}
+
+	// Output:
+	// arr[0 0] = 1
+	// arr[0 1] = 2
+	// arr[0 2] = 3
+	// arr[0 3] = 4
+	// arr[0 4] = 5
+	// arr[1 0] = 6
+	// arr[1 1] = 7
+	// arr[1 2] = 8
+	// arr[1 3] = 9
+	// arr[1 4] = 10
+}
+
+func Example_float64Tensor2x5ColMajor() {
+	pool := memory.NewGoAllocator()
+
+	b := array.NewFloat64Builder(pool)
+	defer b.Release()
+
+	raw := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	b.AppendValues(raw, nil)
+
+	arr := b.NewFloat64Array()
+	defer arr.Release()
+
+	f64 := tensor.NewFloat64(arr.Data(), []int64{2, 5}, []int64{8, 16}, []string{"x", "y"})
+	defer f64.Release()
+
+	for _, i := range [][]int64{
+		[]int64{0, 0},
+		[]int64{0, 1},
+		[]int64{0, 2},
+		[]int64{0, 3},
+		[]int64{0, 4},
+		[]int64{1, 0},
+		[]int64{1, 1},
+		[]int64{1, 2},
+		[]int64{1, 3},
+		[]int64{1, 4},
+	} {
+		fmt.Printf("arr%v = %v\n", i, f64.Value(i))
+	}
+
+	// Output:
+	// arr[0 0] = 1
+	// arr[0 1] = 3
+	// arr[0 2] = 5
+	// arr[0 3] = 7
+	// arr[0 4] = 9
+	// arr[1 0] = 2
+	// arr[1 1] = 4
+	// arr[1 2] = 6
+	// arr[1 3] = 8
+	// arr[1 4] = 10
+}
diff --git a/go/arrow/tensor/numeric.gen.go b/go/arrow/tensor/numeric.gen.go
new file mode 100644
index 0000000..1e9c1e9
--- /dev/null
+++ b/go/arrow/tensor/numeric.gen.go
@@ -0,0 +1,277 @@
+// Code generated by tensor/numeric.gen.go.tmpl. DO NOT EDIT.
+
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tensor
+
+import (
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+)
+
+// Int8 is an n-dim array of int8s.
+type Int8 struct {
+	tensorBase
+	values []int8
+}
+
+// NewInt8 returns a new n-dimensional array of int8s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewInt8(data *array.Data, shape, strides []int64, names []string) *Int8 {
+	tsr := &Int8{tensorBase: *newTensor(arrow.PrimitiveTypes.Int8, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Int8Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Int8) Value(i []int64) int8 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Int8) Int8Values() []int8   { return tsr.values }
+
+// Int16 is an n-dim array of int16s.
+type Int16 struct {
+	tensorBase
+	values []int16
+}
+
+// NewInt16 returns a new n-dimensional array of int16s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewInt16(data *array.Data, shape, strides []int64, names []string) *Int16 {
+	tsr := &Int16{tensorBase: *newTensor(arrow.PrimitiveTypes.Int16, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Int16Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Int16) Value(i []int64) int16 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Int16) Int16Values() []int16  { return tsr.values }
+
+// Int32 is an n-dim array of int32s.
+type Int32 struct {
+	tensorBase
+	values []int32
+}
+
+// NewInt32 returns a new n-dimensional array of int32s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewInt32(data *array.Data, shape, strides []int64, names []string) *Int32 {
+	tsr := &Int32{tensorBase: *newTensor(arrow.PrimitiveTypes.Int32, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Int32Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Int32) Value(i []int64) int32 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Int32) Int32Values() []int32  { return tsr.values }
+
+// Int64 is an n-dim array of int64s.
+type Int64 struct {
+	tensorBase
+	values []int64
+}
+
+// NewInt64 returns a new n-dimensional array of int64s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewInt64(data *array.Data, shape, strides []int64, names []string) *Int64 {
+	tsr := &Int64{tensorBase: *newTensor(arrow.PrimitiveTypes.Int64, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Int64Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Int64) Value(i []int64) int64 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Int64) Int64Values() []int64  { return tsr.values }
+
+// Uint8 is an n-dim array of uint8s.
+type Uint8 struct {
+	tensorBase
+	values []uint8
+}
+
+// NewUint8 returns a new n-dimensional array of uint8s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewUint8(data *array.Data, shape, strides []int64, names []string) *Uint8 {
+	tsr := &Uint8{tensorBase: *newTensor(arrow.PrimitiveTypes.Uint8, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Uint8Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Uint8) Value(i []int64) uint8 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Uint8) Uint8Values() []uint8  { return tsr.values }
+
+// Uint16 is an n-dim array of uint16s.
+type Uint16 struct {
+	tensorBase
+	values []uint16
+}
+
+// NewUint16 returns a new n-dimensional array of uint16s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewUint16(data *array.Data, shape, strides []int64, names []string) *Uint16 {
+	tsr := &Uint16{tensorBase: *newTensor(arrow.PrimitiveTypes.Uint16, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Uint16Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Uint16) Value(i []int64) uint16 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Uint16) Uint16Values() []uint16 { return tsr.values }
+
+// Uint32 is an n-dim array of uint32s.
+type Uint32 struct {
+	tensorBase
+	values []uint32
+}
+
+// NewUint32 returns a new n-dimensional array of uint32s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewUint32(data *array.Data, shape, strides []int64, names []string) *Uint32 {
+	tsr := &Uint32{tensorBase: *newTensor(arrow.PrimitiveTypes.Uint32, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Uint32Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Uint32) Value(i []int64) uint32 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Uint32) Uint32Values() []uint32 { return tsr.values }
+
+// Uint64 is an n-dim array of uint64s.
+type Uint64 struct {
+	tensorBase
+	values []uint64
+}
+
+// NewUint64 returns a new n-dimensional array of uint64s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewUint64(data *array.Data, shape, strides []int64, names []string) *Uint64 {
+	tsr := &Uint64{tensorBase: *newTensor(arrow.PrimitiveTypes.Uint64, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Uint64Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Uint64) Value(i []int64) uint64 { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Uint64) Uint64Values() []uint64 { return tsr.values }
+
+// Float32 is an n-dim array of float32s.
+type Float32 struct {
+	tensorBase
+	values []float32
+}
+
+// NewFloat32 returns a new n-dimensional array of float32s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewFloat32(data *array.Data, shape, strides []int64, names []string) *Float32 {
+	tsr := &Float32{tensorBase: *newTensor(arrow.PrimitiveTypes.Float32, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Float32Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Float32) Value(i []int64) float32  { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Float32) Float32Values() []float32 { return tsr.values }
+
+// Float64 is an n-dim array of float64s.
+type Float64 struct {
+	tensorBase
+	values []float64
+}
+
+// NewFloat64 returns a new n-dimensional array of float64s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func NewFloat64(data *array.Data, shape, strides []int64, names []string) *Float64 {
+	tsr := &Float64{tensorBase: *newTensor(arrow.PrimitiveTypes.Float64, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.Float64Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *Float64) Value(i []int64) float64  { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *Float64) Float64Values() []float64 { return tsr.values }
+
+var (
+	_ Interface = (*Int8)(nil)
+	_ Interface = (*Int16)(nil)
+	_ Interface = (*Int32)(nil)
+	_ Interface = (*Int64)(nil)
+	_ Interface = (*Uint8)(nil)
+	_ Interface = (*Uint16)(nil)
+	_ Interface = (*Uint32)(nil)
+	_ Interface = (*Uint64)(nil)
+	_ Interface = (*Float32)(nil)
+	_ Interface = (*Float64)(nil)
+)
diff --git a/go/arrow/tensor/numeric.gen.go.tmpl b/go/arrow/tensor/numeric.gen.go.tmpl
new file mode 100644
index 0000000..282cbf2
--- /dev/null
+++ b/go/arrow/tensor/numeric.gen.go.tmpl
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tensor
+
+import (
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+)
+
+{{range .In}}
+
+// {{.Name}} is an n-dim array of {{.Type}}s.
+type {{.Name}} struct {
+	tensorBase
+	values []{{.Type}}
+}
+
+// New{{.Name}} returns a new n-dimensional array of {{.Type}}s.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+func New{{.Name}}(data *array.Data, shape, strides []int64, names []string) *{{.Name}} {
+	tsr := &{{.Name}}{tensorBase:*newTensor(arrow.PrimitiveTypes.{{.Name}}, data, shape, strides, names)}
+	vals := tsr.data.Buffers()[1]
+	if vals != nil {
+		tsr.values = arrow.{{.Name}}Traits.CastFromBytes(vals.Bytes())
+		beg := tsr.data.Offset()
+		end := beg + tsr.data.Len()
+		tsr.values = tsr.values[beg:end]
+	}
+	return tsr
+}
+
+func (tsr *{{.Name}}) Value(i []int64)  {{or .Type}} { j := int(tsr.offset(i)); return tsr.values[j] }
+func (tsr *{{.Name}}) {{.Name}}Values() []{{or .Type}} { return tsr.values }
+{{end}}
+
+var (
+{{range .In}}
+	_ Interface = (*{{.Name}})(nil)
+{{- end}}
+)
diff --git a/go/arrow/tensor/numeric.gen_test.go b/go/arrow/tensor/numeric.gen_test.go
new file mode 100644
index 0000000..066f7ca
--- /dev/null
+++ b/go/arrow/tensor/numeric.gen_test.go
@@ -0,0 +1,980 @@
+// Code generated by tensor/numeric.gen_test.go.tmpl. DO NOT EDIT.
+
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tensor_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+	"github.com/apache/arrow/go/arrow/memory"
+	"github.com/apache/arrow/go/arrow/tensor"
+)
+
+func TestTensorInt8(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewInt8Builder(mem)
+	defer bld.Release()
+
+	raw := []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewInt8Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Int8.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Int8)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Int8Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v int8
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorInt16(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewInt16Builder(mem)
+	defer bld.Release()
+
+	raw := []int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewInt16Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Int16.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Int16)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Int16Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v int16
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorInt32(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewInt32Builder(mem)
+	defer bld.Release()
+
+	raw := []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewInt32Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Int32.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Int32)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Int32Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v int32
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorInt64(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewInt64Builder(mem)
+	defer bld.Release()
+
+	raw := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewInt64Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Int64.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Int64)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Int64Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v int64
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorUint8(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewUint8Builder(mem)
+	defer bld.Release()
+
+	raw := []uint8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewUint8Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Uint8.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Uint8)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Uint8Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v uint8
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorUint16(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewUint16Builder(mem)
+	defer bld.Release()
+
+	raw := []uint16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewUint16Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Uint16.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Uint16)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Uint16Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v uint16
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorUint32(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewUint32Builder(mem)
+	defer bld.Release()
+
+	raw := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewUint32Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Uint32.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Uint32)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Uint32Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v uint32
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorUint64(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewUint64Builder(mem)
+	defer bld.Release()
+
+	raw := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewUint64Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Uint64.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Uint64)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Uint64Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v uint64
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorFloat32(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewFloat32Builder(mem)
+	defer bld.Release()
+
+	raw := []float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewFloat32Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Float32.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Float32)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Float32Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v float32
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestTensorFloat64(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewFloat64Builder(mem)
+	defer bld.Release()
+
+	raw := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewFloat64Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.Float64.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.Float64)
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5 * bw, 1 * bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.Float64Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v float64
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
diff --git a/go/arrow/tensor/numeric.gen_test.go.tmpl b/go/arrow/tensor/numeric.gen_test.go.tmpl
new file mode 100644
index 0000000..dd512e8
--- /dev/null
+++ b/go/arrow/tensor/numeric.gen_test.go.tmpl
@@ -0,0 +1,126 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tensor_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+	"github.com/apache/arrow/go/arrow/memory"
+	"github.com/apache/arrow/go/arrow/tensor"
+)
+
+{{range .In}}
+
+func TestTensor{{.Name}}(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.New{{.Name}}Builder(mem)
+	defer bld.Release()
+
+	raw := []{{.Type}}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.New{{.Name}}Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+		bw    = int64(arrow.PrimitiveTypes.{{.Name}}.(arrow.FixedWidthDataType).BitWidth()) / 8
+	)
+
+	tsr := tensor.New(arr.Data(), shape, nil, names).(*tensor.{{.Name}})
+	defer tsr.Release()
+
+	tsr.Retain()
+	tsr.Release()
+
+	if got, want := tsr.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := tsr.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.Strides(), []int64{5*bw, 1*bw}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := tsr.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := tsr.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := tsr.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := tsr.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if tsr.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !tsr.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !tsr.IsRowMajor() || tsr.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := tsr.{{.Name}}Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v {{.Type}}
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := tsr.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+{{end}}
diff --git a/go/arrow/tensor/tensor.go b/go/arrow/tensor/tensor.go
new file mode 100644
index 0000000..53d5a45
--- /dev/null
+++ b/go/arrow/tensor/tensor.go
@@ -0,0 +1,239 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package tensor provides types that implement n-dimensional arrays.
+package tensor
+
+import (
+	"fmt"
+	"sync/atomic"
+
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+	"github.com/apache/arrow/go/arrow/internal/debug"
+)
+
+// Interface represents an n-dimensional array of numerical data.
+type Interface interface {
+	// Retain increases the reference count by 1.
+	// Retain may be called simultaneously from multiple goroutines.
+	Retain()
+
+	// Release decreases the reference count by 1.
+	// Release may be called simultaneously from multiple goroutines.
+	// When the reference count goes to zero, the memory is freed.
+	Release()
+
+	// Len returns the number of elements in the tensor.
+	Len() int
+
+	// Shape returns the size - in each dimension - of the tensor.
+	Shape() []int64
+
+	// Strides returns the number of bytes to step in each dimension when traversing the tensor.
+	Strides() []int64
+
+	// NumDims returns the number of dimensions of the tensor.
+	NumDims() int
+
+	// DimName returns the name of the i-th dimension.
+	DimName(i int) string
+
+	DataType() arrow.DataType
+	Data() *array.Data
+
+	// IsMutable returns whether the underlying data buffer is mutable.
+	IsMutable() bool
+	IsContiguous() bool
+	IsRowMajor() bool
+	IsColMajor() bool
+}
+
+type tensorBase struct {
+	refCount int64
+	dtype    arrow.DataType
+	bw       int64 // bytes width
+	data     *array.Data
+	shape    []int64
+	strides  []int64
+	names    []string
+}
+
+// Retain increases the reference count by 1.
+// Retain may be called simultaneously from multiple goroutines.
+func (tb *tensorBase) Retain() {
+	atomic.AddInt64(&tb.refCount, 1)
+}
+
+// Release decreases the reference count by 1.
+// Release may be called simultaneously from multiple goroutines.
+// When the reference count goes to zero, the memory is freed.
+func (tb *tensorBase) Release() {
+	debug.Assert(atomic.LoadInt64(&tb.refCount) > 0, "too many releases")
+
+	if atomic.AddInt64(&tb.refCount, -1) == 0 {
+		tb.data.Release()
+		tb.data = nil
+	}
+}
+
+func (tb *tensorBase) Len() int {
+	o := int64(1)
+	for _, v := range tb.shape {
+		o *= v
+	}
+	return int(o)
+}
+
+func (tb *tensorBase) Shape() []int64           { return tb.shape }
+func (tb *tensorBase) Strides() []int64         { return tb.strides }
+func (tb *tensorBase) NumDims() int             { return len(tb.shape) }
+func (tb *tensorBase) DimName(i int) string     { return tb.names[i] }
+func (tb *tensorBase) DataType() arrow.DataType { return tb.dtype }
+func (tb *tensorBase) Data() *array.Data        { return tb.data }
+
+// IsMutable returns whether the underlying data buffer is mutable.
+func (tb *tensorBase) IsMutable() bool { return false } // FIXME(sbinet): implement it at the array.Data level
+
+func (tb *tensorBase) IsContiguous() bool {
+	return tb.IsRowMajor() || tb.IsColMajor()
+}
+
+func (tb *tensorBase) IsRowMajor() bool {
+	strides := rowMajorStrides(tb.dtype, tb.shape)
+	return equalInt64s(strides, tb.strides)
+}
+
+func (tb *tensorBase) IsColMajor() bool {
+	strides := colMajorStrides(tb.dtype, tb.shape)
+	return equalInt64s(strides, tb.strides)
+}
+
+func (tb *tensorBase) offset(index []int64) int64 {
+	var offset int64
+	for i, v := range index {
+		offset += v * tb.strides[i]
+	}
+	return offset / tb.bw
+}
+
+// New returns a new n-dim array from the provided backing data and the shape and strides.
+// If strides is nil, row-major strides will be inferred.
+// If names is nil, a slice of empty strings will be created.
+//
+// New panics if the backing data is not a numerical type.
+func New(data *array.Data, shape, strides []int64, names []string) Interface {
+	dt := data.DataType()
+	switch dt.ID() {
+	case arrow.INT8:
+		return NewInt8(data, shape, strides, names)
+	case arrow.INT16:
+		return NewInt16(data, shape, strides, names)
+	case arrow.INT32:
+		return NewInt32(data, shape, strides, names)
+	case arrow.INT64:
+		return NewInt64(data, shape, strides, names)
+	case arrow.UINT8:
+		return NewUint8(data, shape, strides, names)
+	case arrow.UINT16:
+		return NewUint16(data, shape, strides, names)
+	case arrow.UINT32:
+		return NewUint32(data, shape, strides, names)
+	case arrow.UINT64:
+		return NewUint64(data, shape, strides, names)
+	case arrow.FLOAT32:
+		return NewFloat32(data, shape, strides, names)
+	case arrow.FLOAT64:
+		return NewFloat64(data, shape, strides, names)
+	default:
+		panic(fmt.Errorf("arrow/tensor: invalid data type %s", dt.Name()))
+	}
+}
+
+func newTensor(dtype arrow.DataType, data *array.Data, shape, strides []int64, names []string) *tensorBase {
+	tb := tensorBase{
+		refCount: 1,
+		dtype:    dtype,
+		bw:       int64(dtype.(arrow.FixedWidthDataType).BitWidth()) / 8,
+		data:     data,
+		shape:    shape,
+		strides:  strides,
+		names:    names,
+	}
+	tb.data.Retain()
+
+	if len(tb.shape) > 0 && len(tb.strides) == 0 {
+		tb.strides = rowMajorStrides(dtype, shape)
+	}
+	return &tb
+}
+
+func rowMajorStrides(dtype arrow.DataType, shape []int64) []int64 {
+	dt := dtype.(arrow.FixedWidthDataType)
+	rem := int64(dt.BitWidth() / 8)
+	for _, v := range shape {
+		rem *= v
+	}
+
+	if rem == 0 {
+		strides := make([]int64, len(shape))
+		rem := int64(dt.BitWidth() / 8)
+		for i := range strides {
+			strides[i] = rem
+		}
+		return strides
+	}
+
+	var strides []int64
+	for _, v := range shape {
+		rem /= v
+		strides = append(strides, rem)
+	}
+	return strides
+}
+
+func colMajorStrides(dtype arrow.DataType, shape []int64) []int64 {
+	dt := dtype.(arrow.FixedWidthDataType)
+	total := int64(dt.BitWidth() / 8)
+	for _, v := range shape {
+		if v == 0 {
+			strides := make([]int64, len(shape))
+			for i := range strides {
+				strides[i] = total
+			}
+			return strides
+		}
+	}
+
+	var strides []int64
+	for _, v := range shape {
+		strides = append(strides, total)
+		total *= v
+	}
+	return strides
+}
+
+func equalInt64s(a, b []int64) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}
diff --git a/go/arrow/tensor/tensor_test.go b/go/arrow/tensor/tensor_test.go
new file mode 100644
index 0000000..cb2631c
--- /dev/null
+++ b/go/arrow/tensor/tensor_test.go
@@ -0,0 +1,162 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tensor_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/apache/arrow/go/arrow"
+	"github.com/apache/arrow/go/arrow/array"
+	"github.com/apache/arrow/go/arrow/memory"
+	"github.com/apache/arrow/go/arrow/tensor"
+)
+
+func TestTensor(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewFloat64Builder(mem)
+	defer bld.Release()
+
+	raw := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewFloat64Array()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+	)
+
+	f64 := tensor.New(arr.Data(), shape, nil, names).(*tensor.Float64)
+	defer f64.Release()
+
+	f64.Retain()
+	f64.Release()
+
+	if got, want := f64.Len(), 10; got != want {
+		t.Fatalf("invalid length: got=%d, want=%d", got, want)
+	}
+
+	if got, want := f64.Shape(), shape; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid shape: got=%v, want=%v", got, want)
+	}
+
+	if got, want := f64.Strides(), []int64{40, 8}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid strides: got=%v, want=%v", got, want)
+	}
+
+	if got, want := f64.NumDims(), 2; got != want {
+		t.Fatalf("invalid dims: got=%d, want=%d", got, want)
+	}
+
+	for i, name := range names {
+		if got, want := f64.DimName(i), name; got != want {
+			t.Fatalf("invalid dim-name[%d]: got=%q, want=%q", i, got, want)
+		}
+	}
+
+	if got, want := f64.DataType(), arr.DataType(); got != want {
+		t.Fatalf("invalid data-type: got=%q, want=%q", got.Name(), want.Name())
+	}
+
+	if got, want := f64.Data(), arr.Data(); got != want {
+		t.Fatalf("invalid data: got=%v, want=%v", got, want)
+	}
+
+	if f64.IsMutable() {
+		t.Fatalf("should not be mutable")
+	}
+
+	if !f64.IsContiguous() {
+		t.Fatalf("should be contiguous")
+	}
+
+	if !f64.IsRowMajor() || f64.IsColMajor() {
+		t.Fatalf("should be row-major")
+	}
+
+	if got, want := f64.Float64Values(), raw; !reflect.DeepEqual(got, want) {
+		t.Fatalf("invalid backing array: got=%v, want=%v", got, want)
+	}
+
+	for _, tc := range []struct {
+		i []int64
+		v float64
+	}{
+		{i: []int64{0, 0}, v: 1},
+		{i: []int64{0, 1}, v: 2},
+		{i: []int64{0, 2}, v: 3},
+		{i: []int64{0, 3}, v: 4},
+		{i: []int64{0, 4}, v: 5},
+		{i: []int64{1, 0}, v: 6},
+		{i: []int64{1, 1}, v: 7},
+		{i: []int64{1, 2}, v: 8},
+		{i: []int64{1, 3}, v: 9},
+		{i: []int64{1, 4}, v: 10},
+	} {
+		t.Run(fmt.Sprintf("%v", tc.i), func(t *testing.T) {
+			got := f64.Value(tc.i)
+			if got != tc.v {
+				t.Fatalf("arr[%v]: got=%v, want=%v", tc.i, got, tc.v)
+			}
+		})
+	}
+}
+
+func TestInvalidTensor(t *testing.T) {
+	mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer mem.AssertSize(t, 0)
+
+	bld := array.NewBinaryBuilder(mem, arrow.BinaryTypes.Binary)
+	defer bld.Release()
+
+	raw := [][]byte{{1}, {2, 2}, {3, 3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}}
+	bld.AppendValues(raw, nil)
+
+	arr := bld.NewBinaryArray()
+	defer arr.Release()
+
+	var (
+		shape = []int64{2, 5}
+		names = []string{"x", "y"}
+	)
+
+	t.Run("invalid-binary", func(t *testing.T) {
+		want := fmt.Errorf("arrow/tensor: invalid data type binary")
+		defer func() {
+			e := recover()
+			if e == nil {
+				t.Fatalf("expected an error: %v", want)
+			}
+			switch err := e.(type) {
+			case error:
+				if !reflect.DeepEqual(err, want) {
+					t.Fatalf("invalid error: got=%v (%T), want=%v", err, err, want)
+				}
+			default:
+				t.Fatalf("invalid error: got=%v (%T), want=%v", err, err, want)
+			}
+		}()
+		tsr := tensor.New(arr.Data(), shape, nil, names)
+		defer tsr.Release()
+	})
+
+}