[Haskell-cafe] Client-extensible heterogeneous types (Duck-typed
variadic functions?)
Jacek Generowicz
jacek.generowicz at cern.ch
Thu Oct 14 15:37:16 EDT 2010
Thank you all for your contributions so far. Plenty of food for thought.
I though I'd try to put it into practice and have a go at the
motivating example I gave: essentially a EDSL for defining simple
maths tests.
I've included the beginnings of an attempt at the end. It started
promisingly. As long as I stuck to binary operators over integers,
everything went smoothly, and adding new question types was a joy.
The first annoyance comes when adding the first unary operation into
the set of questions. Then I was forced to duplicate make into make1
and make2: essentially identical functions, differing only in the
number of arguments they take. This sort of copy-paste programming
really annoys me, but I can live with it in this case, as the
duplication will only be in one dimension (operator arity), and
concerns only one function.
But it all goes pear shaped as soon as I try to cater for questions
dealing with fractions, for example: Now the type system requires me
to duplicate all the question-making utilities and give them different
names. I tried to mitigate this by using type classes but got walloped
by the No Monomorphism Restriction, and so on, and so forth. Wherever
I turned, the type system was getting in the way.
Looking at it another way, I have the Question type which can contain
a sufficient variety of questions, but providing a set of utilities
for conveniently populating the type, without excessive code
duplication, is something that I am unable to do with Haskell's type
system getting in the way. But I take this to be my shortcoming rather
than Haskell's, so I would appreciate advice on how to proceed with
this exercise.
Code follows.
Thank you all.
======================================================
import System.IO (hFlush, stdout)
data Result = Correct | Improve String | Huh String | Incorrect String
deriving Show
data Question = Question { ask :: String
, answer :: String
, check :: String -> Result }
bool2result True = Correct
bool2result False = Incorrect ""
-- askers
infix2 sym a b = show a ++ " " ++ sym ++ " " ++ show b
prefix1 sym a = sym ++ " " ++ show a
prefix2 sym a b = sym ++ " " ++ show a ++ " " ++ show b
-- checkers
chk correct given = bool2result $ read given == correct
-- makers
make1 op symbol asker checker a = Question ask (show answer) check where
ask = asker symbol a
answer = op a
check = checker answer
make2 op symbol asker checker a b = Question ask (show answer) check
where
ask = asker symbol a b
answer = op a b
check = checker answer
-- question 'types'
addition = make2 (+) "+" infix2 chk
subtraction = make2 (-) "-" infix2 chk
multiplication = make2 (*) "x" infix2 chk
power = make2 (^) "^" infix2 chk
square = (flip power) 2
cube = (flip power) 3
square' = make1 (^2) "square" prefix1 chk
questions = [ addition 1 2
, subtraction 3 2
, multiplication 4 5
, square 3
, cube 3 ]
test :: [Question] -> IO ()
test [] = return ()
test (q:qs) = do
putStr $ ask q ++ " = "
hFlush stdout
reply <- getLine
putStrLn $ show $ check q reply
test qs
main = test questions
More information about the Haskell-Cafe
mailing list