
@stamourv changed my mind, I’ll just leave it the way it is and instead put that energy into writing something different :p

next time I’ll be clearer about when it’s no longer a WIP though

is there any way to express this in typed/racket? (struct (a) Some ([v : a]))
(: sneaky-hetero (Listof Some))
(define sneaky-hetero (list (Some 1) (Some "a")))
typed/racket obviously doesn’t like the above code. I am trying to express that I have a list of polymorphic data structures, and I want to retain the type of each item in the list - so (Listof (Some Any)) won’t do - but I don’t want to restrict which types my data structures’ type variables can be initialized to.

As far as I know, Typed Racket’s type system is not able to express that (I might be wrong, though). But I guess occurrence typing (http://docs.racket-lang.org/ts-guide/occurrence-typing.html) might be able to get you half way there. Let (: sneaky-hetero (Listof (Some Any)))
and then match
on the elements as you get them out of the list.

You might be able to do this by declaring the type of the field in the struct as an intersection of A and a union of the types you want to allow the struct to be initialized with

But intersections are a relatively new feature, so it’s possible you might run into some use cases of intersections that haven’t been fully fleshed out yet (bug reports welcome! =)

alas, both the match and the intersection solutions would require me to be able to list all possible types a priori, which isn’t possible in this case. :disappointed:

I believe you hit the edge of Typed Racket’s current capabilities. You want to abstract over the data container (the struct Some
, in your case), which can’t be done in the current system. That’s the same reason why dict
doesn’t exist in Typed Racket, one has to be specific about the underlying implementation of the dictionary (using hash-ref
, assoc
and friends).

@leafac are you saying they could accomplish this task with generics (if they existed in TR)? I don’t think I fully understand the issue, and it’s not obvious to me at the moment why generics would solve this

(I’m not trying to suggest they would not — just trying to understand)

I was thinking, for example, of the following Java code:
import java.util.ArrayList;
import java.util.List;
class Some<T> {
T value;
public Some(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class Generics {
public static void main(String[] args) {
List<Some> sneakyHetero = new ArrayList<>();
sneakyHetero.add(new Some(1));
sneakyHetero.add(new Some("a"));
}
}

@leafac that’s using raw types which is not really something we’d want in TR

I’m afraid I don’t understand what you mean by “raw types.” Could you please expand on that?

Some
in Java is both a type constructor, but also a type

when you use it as a type, it’s basically cheating and working like pre-generics java

I understand it, thanks for the explanation. Indeed, I got some warnings from the code above. In conclusion, I was wrong: generics don’t give the expressiveness necessary to represent what @mebassett wanted. In other words, that problem is not related to dict
. Is this right?

parametric types aren’t sufficient to represent heterogenous lists (and similarly, other heterogenous containers like dict)

@leafac I think @mebassett is thinking of existential types, which TR does not (directly) support

but the subtyping solution is the right thing for that problem, I believe

@thinkmoore: I’m going to try to summarize what you said to confirm that I understood. Typed Racket’s All
is equivalent in power to Java’s generics. They are both forms of parametric polymorphism. They are capable of expressing homogenous containers (for example, Java’s List
) but not capable of expressing heterogeneous containers (for example, Racket’s dict
). Did I get it?
@samth: When I learned about existential types (from reading Pierce’s book), I associated them with interfaces. How would they apply to the problem at hand? Also, what do you mean by “subtyping solution?”

I mean (Listof (Some Any))

and dict
is not a heterogenous container any more than lists are

also, for both Java and TR, the “homogenous”/heterogenous distinction doesn’t make as much sense, because you can have List<Object>
or (Listof Any)
which can contain any value

So the “subtyping solution” is to use occurrence typing. In other words, to match
on the value as it comes out of the list. Right?

yes

well, it really depends what you’re trying to do with the elements of the list

“and dict
is not a heterogenous container any more than lists are”: The way I understand it, lists in Typed Racket are either homogeneous (for example, (Listof Integer)
), or they are more like tuples, in the sense that it has a fixed size and each positing has a specified type (for example, (List Integer String)
). (And tuples are really just Pair
in disguise.)
So, how come Listof
and HashTable
are things, but dict
is not?

@thinkmoore, @pnwamk: You said generics don’t exist in Typed Racket, but parametric types do. I thought they were the same thing. Can you please help me get this straight?

generics in Racket are sort of like inferfaces in Java — it’s orthogonal to parametricity

we could imagine, if and when generics are added to TR, many will use parametric types

but they wouldn’t have to

And I thought interfaces in Java were related to existential types, but @samth just said Typed Racket doesn’t have them. As is usually the case when I try to think of type systems, I’m getting more confused :slightly_smiling_face:

there’s a confusion here because “generic” in Java refers to parametric polymophism, but “generic” in Racket refers to ad-hoc interfaces like dict

he said “TR does not (directly) support”

Oh, it’s getting clearer now :slightly_smiling_face:

One more attempt to understand things: @mebassett wanted to create a list in which all elements are Some A
, but for different A
at each element. In Java, the solution for this is not use Java’s generics (parametric polymorphism), but to let Some
be an interface, which all A
would implement. This ties us back to existential types, which Java’s interfaces look like.
Now, Typed Racket supports parametric polymorphism (for example, (All (A) ...
), but we just established that it does not solve the problem. What we want is Java’s interfaces, which we call Racket’s generics. And Typed Racket does not support Racket’s generics. So @mebassett is stuck with occurrence typing, which relies on subtyping.

In conclusion, my initial observation was correct. @mebassett’s problem is related to dict
. Both touch on Typed Racket’s lack of support for Racket’s generics (or Java’s interfaces, or existential types).

It sort of depends what @mebassett wants to do with the elements of the list

this discussion has evolved quite a bit from my original question (and has been nevertheless enlightening). the context of my problem was me trying to write around TR’s lack of generics. I was trying to write a way were one could “register” certain structures and casting operations so that one could dynamically cast those structures into JSExpr. id est, I wanted a (: dynamic-json-cast (-> Some-Jsonable-Type JSExpr))
My plan of attack was to have dynamic-json-cast
search through a [mutable] list of (struct (T) Json-Cast-Datum ([predicate : (-> Any Boolean : T)] [->json (-> T JSExpr)]))
.

I think I have a hack that might solve your problem. Instead of registering Json-Cast-Datum
into a list, why don’t you make dynamic-json-cast
an extensible function (https://docs.racket-lang.org/extensible-functions/index.html)?

Everywhere you would mutate the list of Json-Cast-Datum
, just extend dynamic-json-cast
. This relies on occurrence typing to work, it’s the subtyping solution @samth was talking about. But it’s a convenient way to list the various cases in multiple places, instead of centralizing them. It even allows third-party libraries to extend dynamic-json-cast
, if necessary.

@notjack: Sounds good.

Disclosure: I’m the author of extensible functions :slightly_smiling_face:

@leafac thanks for the tip! I’ll give it a go, that might work.

@mebassett: Thank you for considering it. Don’t mind the “dependency problems” reported by the package manager (https://pkgd.racket-lang.org/pkgn/package/extensible-functions). I already uploaded a fixed version, you can install it now. And tomorrow that warning is hopefully going to go away.

Is there any reason not to make POD structs in my library serializable, in the racket/serialize
sense?

Someone opened a PR to make values from data/maybe
and data/either
serializable, which seems reasonable to me, but I don’t know if there are any downsides.

Is there a way to create null output ports—I mean ports that just throw away all output?

NM, it’s called open-output-nowhere
and is in the miscellaneous section of the ports reference.