tags:

views:

187

answers:

3

Sorry if the question is very elementary, I am still very new to Haskell. Lets say I have a function that can only work with two numbers that are in the golden ration (1.618), how do I define the types of myfun x y to take only golden ratio numbers. What happens if I invoke myfun without golden ratio numbers from within my program (a compile error?)? What happens if the call without golden ratio numbers is made at runtime via user input?

+2  A: 

The best you can do practically is a run-time check. There could be some type-level calculus I don't know (see luqui's comment), but that's not pratical in Haskell.

You could use an assert, which is what you want to replace,

checker :: a -> b -> Bool
checker x y = x * 1.618 `approxEqual` y

unsafeMyfun :: a -> b -> c
unsafeMyfun x y = assert (checker x y) (doRealThingWith a b)

or return a Maybe a (or Either err a) to avoid exceptions which cannot be caught in pure functions,

myfun :: a -> b -> Just c
myfun x y = do
              guard $ checker x y
              return $ doRealThingWith x y

or use a custom contract type as in Tom's answer etc. In any way, it's not possible to check the constraint in compile time. In fact, due to the IO monad, any compile-time constraint cannot be precise.

KennyTM
No - he can do much better by using the type system to ensure all aledgedly golden numbers are checked before being fed to `myfun`.
TomMD
Also it's better to never compare two floating-point numbers for exact equivalence.
EFraim
@EFraim: As commented by the OP, "Don't get too caught up in a bad example. If it helps imagine x and y are a message and salted hash...", so let's not focus on it.
KennyTM
@KennyTM: I agree we should not focus on it too much, but comparing floating-point numbers the right way is not asking much.
EFraim
@EFraim: Yeah so I changed to `approxEqual` already.
KennyTM
I take it from the downvote that some people disagree with this but it's true, and as Kenny said, the IO monad proves it: `inputA <- getLine; inputB <- getLine; let [numA, numB] = map read [inputA, inputB] :: [Double]; magicRatioFunction numA numB`
Chuck
I eliminated my downvote now that `error` is no longer a necessary part of this answer (ment to mention that in the first comment - obviously forgot). Perfectly reasonable answer and good distinction between what components get compile time guarantees and that the program as a whole still needs runtime checks.
TomMD
+9  A: 

You might want an ADT that can only be constructed with golden ratio numbers then write myfun to accept that data type.

I've assumed Integer as a base type, but you could use others (ex: Double or Float) or even be polymorphic.

1) Make the ADT

module Golden (Gold, getGold, buildGold) where

data Gold = G Integer Integer

getGold :: Gold -> (Integer, Integer)
getGold (G x y) = (x, y)

buildGold :: Integer -> Integer -> Maybe Gold
buildGold x y
    | isGolden x y = Just (G x y)
    | otherwise    = Nothing

Notice this module exports the Gold type but not the constructor (namely, not G). So the only way to get a value of type Gold is with buildGold which performs a run-time check - but only one - so the values of Gold can be used and assumed to be a golden ratio by all the consumers without checking.

2) Use the ADT to build myfun

myfun :: Gold -> ???
myfun g = expr
  where (x, y) = getGold g

Now if you try to call myfun with a non-golden number (a value not of type Gold) then you will get a compile time error.

Recap To build golden numbers buildGold function must be used, which forces the number to be checked.

Notice what gets checked when! You have a compile time guarantee that myfun, and all other functions you want to use with Gold, are always provided golden ratios. The program input (from user, network, or where ever) still needs runtime checks and that's what buildGold provides; obviously there's never going to be a program that can promise the human won't type something undesirable.

The alternatives given in the comments to your question are also worthy of consideration. An ADT is slightly heavy weight if all you need is a single function, myfun, that can fail then just have myfun :: (Integer, Integer) -> Maybe ???.

TomMD
Have you actually tried to run it?
KennyTM
You should export getGold and buildGold and you should export Gold without its constructors.
sepp2k
`data Gold = G Double Double` if you want to use a fixed type; you'll never find two `Integer`s that satisfy the golden ratio.
Paul Kuliniewicz
Yes, I shoul.d have exported those - edited. `Gold` was exported without its constructors so unless you want an unnecessarily explicit `()`, I'm not sure what you want.Kenny: Obviously this doesn't run as there are omitted functions for the asker to fill in along with myfun not actually being in a module or importing anything. The concept of ADTs is what's important.
TomMD
@Tom: Assume `myfun g = a + b` and `isGolden x y = x == y`. I'm interested in how to compile-time convert a `Maybe Gold` into a `Gold`.
KennyTM
@KennyTM: There are many ways, the most common ones are function or case expression pattern matching, but the `maybe` function works as does `fromJust`, but that one isn't safe.
TomMD
@TomMD: Never mind, I get your point now. +1. But yours is still not a compile-time check, it's just shifting the responsibility of checking from the `assert` to `buildGold`.
KennyTM
@Kenny: Right, I should have been more clear that `myfun` gets the compile time certainty that its inputs will be golden, but there is no magic that eliminates the need for runtime checking of the input.
TomMD
Dude, I hate to tell you this, but two integers will never be in the golden ratio (1 + 5)/2. It's an irrational number (since 5 is) - literally, not the ratio of any two integers. Other than that, good stuff!
rampion
rampion, Paul: I know two integers won't be exactly a golden ratio and neither will any n bit number (ex: Double or Float). This is why you don't have `a/b == golden` and instead need some sort of approximate equality or boolean test which I called 'isGolden'.
TomMD
+5  A: 

The easiest technique is to use smart constructors, which use a function from Int to GoldenInt, that checks that your values are in the required ratios.

With more effort, you can use type level numbers to ensure that no runtime check is necessary, however, given you're a beginner, I would stick to the smart constructor method.

Tom's answer above is an example of this idiom.

Don Stewart
The type arithmetic and the end of the smart constructors link is very clever (and the first piece of code I have seen since my freshman year in CS that uses Peano numbers). Can the type arithmetic be applied to anything or only a certain "counting class" of problems?
Jason Christa