Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I use IO constructor

Tags:

haskell

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?

like image 996
Incerteza Avatar asked Sep 30 '13 11:09

Incerteza


People also ask

Is IO a data constructor Haskell?

IO is a type constructor, not a value constructor. IO True would be a type, not a value (if True was a type).

How does the IO Monad work?

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.

How is Haskell IO pure?

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.


1 Answers

Background

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.

Why would you want to not export constructors for a data type?

And there are basically two reasons for this – the first one is probably the most obvious one.

1. Constructors are also deconstructors

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.

2. You don't want to allow any value

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.

like image 114
kqr Avatar answered Oct 01 '22 12:10

kqr