So, I started working with concepts, which are awesome, and I ran across a use case that seems like it should work, but fails to compile. Basically, we see concrete types as subtypes of concepts they match in most cases, but it seems like they are not handled the same way when they themselves are the generic type. Here is some code that I think should work that doesn't compile. Hopefully it makes my question clear.
import typetraits # just for the silly use of name() later
type
# A concept with a generic type
HasId[T] = concept thing
thing.id is T
# Two types that match the concept
IThing = object
id: int
SThing = object
id: string
let i1 = IThing(id: 0)
let s1 = SThing(id: "hi")
# We recognize the concrete objects as matching the concept
assert i1 is HasId
assert s1 is HasId
# We recognize the types themselves as matching the concept
assert IThing is HasId
assert IThing is HasId[int] # explicitly naming the generic type
assert SThing is HasId
assert SThing is HasId[string]
# We can use the concept to match concrete objects passed to procs
proc printId(thing: HasId) : void =
echo thing.id
printId(i1)
printId(s1)
# We can use the type itself to choose which proc to match
proc printType(thingType: typedesc[IThing]): void =
echo name(thingType)
proc printType(thingType: typedesc[SThing]): void =
echo name(thingType)
printType(IThing)
printType(SThing)
# I would like to be able to reduce those two procs above to one function that
# is more flexible: that matches not against the specific object types, but
# against the concept.
#
# The following will fail to compile:
proc printConceptType(thingType: typedesc[HasId]): void =
echo name(thingType)
printConceptType(IThing)
printConceptType(SThing)
# I thought, maybe we should be explicit about paramterizing the generic type?
# Still fails to compile:
#
proc printConceptTypeWithGeneric[T](thingType: typedesc[HasId[T]]): void =
echo name(thingType)
printConceptType(IThing)
printConceptType(SThing)
My use case for this is I want to support a type of compile-time polymorphism, like generic typed marshaling with constraints. For example, I'd like to be able to define a HasId concept that requires that the object have an id field of some given type, and require a few procs to exist to manipulate that type. Then I want to be able to use the system.fieldPairs iterator to generically marshal and unmarshal any object that matches the HasId concept. Marshaling is pretty straightforwards, something like:
proc marshal*(obj: HasId): SerializedType
but I'd like to be able to write something like:
proc unmarshal(t: typedesc[HasId], input: SerializedType): HasId =
result = new(t_type)
# do stuff to de-serialize...
As it currently stands, I am pretty the following will work, but it seems silly to have to pass in a concrete object just to identify what kind of object I want to build. Seems more logical to pass in the type only, since I'm going to use reflection to build it anyway:
proc umarshal(t: HasId, input: SerializedType): HasId =
result = new(type(t))
# do stuff to de-serialize...
Am I missing something? Is this just an edge case with concepts we haven't covered yet?