5 ms·
Yeah, this is a really great Haskell feature, and it often feels like magic the first time you see it. It's key to a whole lot of useful Haskell idioms. Well-k
by ekidd 9y ago
Yeah, this is a really great Haskell feature, and it often feels like magic the first time you see it. It's key to a whole lot of useful Haskell idioms.
Well-known languages which do this include Haskell and Rust (where it's also useful). In Rust, you can write things like:
// Generate an appropriate default value for a type.
// Here, this will create an empty vector of String.
let my_vec: Vec<String> = Default::default();
// Parse JSON into the specified type.
let numbers: Vec<u32> = serde_json::from_str("[1, 2, 3]")?;
I think there may be ways to do something similar with C++ template metaprogramming and partial specialization, and I wouldn't be surprised if some of the ML family supported something similar using "modules". (But I don't really understand ML modules, so don't quote me on that.)
It's a great feature, and I would love for more strongly-typed languages to support it.
- gpderetta 9y agoIn general you can't do it in C++. There is no way to propagate the result type to the function call itself. The workaround it to return a proxy object with a template conversion operator, which is very effective but it is a leaking abstraction. edit: as long as you are willing to manually specify the result type you can of course have an additional template argument which is not deduced from any function arguments.
- bjz_ 9y agoThis is part of the reason why I still feel constrained when using systems where the type system is bolted on after the fact, like Typescript, Flow, or Dialyzer. You can gain so much leverage from the type system being able to generate code like this for you. ML can do this, but you have to mention the module explicitly. OCaml's upcoming modular implicits will allow this with less boilerplate though.
- dmytrish 9y agoYes, it feels great to just drop `read` into a context with enough type information to infer the return type. On the other hand, when the type context is not sufficient, it gets a bit bulky: -- with sufficient type context: xs <- map read <$> lines <$> getContents -- an ugly inline type signature: xs <- map (read :: String -> Int) <$> lines <$> getContents For many such calls it's good to monomorphize: readInt :: String -> Int readInt = read but it still does not feel elegant (what about other return types?). Edit: obviously, this must have been `xs <-`, not `let xs =`.
- masklinn 9y agoCouldn't you just type `xs` as `[Int]` and let the compiler work itself backwards from there?
- dmytrish 9y agoI don't know how can I actually do this inside a do-block: xs :: [Int] <- ... does not work without `ScopedTypeVariables` (and I don't like proliferation of redundant language extensions). In Rust, sure, it's not a problem and plays well with the language. My point is, it is easy to abuse this feature (this example is from Haskell Prelude!), even though it is indispensable in some situations (like `mempty :: a`).
- marcosdumay 9y agoYou can either type the return value at the end of your line: xs <- map read <$> lines <$> getContents :: IO [Int] Or you can force the xs type on some other line: let _ = xs :: Int There is a recent idiom for that last format that lets you write on the same line as the expression. I think it's something like: (xs @ [Int]) <- map read <$> lines <$> getContents But I haven't used it yet and I am not sure exactly what version of GHC allows it.
- dmytrish 9y agoToday I learned that `:: IO [Int]` at the end of an expression is applied to the whole expression. Thanks!
- thinkpad20 9y agoAlmost all Haskell code written these days uses at least a handful, if not a barrel-full, of extensions, and ScopedTypeVariables is one of the most common ones. Of course, you’re completely free to do whatever you’d like with your own code, but you’ll probably find it hard to avoid extensions — at the least many of them will be used by some libraries you’re depending on.
- 9y ago
- zbentley 9y agoOddly, at the totally other end of the type-system spectrum, the "wantarray" feature of Perl allows you to implement this behavior yourself for a subset (two, in this case) of Perl's main data types* . http://perldoc.perl.org/functions/wantarray.html http://perldoc.perl.org/functions/wantarray.html You have to wire it up yourself, and it happens at runtime (because most everything interesting in Perl does) but your function can alter its return type from a scalar to a vector (list, in Perl parlance. Pearlance? Nevermind) based on the way in which it is invoked. I've seen this used for great evil and great good, but regardless: it's an interesting case in point that such features can be present (albeit in really different forms) and useful even in languages that bear very little resemblance to Haskell/Rust/etc. *contexts, in this case, but it allows for equivalent behavior to return-type polymorphism in a very limited way. Edit: formatting.
- mostlylurks 9y agoIf I've understood this correctly, you can more or less do this in C++ by using voldemort types. Here's a nonsensical but demonstrative example program: #include <string> #include <iostream> auto polyret() { struct proof_of_concept { operator int() { return 4; } operator double() { return 1.5; } operator std::string() { return "I'm a string!"; } }; return proof_of_concept(); } int main() { int a = polyret(); double b = polyret(); std::string c = polyret(); std::cout << "int: " << a << std::endl; //prints 4 std::cout << "double: " << b << std::endl; //prints 1.5 std::cout << "string: " << c << std::endl; //prints "I'm a string!" }