4 ms·
It is only ironic until you realize static typing can often save you from writing code, and not just require more code.
by Peaker 14y ago
It is only ironic until you realize static typing can often save you from writing code, and not just require more code.
- SilasX 14y agoIn general, sure. Doesn't change that the "read" function seems to break the spirit of that.
- Peaker 14y agoHow so? Inferring which implementation of "read" to use based on the types is exactly the same spirit of inferring which implementation of "length" to use. Haskell can maintain consistency in both, but dynamically typed languages must revert to explicitly choosing the implementation in the former.
- SilasX 14y agoYes, it goes nicely with that spirit. But it doesn't go nicely with "have a well-specified return type for all functions that you know in advance", as "read" can return anything. It's great for languages to pick the right implementation of "length" on the fly, but the point is, they all return integers. "read"? Who knows what type you'll get back in Haskell, the "you must specify [or at least plan out the] output type" language? There, it seems to break the trend.
- Peaker 14y agoRead in Haskell returns a well defined type, it just happens to be polymorphic.
- SilasX 14y agoBy that broad a definition of "a well defined polymorphic type", so does every function in every language, at least those that can say "everything's an object/function/data/etc!"
- Peaker 14y agothe type is statically checked, not dynamically checked. Unlike something that just returns an unknown existential "object" type.
- SilasX 14y agoPlease explain more thoroughly and with fewer shortcuts.
- Peaker 14y agoThese two types in Haskell are very similar: show :: Show a => a -> String read :: Read a => String -> a And there's also a law that connects their behavior: read . show = id show . read = id There's nothing special about the position of the polymorphic value, it can be the argument or result. In either case, it is fully statically typed, and you never need to "downcast" it to actually use it. There is complete type safety. Note that this is based on a correction of an OO languages' mistake: it separates the passing of the function table parameter from passing the values. This allows passing a function table for any position in the type, and not just for the first-argument-type. Having something like: Object read(String x) { .. } Is entirely different for two reasons: * The implementation isn't chosen by the type * You will need to eventually convert the "Object" type to the specific type you need. In Haskell, when you write: read :: Read a => String -> a It is actually a shorthand form for: read :: forall a. Read a => String -> a The "forall" is called "universal quantification" and here it is like an implicit parameter "a" (think, template <a> parameter). Caller gets to pass in/choose any "a" they want, and as long as there is a valid Read instance for that type, it will work. However, in you could also (in pseudo-syntax): read :: exists a. Read a => String -> a This is called an "existential quantification", and it is not like an extra parameter "a", but like a tuple: (a, Read a => String -> a). It means: "There exists a type 'a' such that this will be the result type of read". i.e: The caller does not get to choose which type "a" it is, but instead the caller gets a value of an arbitrary, unknown type that happens to have a Read instance. The only way to make any use of a value whose type is existentially quantified like that is to unsafely "cast" it to the type you want, and hope this is the correct type actually returned by "read" here. This is of course nonsense in Haskell, and nobody would ever do that. However, it is very typical OO/Java code. Whenever a Java function returns an Object, it is basically returning an existentially quantified variable, and the caller needs to correctly "cast it down" to the correct type.