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

typetraits: add rangeof(T), a shortcut for low(T)..high(T) #15232

Closed
wants to merge 1 commit into from

Conversation

alaviss
Copy link
Collaborator

@alaviss alaviss commented Aug 27, 2020

This func returns a slice with the full range of type T, basically a
shortcut for low(T)..high(T). This is useful for verifying whether a
value of one type can be converted to an another.

The main use case for this would be as a sugar for the initial
conversion between an ordinal and a range type, though it's flexible
enough that it can be used for many other situations, like conversions
between int/uint of different sizes.

This is an alternative to #15212 in which runtime values and types are
now properly separated with an explicit function call.

Closes #15212.

This func returns a slice with the full range of type `T`, basically a
shortcut for `low(T)..high(T)`. This is useful for verifying whether a
value of one type can be converted to an another.

The main use case for this would be as a sugar for the initial
conversion between an ordinal and a `range` type, though it's flexible
enough that it can be used for many other situations, like conversions
between int/uint of different sizes.
@@ -183,3 +183,32 @@ since (1, 1):

type T2 = T
genericParamsImpl(T2)

func rangeof*(T: typedesc): Slice[T] {.inline, since: (1, 3, 5).} =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be named sliceof and then it's ready to go as far as I'm concerned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sliceof sounds weird IMO. Example:

assert 42 in sliceof(Positive):

It's hard to understand that statement without knowing what slices are in Nim, and even then you'd need to whip out the docs just to know what sliceof() does.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it returns a Slice[T] so I have to know Nim's slice type.

Copy link
Collaborator Author

@alaviss alaviss Aug 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but arguably, comparing between these:

assert 42 in rangeof(Positive)

assert 42 in sliceof(Positive)

The former does a better job at saying "I want to make sure that 42 is within the range of type Positive".

You can know Slice[T] and still have to stop to look at the docs because sliceof(type) just sound alien.

@haoyu234
Copy link
Contributor

haoyu234 commented Sep 9, 2020

I think it is not suitable to add it to the standard library because it is difficult to name and has fewer use cases.

@alaviss
Copy link
Collaborator Author

alaviss commented Sep 12, 2020

I think it is not suitable to add it to the standard library because it is difficult to name and has fewer use cases.

Can you elaborate?

@arnetheduck
Copy link
Contributor

As highlighted by a comment in https://forum.nim-lang.org/t/8188, enum:s are also a kind constrained integer that would need a similar facility - they're not representable as a range though since they might have holes.

@timotheecour
Copy link
Member

As highlighted by a comment in forum.nim-lang.org/t/8188, enum:s are also a kind constrained integer that would need a similar facility - they're not representable as a range though since they might have holes.

just a matter of changing signature to

func rangeof*(T: typedesc): Slice[T]
=>
func rangeof*(T: typedesc[Ordinal]): Slice[T] =

Comment on lines +175 to +193
```nim
import typetraits, strutils

type
AllowedPort = range[1024..65535]

proc setupServer(port: AllowedPort) =
# Setup a webserver...
discard

stdout.write("Please enter a port [1024-65535]: ")
stdout.flushFile()
let port = stdin.readLine().parseInt()

if port in rangeof(AllowedPort):
setupServer(AllowedPort(port))
else:
stderr.write("Invalid port number")
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since when do examples go into the changelog?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a trend started by Nim 1.2 where we put examples of new syntactic sugar to the change log.

Copy link
Member

@timotheecour timotheecour Jul 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example belongs where the API is defined, not in the changelog (DRY, plus follows current convention, plus changelog is too big already for adding such examples)

## setupServer(AllowedPort(port))
## else:
## stderr.write("Invalid port number")
low(T)..high(T)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly think this is too short to justify it's own stdlib function, people can just write that themselves.

@arnetheduck
Copy link
Contributor

just a matter of changing signature to

that doesn't help solving this issue for enum - ie rangeof is somewhat limited capability

@timotheecour
Copy link
Member

that doesn't help solving this issue for enum - ie rangeof is somewhat limited capability

ordinal enums are ordinal

@arnetheduck
Copy link
Contributor

ordinal enums are ordinal

that is a point that's true, yes... but not all enums are ordinal.

@timotheecour
Copy link
Member

that is a point that's true, yes... but not all enums are ordinal.

rangeof(T) with signature i gave in #15232 (comment) will work for ordinal enums, and give sigmatch error for holey enums, ie, it works as designed; it wouldn't make sense to define rangeof(T) for holey enums.

@arnetheduck
Copy link
Contributor

it wouldn't make sense to define rangeof(T) for holey enums.

no, it wouldn't - that was the essence of my initial comment: there's opportunity to create an API with broader support to cover safe conversion of more nim types that could be evaluated.

@alaviss
Copy link
Collaborator Author

alaviss commented Jul 6, 2021

it wouldn't make sense to define rangeof(T) for holey enums.

no, it wouldn't - that was the essence of my initial comment: there's opportunity to create an API with broader support to cover safe conversion of more nim types that could be evaluated.

#15212 covers this

@timotheecour
Copy link
Member

timotheecour commented Jul 6, 2021

that was the essence of my initial comment: there's opportunity to create an API with broader support to cover safe conversion of more nim types that could be evaluated.

i don't understand the essence of your comment then, what would your API be for holey enums.
We already have items for enums (including holey enums) see std/enumutils.

With #18044, that API even becomes O(1) (even for holey enums).

And by simply exposing a private-for-now symbol, int => Enum (even with holes) that doesn't raise can be done in O(1) as well after that PR, which solves the problem raised in https://forum.nim-lang.org/t/8188, without raising, and in O(1) instead of O(n)

## # Setup a webserver...
## discard
##
## stdout.write("Please enter a port [1024-65535]: ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example is way too complex especially for this simple API; good examples should illustrate semantics and edge case, that's it

@timotheecour
Copy link
Member

timotheecour commented Jul 8, 2021

docs say:

This is a shortcut for low(T)..high(T).

but this fails, see 3 cases below:

import std/typetraits
for a in low(int8)..high(int8): discard # ok
for a in rangeOf(int8): discard # ok
type A1 = low(int8)..high(int8) # ok
type A2 = Slice[int8.low..int8.high]
# type A3 = rangeOf(int8) # Error: expected type, but got: rangeof(int8)
# type A4 = range[rangeOf(int8)] # Error: expected range

var x1: array[int8.low..int8.high, int]
var x2: array[range[int8.low..int8.high], int]
# var x3: array[rangeOf(int8), int] # Error: ordinal type expected

so I agree with @Araq, rangeOf should be named sliceOf, if anything.

note

it also fails with: template rangeof*(T: typedesc): untyped = low(T)..high(T)

arguably, the compiler should work with that definition at least, but it hits one of those buggy parts of compiler (array + range generic instantiation of the 1st argument) that's full of hard-coded special cases.

Maybe this PR could be revisited once range + array work with this definition.

@alaviss
Copy link
Collaborator Author

alaviss commented Sep 9, 2021

This was originally made to address the "types should be separated from runtime value" criticism in #15212, but now I think this is just an API that happens to overlap with some cases of #15212 and not the solution.

If anyone wants this, feel free to pick it up and make a new PR.

@alaviss alaviss closed this Sep 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants