Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to found most of the errors in programs written in Haskell by adding a new feature, something I call it "subtype system", to Haskell?

I have learned Haskell for about one year, and came up with a question that could the talented compiler writers add a new feature called "subset" by me to enhance the Haskell's type system to catch many errors including IOExceptions in compiling stage. I'm novice of Theory of types, and forgive my wishful thinking.

My initial purpose is not how to solve the problem but to know whether there exists a related solution but because of some reasons the solution is not introduced to Haskell.

Haskell is nearly perfect in my mind except for some little things, and I will express my wish to Haskell of the future in the following lines.

The following is the major one:

If we can define a type, which is just a "subset" of Int assuming Haskell allows us to do that, like bellow:

data IntNotZero = Int {except `0`} -- certainly it is not legal in Haskell, but I just assume that Haskell allows us to define a type as a "subset" of an already existing type. I'm novice of Theory of types, and forgive me.

And If a function needs a parameter of Int, a variable of IntNotZero, which is just a "subset" of Int, can also be a parameter of the function. But, If a function needs a IntNotZero, then a Int is illegal.

For example:

div' :: Int -> IntNotZero -> Int
div' = div

aFunction :: Int -> Int -> Int --If we casually write it, then the compiler will complain for type conflict.
aFunction = div'

aFunction2 :: Int -> Int -> Int --we have to distinguish between `Int` and `IntNotZero`.
aFunction2 m n = type n of --An assumed grammar like `case ... of` to separate "subset" from its complement. `case ...of` only works on different patterns.
                   IntNotZero -> m `div` n
                   otherwise  -> m + n

For a more useful example:

data HandleNotClosed = Handle {not closed} --this type infers a Handle not closed

hGetContents' :: HandleNotClosed -> IO String --this function needs a HandleNotClosed and a Handle will make a type conflict.
hGetContents' = hGetContents

wrongMain = do
         ...
         h <- openFile "~/xxx/aa" ReadMode
         ... -- we do many tasks with h and we may casually closed h
         contents <- hGetContents' h --this will raise a type conflict, because h has type of Handle not HandleNotClosed.
         ...

rightMain = do
         ...
         h <- openFile "~/xxx/aa" ReadMode
         ... -- we do many tasks with h and we may casually closed h
         type h of -- the new grammar.
              HandleNotClosed -> do
                                   contents <- hGetContents' h
                                   ...
              otherwise       -> ...

If we combine ordinary IO with Exception to a new "supset", then we may get free of IOErrors.

like image 874
TorosFanny Avatar asked Jun 23 '13 07:06

TorosFanny


People also ask

What do you think about Haskell’s compile errors?

As part of working with the language, I’ve had to work with its compile errors. The Haskell compiler gives you errors that are extremely informative—if you know the language. If you don’t know the language very well, the compiler errors can occasionally be opaque and unhelpful.

What do you like most about Haskell?

The Haskell compiler gives you errors that are extremely informative—if you know the language. If you don’t know the language very well, the compiler errors can occasionally be opaque and unhelpful. I’ve very much enjoyed using Haskell, and I figure the best way for me to give back to the community is to make this situation a little better.

What are the most common types of programming errors?

Today, we’re going to talk about the seven most common types of programming errors and how you can avoid them. 1. Syntax Errors Just like human languages, computer languages have grammar rules. But while humans are able to communicate with less-than-perfect grammar, computers can’t ignore mistakes, i.e. syntax errors.

How do I report an error in mydiv2 in Haskell?

Well, we can always use Haskell's Maybe type to represent a computation that might fail: And thanks to the magic of monads, we can actually string together calls to myDiv2 quite nicely: This approach to error-reporting is used by Network.URI.parseURI , which is included with most Haskell compilers.


2 Answers

What you want sounds similar to "refinement types" à la Liquid Haskell. This is an external tool that allows you to "refine" your Haskell types by specifying additional predicates that hold over your types. To check that these hold, you use an SMT solver to verify all the constraints have been satisfied.

The following code snippets are taken from their introductory blog post.

For example, you could write the type that zero is 0:

{-@ zero :: { v : Int | v = 0 } @-}
zero :: Int
zero = 0

You'll notice that the syntax for types looks just like set notation for math--you're defining a new type as a subset of the old on. In this case, you're defining the type of Ints that are equal to 0.

You can use this system to write a safe divide function:

{-@ divide :: Int -> { v : Int | v != 0 } -> Int @-}
divide :: Int -> Int -> Int
divide n 0 = error "Cannot divide by 0."
divide n d = n `div` d

When you actually try to compile this program, Liquid Haskell will see that having 0 as the denominator violates the predicate and so the call to error cannot happen. Moreover, when you try to use divide, it will check that the argument you pass in cannot be 0.

Of course, to make this useful, you have to be able to add information about the postconditions of your functions, not just the preconditions. You can just do this by refining the result type of the function; for example, you can imagine the following type for abs:

{-@ abs :: Int -> { v : Int | 0 <= v } @-}

Now the type system knows that the result of calling abs will never be negative, and it can take advantage of this fact when it needs to verify your program.

Like other people mentioned, using this sort of type system means you will have to have proofs in your code. The advantage of Liquid Haskell is that it uses an SMT solver to generate the proof for you automatically--you just have to write the assertions.

Liquid Haskell is still a research project, and it's limited by what can reasonably be done with an SMT solver. I haven't used it myself, but it looks really awesome and seems to be exactly what you want. One thing I'm not sure about is how it interacts with custom types and IO--something you might want to look into yourself.

like image 101
Tikhon Jelvis Avatar answered Sep 19 '22 06:09

Tikhon Jelvis


The problem is that the compiler can't determine if something is of type IntNotZero. For example:

f :: Int -> IntNotZero
f x = someExtremlyComplexComputation

the compiler would have to prove that someExtremlyComplexComputation doesn't produce a zero result, which is in general impossible.

One way how to approach this is in plain Haskell to create a module that hides the representation of IntNotZero and publishes only a smart constructor such as

module MyMod (IntNotZero(), intNotZero) where

newtype IntNotZero = IntNotZero Int

intNotZero :: Int -> IntNotZero
intNotZero 0 = error "Zero argument"
intNotZero x = IntNotZero x

-- etc

The obvious drawback is that the constraint is checked only at runtime.


There are more complex systems than Haskell that use Dependent types. These are types that depend on values, and they allow you to express just what you want. Unfortunately these systems are rather complex and not much widespread. If you're interested in the subject, I suggest you to read Certified Programming with Dependent Types by Adam Chlipala.

like image 28
Petr Avatar answered Sep 19 '22 06:09

Petr