Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics and numbers #46

Open
SeanTAllen opened this issue Sep 16, 2020 · 0 comments
Open

Generics and numbers #46

SeanTAllen opened this issue Sep 16, 2020 · 0 comments

Comments

@SeanTAllen
Copy link
Member

This comes up often. Writing generic functions over number types.

For example a reasonable naive example might be:

class Block[T: Unsigned]
  var data: T

  new create(data': T) =>
    data = data'

  fun parity(): Bool =>
    (data.popcount() % 2) == 1

  fun apply_xor(o: Block[T]) =>
    data = o.data xor data

where the correct Pony version to compile would be

class Block[T: (Unsigned & UnsignedInteger[T])]
  var data: T

  new create(data': T) =>
    data = data'

  fun parity(): Bool =>
    (data.popcount() % T.from[U8](2)) == T.from[U8](1)

  fun ref apply_xor(o: Block[T]) =>
    data = o.data xor data

As @jemc explained in Zulip, the issue is:

First thing I did was change the type parameter constraint to Unsigned & UnsignedInteger[T] instead of just Unsigned
Unsigned is the union type that contains all builtin unsigned integers
If you use only this union as the constraint, then theoretically from the compiler's perspective you could instantiate with a union type as the type argument - for example Block[(U8 | U16)], which would cause problems because you can't do binary operations on numbers of varying types
Using the trait UnsignedInteger[T] as a constraint for T guarantees that T must be able to do those binary operations that are part of the trait with a second operand of type T, which resolves that issue.

The other relevant thing I did to fix the code was to wrap the numeric literals in some extra needed magic - changing 2 into T.fromU8

The Pony compiler can't do number literals for a generic type - it needs to know it as a single type at the time of encountering the literal. So the T.fromU8 expression tells Pony that the number literal is of type U8, then it will convert to type T.

Since we're dealing with number literals, which constant values in LLVM, the extra conversion can be trivially optimized by LLVM such that there is no runtime overhead for the conversion from U8 to T.

Once this pattern is created, we should in turn create an FAQ on the website and link to this pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant