Buffer.fromArray() missing from motoko base lib

I wanted to know if there is a reason why there is no fromArray fn for the Buffer class. All the other class data structures in the lib have a fromArray or fromEntries method that allows for instantiation from stable data structures.
Does having a fromArray method in the Buffer class have unexpected consequences like with the Array.append() fn? If not, I think it would be beneficial to include it in the base lib instead of devs implementing it in each project.
Would it also be possible for me to make a PR to the base repo with this feature?

2 Likes

I see no reason for not having this (@MatthewHammer whaddya think?). And do feel free to open a PR.

1 Like

It’s hard to recall, but I seem to remember some pressure to keep the API very small, because it enlarges each Buffer instance to have more methods?

Separately, I’m curious what reasonable semantics doing b.fromArray(a) would mean if b was non-empty? Would it erase those existing elements, or append to them? Either way seems confusing to me, personally.

But how about another place for this bufferFromArray functionality, which I agree is missing and reasonable?

For instance, I think a toBuffer function makes sense in the existing Array module, where it would be static, and not associated with any existing buffer object. Likewise, having a special array method toBuffer() would also be reasonable, but perhaps more work to add.

What do you think @tomijaga and @claudio ?

Here’s a PR https://github.com/dfinity/motoko-base/pull/387

3 Likes

I was thinking of adding it as a static method in the Buffer module. This method would only instantiate a Buffer with all the elements in an array
So users would be able to instantiate buffers from an existing array like this:
let b = Buffer.fromArray([1, 2])

I like that you made a pr with this exact functionality in the Array module, but I think adding it to the Buffer would be more intuitive.

3 Likes

There is PR for Buffer.fromArray Add fromArray method to Buffer by xlaywan · Pull Request #368 · dfinity/motoko-base · GitHub

1 Like

Thanks, this is the exact functionality I was referring to.
I noticed that this pr uses the add buffer method for the fromArray method. The problem with that the given array could be significantly bigger than the size of the buffer. In this case, the buffer could resize and copy all its values more than once to add all the elements of the array. I made a PR that only resizes the array once if this happens. Efficient addArray and fromArray buffer methods by tomijaga · Pull Request #388 · dfinity/motoko-base · GitHub

2 Likes

Buffer.fromArray is listed in the Buffer documentation, but does not exist according to dfx deploy when running dfx 0.11.2:

type error [M0072], field fromArray does not exist in type
  module {
    type Buffer<X> =
      {
        add : X -> ();
        append : Buffer<X> -> ();
        clear : () -> ();
        clone : () -> Buffer<X>;
        get : Nat -> X;
        getOpt : Nat -> ?X;
        put : (Nat, X) -> ();
        removeLast : () -> ?X;
        size : () -> Nat;
        toArray : () -> [X];
        toVarArray : () -> [var X];
        vals : () -> {next : () -> ?X}
      };
    Buffer : <X>Nat -> Buffer<X>
  }

My code:

import Buffer "mo:base/Buffer";
...
let media : Buffer.Buffer<Media> = Buffer.fromArray<Media>(init.mediaEntries);

I see that fromArray exists in the buffer module, so why is dfx deploy surfacing these erroneous errors? Intellisense does not underline the function call, but the errors print out when running dfx build or dfx deploy and prevent a successful build.

If I change the call to an actually-non-existent function:

let media : Buffer.Buffer<Media> = Buffer.someOtherFunction<Media>(init.mediaEntries);

I get intellisense red underlines on the call and when I hover over it I see following, which does list fromArray as a function it’s expecting to exist on the Buffer module:

field someOtherFunction does not exist in type
  module {
    type Buffer<X> =
      {
        add : X -> ();
        append : Buffer__33665<X> -> ();
        clear : () -> ();
        clone : () -> Buffer__33665<X>;
        get : Nat -> X;
        getOpt : Nat -> ?X;
        put : (Nat, X) -> ();
        removeLast : () -> ?X;
        size : () -> Nat;
        toArray : () -> [X];
        toVarArray : () -> [var X];
        vals : () -> {next : () -> ?X}
      };
    Buffer : <X>Nat -> Buffer__33665<X>;
    fromArray : <X>[X] -> Buffer__33665<X>;
    fromVarArray : <X>[var X] -> Buffer__33665<X>
  }

dfx --version returns 0.11.2, which matches the dfx version specified in my dfx.json.

What’s going on here? Am I calling the function incorrectly? I’m calling it the same as I’m calling other base library module functions. My workaround is to implement a bufferToArray function, but I’d like to know why I can’t use the one defined in the base library.

If you see here, fromArray is just a normal function making buffer by iterating over array. So you can build your own buffer to array function doing same. Because fromArray is removed actually i read somewhere.

It’s still in the codebase, and that’s the function I copied into my own bufferFromArray function (except I just used array.size() instead of newCapacity(array.size())

If the function was removed, it should probably be removed from the documentation if developers are expected to implement it themselves. But the removal of the function doesn’t explain the strange intellisense behavior I’m experiencing unless there’s some weird versioning issues going on where dfx deploy is using a different version of the base libraries than intellisense is.

Thanks to @kentosugama’s rework of the Buffer module, Buffer.fromArray() and many more utility methods/functions now exists in motoko-base. (See motoko-base/Buffer.mo at aafcdee0c8328087aeed506e64aa2ff4ed329b47 · dfinity/motoko-base · GitHub)

As of this post, I believe these changes are in the base moc-0.7.2 release, but I’d probably use the latest moc or at least 0.7.3 since there’s a few bug fixes for the functions in that class that have come out in the meantime.

If you use vessel, you can reference the latest base release like so

let packages = [
  { name = "base"
  , repo = "https://github.com/dfinity/motoko-base"
  , version = "moc-0.7.2"
  , dependencies = [ "base" ]
  }
]: List Package

Or the latest commit via

let packages = [
  { name = "base"
  , repo = "https://github.com/dfinity/motoko-base"
  , version = "<commit_hash_here>"
  , dependencies = [ "base" ]
  }
]: List Package
2 Likes