Repeating a character/string in Motoko (repost)

Python lets you use the asterisk operator to repeat a character or string a given number of times:

>>> 'abc' * 7
'abcabcabcabcabcabcabc'

Is something like this possible in Motoko?

I tried to achieve the effect by combining Array.init and Array.fold as follows, but am running into issues with array mutability. Here’s my attempt. I’m hoping someone can correct this code to avoid the mutability issues, but also point me toward a better way to repeat a string in Motoko.

func fold(accum: Text, el: Text): Text {
    accum # el;
};
let arr: [Text] = Array.init(7, "abc");
let indent = Array.foldLeft<Text, Text>(arr, "", fold);
2 Likes

Motoko does not have a whole lot of syntactic sugar yet. Your solution seems correct, maybe just add the freeze function.

   let arr = Array.freeze(Array.init(7, "abc"));
   let res = Array.foldLeft(arr, "", func (acc, x) { acc # x });
1 Like

Works great, thanks!

1 Like

Well, almost. I had to add the <Text, Text> type to the foldLeft call or it complained about type error, cannot infer type of variable

let arr = Array.freeze(Array.init(7, "abc"));
let res = Array.foldLeft<Text, Text>(arr, "", func (acc, x) { acc # x });
1 Like

There should probably be better support for text ops in the library, but you can slightly improve on enzo’s solution:

let arr = Array.tabulate(7, func _ { "abc" });
let res = Array.foldLeft(arr, "", Text.concat);

This way, the type parameters aren’t necessary either.

3 Likes

I got type error, cannot infer type of wildcard with your code, @rossberg. Adding the type to the tabulate call works. Not sure why that couldn’t be inferred from the function’s return type. I also had to import Text, which I guess is necessary when using functions like concat but not when using using the Text type

import Text "mo:base/Text";
let arr = Array.tabulate<Text>(7, func _ { "abc" });
let indent = Array.foldLeft(arr, "", Text.concat);

That’s correct, the types are always in scope, but all value (including modules) have to imported explicitly.

1 Like

Ah, yes, sorry. For the call of a generic function, the type checker can only propagate types in one direction, either inside out or outside in. That means that you either need to provide all type arguments explicitly (so that all the values’ argument types can be inferred), or all value arguments need to be explicitly typed (so that the type arguments can be inferred). That means that either of the following would work:

Array.tabulate(7, func (_ : Nat) : Text { "abc" })
Array.tabulate<Text>(7, func _ { "abc" })

Hope that helps.

3 Likes

Is this a temporary limitation of the type checker? I believe the TypeScript compiler has no trouble inferring both inside out and outside in.

For example, this compiles fine in TS:

function foo<T>(x: T, f: (arg: T) => number): number {
  return f(x);
}

foo(10, x => x + 5);

We have no plans to improve this at the moment.

I know that C# went to quite some lengths to get this to work, leading to a very complicated inference algorithm based on solving constraints in dependency order that we don’t want to commit to.

I suspect TS made a similar choice.