Reputation: 33
I'm learning Haskell, running through the lectures: http://www.cis.upenn.edu/~cis194/spring13/
I've got:
module HanoiDisk(HanoiDisk, hanoiDisk) where
import Control.Exception
data HanoiDisk = HanoiDisk' Integer deriving (Show)
hanoiDisk :: Integer -> HanoiDisk
hanoiDisk n = assert (n >= 1) $ HanoiDisk' n
This works, but if i have:
main = do
print(show (hanoiDisk (-3))
I only get an error during run-time and not at compile-time.
I'm pretty keen to understand how to eliminate run-time exceptions entirely.
Can anyone provide an alternative approach?
Thanks
Upvotes: 2
Views: 407
Reputation: 503
From what I understand, you want a way to "fail nicely" when someone applies the function hanoiDisk
to an argument that's less than 1.
As a commenter stated, doing that at compile time is outside the scope of basic Haskell and you shouldn't need it in your day-to-day code!
You can definitely "fail nicely" by using the Either a b
datatype.
The idea is that if you have a function hanoiDisk :: Integer -> HanoiDisk
that takes an Integer
and is supposed to return a HanoiDisk
value if the input is "good" and an error value of some sort when the input is "bad", you can encode that using alternate constructors.
The constructors for the Either a b
datatype are Left a
and Right b
where
an error output would be of the form Left a
and a good output would be of the form Right b
. Let's rewrite your function using this.
hanoiDisk :: Integer -> Either String HanoiDisk
hanoiDisk n = if n >= 1
then Right (HanoiDisk' n)
else Left "a hanoi disk must be least 1"
Let's discuss the simpler problem of constructing numbers that must be nonnegative (as opposed to positive) in a way that's acceptable to the compiler.
I think the problem is tied to the way numbers are parsed by the compiler. Any time you use the symbols '0', '1', '2', '3', '4', ..., '9' to represent digits in your program the language parser expects the end result to conform to a type like Int, Double, etc. and so when you use these symbols you open yourself up to the possibility that someone might prepend a '-' to the sequence of digits and turn your nonnegative number into a negative one.
Let's make a new module called Natural which will allow us to create positive numbers. In it, we define "aliases" for the symbols '0',...,'1' using the first two letters of each symbol's name (eg. tw
for '2'). Since humans write natural numbers using the decimal system, we create a data type called Natural that takes two arguments - the first digit of the number we're representing and then a list of subsequent digits. Finally, we selectively export functions from the module to prohibit "misuse" by users.
module Natural (ze,on,tw,th,fo,fi,si,se,ei,ni,Natural(..)) where
newtype Digit = Digit Int
ze = Digit 0
on = Digit 1
tw = Digit 2
th = Digit 3
fo = Digit 4
fi = Digit 5
si = Digit 6
se = Digit 7
ei = Digit 8
ni = Digit 9
data Natural = Nat Digit [Digit]
As an example, the natural number 312 would be represented as Nat th [on,tw]
.
Any module importing Natural would only have access to the functions that we export, so attempts to use anything else to define a value of type Natural
would result in compile errors. Furthermore, since we didn't export the Digit
constructor there's no way for importers to define their own values for the Digit
type.
I'm leaving out definitions of the instances for Num
, Integral
, Eq
, Ord
, etc. because I don't think they would add more to my explanation.
Upvotes: 2
Reputation: 71065
Haskell checks types when compiling a code, not values. To make types depend on values is the job of "dependent types". It is an advanced topic.
The other way to achieve this is to make your hanoiDisk
work not with Integer
s, but with some "PositiveInteger
" type which can not possibly be negative (or 0 as well..?). It is a more basic approach.
There will be nothing to assert -- it should be impossible for you to even write down a negative value with this type. You'll have to make this type an instance of Num
, Eq
, Ord
, and Show
(maybe Enum
as well).
The usual way is to define
data Nat = Z | S Nat
deriving (Eq, Show)
Upvotes: 3