[Haskell-cafe] Client-extensible heterogeneous types (Duck-typed variadic functions?)

Evan Laforge qdunkan at gmail.com
Wed Oct 13 19:32:59 EDT 2010


> I think I'm starting too see what my problem is. I think it boils down to
> hankering for Duck Typing and variadic functions. I fully appreciate that
> passing functions is a wonderful and powerful technique for catering for
> variation, but Haskell's type system cramps my style by insisting that I
> can't put a (Banana -> Cake) in the same container as an (Octopus ->
> Truffles -> DogsBreakfast).

But the thing is, I don't use things like this, even in python.  How
are you expecting to call the functions in that container?  "for f in
c: try: return f(*misc_args) except: pass"?

> -- Imagine that I want to write a program which will help me practice
> -- basic arithmetic.
>
> -- At its core I might have the following three functions
>
> ask :: (Int, Int) -> String
> ask (a,b) = show a ++ " + " ++ show b
>
> answer :: (Int, Int) -> Int
> answer (a,b) = a + b
>
> check :: (Int, Int) -> String -> Bool
> check  q ans = (read ans :: Int) == answer q
>
> -- which present the question, and check whether a given answer is
> -- correct.
>
> -- Now, imagine I've got addition down pat, and want to extend my
> -- repertoire to subtraction. I could introduce some flexibility into
> -- my core functions thus
>
> data Operation = Operation (Int -> Int -> Int) String
>
> ask' :: (Int, Int) -> Operation -> String
> ask'    (a,b) (Operation _ sym) = show a ++ " " ++ sym ++ " " ++ show b
>
> answer' :: (Int, Int) -> Operation -> Int
> answer' (a,b) (Operation op _)  = op a b
>
> check' :: (Int, Int) -> Operation -> String -> Bool
> check' q op ans = (read ans :: Int) == answer' q op
>
> -- Now my program can deal with any binary infix operations on
> -- Ints. But what if I now want to practice a unary operation
> -- (e.g. sqrt)? How about a binary prefix one (e.g. gdc) ?
>
> -- Maybe this is the way forward?
>
> data Question =
>    BinaryInfix  (Int -> Int -> Int) String Int Int |
>    BinaryPrefix (Int -> Int -> Int) String Int Int |
>    UnaryPrefix  (Int -> Int)        String Int

Well, you're creating a little interpreter here.  I agree with you
that this is sometimes easier in a dynamic language because you can
reuse the implementation language at runtime.  In the extreme, in
python, you can simply call eval() on the input string.  I believe
there are some packages on hackage that implement little languages
that you might be able to reuse.

But if you don't need a full-on language, one easy step is to wrap
your haskell functions in a typechecker:

apply1 f [x] = f x
apply1 _ _ = throw hissy fit
apply2 f [x, y] = f x y
etc.

Now you can put them all into one container.  Yes, the family of apply
functions may be a little tedious, and you may be able to use
typeclass magic to automatically select the right apply function, but
it doesn't seem like a big deal to me.  If you want to extend this to
different types, you just have to extend this in one more direction,
and a typeclass definitely helps there.

> -- I'm a little annoyed by the repetitive tedium of answer'': this
> -- will really wind me up when I get on to TernaryPrefix,
> -- QuaternaryPrefix etc. and I will hanker for something like Python's
> -- *args.
>
> -- Now, I go to a party and thoroughly impress my friends with my
> -- newly-acquired arithmetic wizardry. One thing leads to another and
> -- my program ends up in the hands of another soul or two, desperate
> -- to match my mental calculation powers: I acquire some users. And as
> -- every schoolboy knows, users are closely followed by feature
> -- requests.
>
> -- John wants to practice adding fractions. Cindy needs to learn to
> -- find all prime factors of a given number.
>
> -- Clearly
> --
> --      check'' q a = (read a :: Int) == answer'' q
> --
> -- won't cut the mustard any more.
>
> -- Now, I can't see any obvious reason why I can't just keep adding
> -- new constructors to Question, and corresponding patterns to ask,
> -- answer and check, but I'm a lazy bugger and want to palm this off
> -- onto the users by telling them that I am empowering them by giving
> -- them the ability to add new question types to the framework.
>
> -- How would I enable them to do this without them having to mess with
> -- the original source?

Well, I guess you could find the bits of the question framework which
are always the same regardless of how its extended, then think about
what types those have.  Then export that as a library so your users
can put together their own program based on that.  For example, if you
always have a number of wrong answers and a number of right answers
and print a scoreboard, then you have 'Int -> Int -> Scoreboard'.  If
the answers the users are expected to give vary (a single int, or a
list of ints, or a string), then you can export some parsing
primitives.  Eventually, some invisible line is crossed and you have
an EDSL for writing math tests.  Your Question type could look like
'String -> Answer' and Answer = 'Wrong String | Right | ParseError
String'.


More information about the Haskell-Cafe mailing list