Understanding Data extension generic method (?) from 00_load_data

Hi there, I’m looking for any pointers to help in understanding the following from 00_load_data:

extension Data {
    func asTensor<T:ConvertibleFromByte>() -> Tensor<T> {
        return Tensor(map(T.init))
    }
}

I can’t find any documentation on generic methods in Swift (if that’s what it is). I thought it might be equivalent to “Extensions with a Generic Where Clause,” but rewriting it like this:

extension Data where T: ConvertibleFromByte {
    func asTensor() -> Tensor<T> {
        return Tensor(map(T.init))
    }
}

gives the compiler error:

trailing 'where' clause for extension of non-generic type 'Data'
  • if ‘Data’ is non-generic, where does T’s Type come from?
  • it looks as if Data’s Type can be inferred by the compiler (and put into T), but what in the code makes this possible?
  • is this a special case for “Data”, or generally available in the language?

Can anyone suggest any resources that would get someone to understanding this grammar?!

Thanks!

Leo

I think I’m getting it…

The compiler can infer the type later, when the function is called; either from an explicitly typed variable:

let norf = Data([1,2,3])
let purg: Tensor<Int32> = norf.asTensor()
purg
[1, 2, 3]

Or from the return signature of another function (this is what happens in the notebook):

func getTestTensor() -> Tensor<Float> {
    let nurf = try! Data(contentsOf: URL(fileURLWithPath: "/root/swiftai/nbs/train-images-idx3-ubyte.gz"))
    return nurf.asTensor()
}

let foop = getTestTensor()

foop
[ 31.0, 139.0,   8.0, ..., 198.0, 205.0,   2.0]

Tricky though!

Still:

  • why does T.init need to be mapped over the contents?

Indeed, Data is a non-generic type, and the compiler error is expected.

The original code is equivalent to this:

extension Data {
    func asTensor<T>() -> Tensor<T> where T: ConvertibleFromByte {
        return Tensor(map(T.init))
    }
}

T is a generic parameter of the Data.asTensor method, not on Data itself.

More generally: non-generic types can totally define generic methods and subscripts.

1 Like

Right, thanks @dan-zheng .

And the T.init closure, I think it is taking an Array of UInt8, initing an Array of something ConvertibleFromByte (either Float or Int32 here), and passing that to the Tensor constructor, is that accurate?

Data.map<T>(_:) has a parameter of type (UInt8) -> T.

The ConvertibleFromByte protocol defines an init(_: UInt8) initializer requirement, which exactly has the function type (UInt8) -> Self and can be passed to Data.map(_:).

The generic parameter T on Data.asTensor() is constrained to ConvertibleFromByte, so we can access the ConvertibleFromByte protocol requirement as T.init(_:).

1 Like