Motoko's type system & converting between types

Every now and then I struggle with converting between different types Motoko has to offer.

Generally conversion within differently-sized instances of one type work well - say I want to convert an Int8 to an Int16 then there’s methods for that - might have to go via Int, but it works and behaves as expected, trapping on overflow etc.

However once you start going between different types, it quickly gets hairy. Say I want to convert a Nat to an Int. Obviously NatN might overflow an IntN, so I’d fully expect to have to handle overflow. However - as far as I can tell there is simply no way to do that at all.

I like to use Nat when a value is guaranteed to be positive, to be more semantically precise. However this then prevents me from ever using this value in a calculation where I might get a negative result.

The same goes for eg these conversions:

  • IntNNatN, this is safe so really should be easy. Yet IntN.abs() returns an… IntN. You can achieve this with a roundtrip through Int (NatN.fromNat(IntN.toInt().abs())) but let’s be real, that’s insane. :wink:
  • NatFloat. While you can convert between Int64 and Float, the inability to get anywhere from a Nat prevents this
  • Text → Numeric. Usually languages offer a way to parse a text-based representation of a numerical value - think Go’s strconv module, C’s stroto... functions etc. As far as I am able to tell, there is no such thing in Motoko.

Am I missing information? The above is based on what I could find in the docs of the stdlib, but I might have missed it. Or am I misusing the type system? Or are these simply examples of Motoko being a very young language, still?

I must say that I enjoy many of the concepts I have encountered in Motoko - pattern matching makes for much nicer error handling than magic return values or exceptions. But its type system tends to be a hindrance as often as it is a help.

5 Likes

Funny that you are asking about this now, I am currently working on numericals types. See Remove Word type by nomeata · Pull Request #214 · dfinity/motoko-base · GitHub for the motoko-base exposition of that.

As for the (wrapping) conversions between NatN and IntN: These are supported by the compiler, but so far not exposed in the base library; this will be fixed with my changes to the compiler and the above PR to base (although it could be fixed in base already now).

As for NatFloat, you can use Float.fromInt – remember that due to subtyping, you can always use a Nat where an Int is expected.

For parsing text to numbers, we don’t have that at the moment. Surely something that we might want eventually, although maybe less pressing than initially perceived: In the typical architecture where the Motoko canister implements the backend of a service, tha a frontend (e.g. in JS) communicates via strongly typed candid value, you hopefully don’t have to do much parsing in the backend – Candid supports transmitting float values as is (float64).

4 Likes

remember that due to subtyping, you can always use a Nat where an Int is expected.

Oh, that I did not consider - should have just tried it. That is helpful. :smiley:

I am currently working on numericals types

Glad to hear that, looking forward to the changes then.

For parsing text to numbers, we don’t have that at the moment. Surely something that we might want eventually, although maybe less pressing than initially perceived

Fair point, as data currently comes into canisters solely via Candid-capable interfaces, one can probably use proper numerical types in many cases.
I can however envision a few cases where I might end up with user-provided textual inputs containing numerical values. For an academic example, consider the textbook exercise of implementing a calculator.

But I can see how it might not be the most pressing matter. :slight_smile:

Appreciate the quick feedback.

Quick question here

How do i convert Nat32 to Text ?

func toText(x : Nat32) : Text

How to invoke this function ?

I believe:

Nat32.toText(1);

2 Likes

Let me add a clarification. It used to be that Float.fromInt did an Int -> Int64 conversion before the Int64 -> Float one. While enough for many purposes, it trapped (in the former conversion) when the Int64 was not able to represent the Int’s magnitude. This has been corrected in the moc version 0.6.9. I expect that the next release of dfx will pick it up.

2 Likes

Is there a supported way of going from Int to Nat, now?

1 Like

Nothing beyond Int.abs, I’m afraid.

Nat to Int is allowed just by subtyping.

Should we add a Nat.fromInt that traps on negative argument?

6 Likes

Is Text to Nat conversion still not provided? I’m writing a backend that responds to http_request and needs to convert a query parameter into a number to be able to look up items in an array, so there’s no frontend to do the conversion for me.

I found this post which provides code to convert text to float, and it’s easy enough especially since I don’t need to worry about decimals, but I was just wondering if text->number conversion is provided in the base library anywhere yet.

In case this helps:

I wrote a simple function based on @Kyan’s post that works well enough for me (I didn’t need decimal support):

public func textToNat(t : Text) : ?Nat {
  var n : Nat = 0;
  for (c in t.chars()) {
    if (Char.isDigit(c)) {
      let charAsNat : Nat = Nat32.toNat(Char.toNat32(c) - 48);
      n := n * 10 + charAsNat;
    } else {
      return null;
    };
  };

  return Option.make(n);
};
2 Likes

@mymikemiller
Do you mind if we add this snippet as a function in the base library?

Of course not, go ahead!

2 Likes

How do I convert an int to a nat?

Fun fact. Nats are Ints so you don’t have to convert them. For Ints to Nats I usually use Int.abs(X). (of course, check to make sure it isn’t negative and you aren’t doing something awful. :slight_smile:

I figured out the Int.abs() returns a nat