5 ms·
You should look at modern structural type systems that support generics. They have none of the flaws you're describing here. TypeScript is one such example. Fo
by spion 10y ago
You should look at modern structural type systems that support generics. They have none of the flaws you're describing here. TypeScript is one such example.
For example if you define a TypeScript interface
interface Serializable {
toJSON():string
}
then any object that has a toJSON() method that returns a string is automatically accepted as a Serializable. You can even define such anonymous interfaces inline without giving them a name:
function frob(obj:{toJSON():string}) {
// do stuff
}
Basically, you get statically checked duck types and no coupling.
- jstimpfle 10y agoI think Go's story is similar. I know neither TypeScript nor Go, but "no coupling" isn't so simple as removing "implements" qualifiers. Basically the only way to really eliminate coupling that I see, is passing explicit function dictionaries, and retract the idea that there is only one valid implementation of any given (type, concept) tuple. It's very bad if all interface implementations have to be physically coupled with the implementation of the class itself (as I think the case is with Java). It's still at least unflexible sometimes if you are free to put the implementation anywhere (at the class definition site, at the interface definition site, at an independent site) but the system enforces that there is at most one implementation of each interface (this is how it's done in Haskell). This strongly discourages alternative implementations because on has to circumvent the machinery. I don't think there can be a system that automates away at least part of the boilerplate that is dictionary passing, while still retaining the flexibility to choose alternative implementations. The Haskell (maybe also TypeScript?) way can be very nice if you can be sure that there can be only one valid implementation of a concept. However, most concepts actually don't lend themselves to a single implementation. Most concepts aren't mathematically pure enough for there to be one and only one canonical implementation. Right now I can think only of a few where it's almost always fine to use the default implementation instead of being explicit, because one doesn't care much about the result - like "Show" for debug output. Take for example JSON: I can think of a thousand ways to create some JSON output from my global data. To get them all in the bag, you would start introducing phantom types and what not, and implement toJSON on those. Really nothing gained at this point, it's only losing some readability at the use site. Given some run-time introspection or metaprogramming on the other hand, a lot of flexibility can be won back. Here is an example of my preferred way to do it (metaproramming) https://github.com/jstimpfle/python-wsl/blob/master/wsl/parse.py#L395 https://github.com/jstimpfle/python-wsl/blob/master/wsl/pars... Just spend 5 lines to get the data in shape for the task at hand. The original function dict, having a specific layout convention, and containing functionality that is just not needed here, is specified here (informally) https://github.com/jstimpfle/python-wsl/blob/master/wsl/domain.py#L39 https://github.com/jstimpfle/python-wsl/blob/master/wsl/doma... So 5 lines and these two locations are completely decoupled. Easy.
- spion 10y agoIts not like its impossible to model metaprogramming with types. TypeScript recently brought a new level of expressiveness and power into the mainstream with mapped types. Example: https://goo.gl/LphqrJ https://goo.gl/LphqrJ Its possible to map over record fields of different types to transform the inner type of a field. Similarly, if I understood it correctly, for your example that would mean you could map an object where every field is of a different but related type Domain<T[Key]> into an object of the same shape containing WsllexFunc<T[Key]> or Decoder<T[Key]>. Furthermore, you don't have to use Domain<T[Key]> - you can define a more restricted interface that only requires wsllex and decode. Then the extra functionality defined in the Domain<T[Key]> is no longer required, nor mentioned. Similarly the types WsllexFunc<X> and Decoder<X> are simple function types, and any function types with the same shape (arguments and return type) would be accepted regardless of their name. Recursively the arguments and return type are themselves structural, and so on (!) IMO its incredible how structural type systems change the game completely and we've yet to realise the full implications of this for dynamic languages. My guess is that this is why Guido is excited about types.
- jstimpfle 10y agoThank you for taking the time (and referring to the point I made instead of criticizing the code, which is experimental and a little crappy in places). Looks interesting and I will keep an eye on it. But am I right guessing that the TypeScript you linked would not work if the schema is only read at runtime?
- spion 10y agoYeah, unfortunately. For something like that, code generation of at least the initial record types would be required for different schemas at before-compile-time. Then they can be transformed further by the type level language. Another option is to model the type as a dictionary of items, each item being a union of the possible types. There is a tradeoff here, and I believe that if the rest of the code statically assumes a concrete schema is in place, code generation + record types is the better choice. Otherwise, dictionaries would probably work okay. There are languages such as Idris and F# that have type providers which can be programmed to read and generate the types from the database, but AFAIK that would still happen at compile-time. https://docs.microsoft.com/en-us/dotnet/articles/fsharp/tutorials/type-providers/accessing-a-sql-database https://docs.microsoft.com/en-us/dotnet/articles/fsharp/tuto... - i guess its a kind of "built in" code generation.