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

Jacek Generowicz jacek.generowicz at cern.ch
Wed Oct 13 20:44:31 EDT 2010


On 2010 Oct 14, at 01:32, Evan Laforge wrote:

>> 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.

Shame. They're damn useful :-)

> How
> are you expecting to call the functions in that container?  "for f in
> c: try: return f(*misc_args) except: pass"?

to_do = [(call, (AuntMabel,)),
          (buy,  ([(12*kg, sugar), (6*bushel, wheat)])),
          (introduce, (Romeo, Juliet))]

for do,it in to_do:
     do(*it)

(Actually, it is far more commonly used in Python in all sorts of  
function wrappers. But the general principle is the same: It's  
somebody else's problem to ensure they give me compatible data, but  
the type system won't grumble about the types being different; it will  
only complain when the result of bringing the types together doesn't  
make sense. All at run-time, of course.)

The thing is, I can arrange for them to be compatible. Python won't be  
able to confirm this statically, but is it too much to ask of Haskell  
to have it figure out (statically) that all of

     (Int -> Bool, Int)
     (Banana -> Apple -> Orange -> Kiwi -> Bool, (Banana, Apple,  
Orange, Kiwi))
     (Bool -> Bool -> Bool, (Bool, Bool))

can be combined to give Bool ?

So, in my maths tester, I'm only ever going to stick together  
compatible versions of ask, answer and check, but in any given set,  
the types of the 3 functions will not be the same as those in any  
other set. At which point Haskell refuses to let me store them in the  
same container. (Without existential types, at least.)

>> 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.

Yes, this can be viewed as an interpreter for maths testing language.

> I agree with you
> that this is sometimes easier in a dynamic language because you can
> reuse the implementation language at runtime.

I don't think I'm looking for that in this case. I'm just asking to be  
allowed to stick both

     (A -> B -> X, (A, B))

and

     (C -> D -> E -> X, (C, D, E))

etc. in the same container, because, frankly, in the context in which  
they are used, they *are* the same.

> In the extreme, in python, you can simply call eval() on the input  
> string.

Aaaargh! No! For the love of all that is good, please! Nooooo! :-)

But seriously, there's enough dynamism, introspection etc. in Python,  
that eval is almost completely avoidable. I've used it once, in a  
situation where faking up a Lisp macro turned out to be an order of  
magnitude simpler than the alternatives. But that's the only time I've  
been tempted.

I find structured objects far easier and safer to manipulate than  
strings.

> 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.

I would hope that the types could be checked statically, as I  
explained above.

> 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.

Except that I now lose the ability to stick them all into the same  
container. (Unless I enable existential quantification.)

>> -- 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.

I'm pretty sure that you could never come up with a sufficiently large  
set of primitives. Even if you could, it seems like far too much work,  
given that the ability to store arbitrary (sets of co-operating)  
functions (which, together, always return the same types) in the same  
container, trivially provides you with full generality.

> Eventually, some invisible line is crossed and you have
> an EDSL for writing math tests.

That is *exactly* where I am heading with this.

> Your Question type could look like
> 'String -> Answer' and Answer = 'Wrong String | Right | ParseError
> String'.

Actually, I think it should be more like:

Answer = ParseError String | Wrong String |  
NeitherWrongNorRightSoTryAgain String | Right

where the fourth (erm, third) option would be used in situations such  
as: If I ask you for 50/100 and you reply 25/50, it's not wrong, but  
I'm not going to give you your cigar until you tell me that it's 1/2.



More information about the Haskell-Cafe mailing list