Why can't I do this:
import Data.Char
getBool = do
c <- getChar
if c == 't'
then IO True
else IO False
instead of using return
?
IO is a type constructor, not a value constructor. IO True would be a type, not a value (if True was a type).
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
Haskell is a pure language Being pure means that the result of any function call is fully determined by its arguments. Procedural entities like rand() or getchar() in C, which return different results on each call, are simply impossible to write in Haskell.
I'll answer the slightly broader (and more interesting) question. This is because there is, at least from a semantical standpoint, more than one IO constructor. There is more than one "kind" of IO
value. We can think that there is probably one kind of IO
value for printing to the screen, one kind of IO
value for reading from a file, and so on.
We can imagine, for the sake of reasoning, IO being defined as something like
data IO a = ReadFile a
| WriteFile a
| Network a
| StdOut a
| StdIn a
...
| GenericIO a
with one kind of value for every kind of IO
action there is. (However, keep in mind that this is not actually how IO
is implemented. IO
is magic best not toyed with unless you are a compiler hacker.)
Now, the interesting question – why have they made it so that we can't construct these manually? Why have they not exported these constructors, so that we can use them? This leads into a much broader question.
And there are basically two reasons for this – the first one is probably the most obvious one.
If you have access to a constructor, you also have access to a de-constructor that you can do pattern matching on. Think about the Maybe a
type. If I give you a Maybe
value, you can extract whatever is "inside" that Maybe
with pattern matching! It's easy.
getJust :: Maybe a -> a
getJust m = case m of
Just x -> x
Nothing -> error "blowing up!"
Imagine if you could do this with IO
. That would mean IO
would stop being safe. You could just do the same thing inside a pure function.
getIO :: IO a -> a
getIO io = case io of
ReadFile s -> s
_ -> error "not from a file, blowing up!"
This is terrible. If you have access to the IO
constructors, you can create a function that turns an IO
value into a pure value. That sucks.
So that's one good reason to not export the constructors of a data type. If you want to keep some of the data "secret", you have to keep your constructors secret, or otherwise someone can just extract any data they want to with pattern matching.
This reason will be familiar to object-oriented programmers. When you first learn object-oriented programming, you learn that objects have a special method that is invoked when you create a new object. In this method, you can also initialise the values of the fields inside the object, and the best thing is – you can perform sanity checking on these values. You can make sure the values "make sense" and throw an exception if they don't.
Well, you can do sort of the same thing in Haskell. Say you are a company with a few printers, and you want to keep track of how old they are and on which floor in the building they are located. So you write a Haskell program. Your printers can be stored like this:
data Printer = Printer { name :: String
, age :: Int
, floor :: Int
}
Now, your building only has 4 floors, and you don't want to accidentally say you have a printer on floor 14. This can be done by not exporting the Printer
constructor, and instead having a function mkPrinter
which creates a printer for you if all the parameters make sense.
mkPrinter :: String -> Int -> Maybe Printer
mkPrinter name floor =
if floor >= 1 && floor <= 4
then Just (Printer name 0 floor)
else Nothing
If you export this mkPrinter
function instead, you know that nobody can create a printer on a non-existing floor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With