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

Jacek Generowicz jacek.generowicz at cern.ch
Thu Oct 14 04:34:21 EDT 2010


On 2010 Oct 14, at 09:19, Evan Laforge wrote:

>>> 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)
>
> As has been pointed out, simply write it like this:
>
> to_do = [call AuntMabel, buy [(12kg, sugar), (6 bushel, weat)], etc.]

Which works for this case, but not in general. For example here's the  
memoizer example I used in response to Brandon:

def memoize(fn):
    cache = {}
    def memoized_fn(*args):
        if args not in cache:
            cache[args] = fn(*args)
        return cache[args]
    return memoized_fn

You memoize a function once, but it will be given different arguments,  
many times, at a later time.

But what should the type of fn be? What should the type of args be? In  
Python, I don't care, as long no type error occurs when they are  
combined thus:

    fn(*args)

How do you let Haskell type check the combination of the types, rather  
than the individual types?

My answer seems to be: define a variant type for holding the  
combinations. The problem with this is that the set of allowed  
combinations is closed at library compile time. I want it to remain  
open for extension. In Duck Typing this happens trivially.

> If they are monadic actions, you can call 'sequence_' on them when you
> want them to "happen".  If not, you really just have a list.
>
>> 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 ?
>
> Yes, this sounds like an existential:
>
> data Boolable forall a. = Boolable (a -> Bool)
>
> But despite the fact that I've been keeping them in the back of my
> mind for years, I've never once come up with a place where one would
> actually be useful.  I guess I just don't think that way.

I think that Haskell allows so many completely different approaches to  
things, that serious Haskell programmers are essentially using  
completely different languages which share a small common core :-)

>>> 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.
>
> Maybe you should focus less on the particular implementation you want
> and more on the end result?  If you start off saying "I want
> heterogenous lists" then you'll start off with a problem for haskell
> :)

Of course. Please don't get the impression that I'm trying to fit  
things into my box and won't accept anything else. I'm here to learn.  
In the process of explaining what I mean in some particular case, I  
end up using language from which says that "I want this", but that  
only refers to the exploration of one particular approach.

I am open to, and eagerly encourage, completely different suggestions.

> Haskell doesn't have the interpreter around at runtime.  But if
> you know exactly what parts of the interpreter you want, you can
> recover them, i.e. with Dynamic or by using 'hint' in the extreme.

Hint. Hmm. Embedding an interpreter into your code. I can imagine lots  
of interesting uses for this. But I don't think I want/need it in this  
case.

Thanks for pointing it out, though.

>
>>> 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.
>
> They can.  The strings coming in from the user, of course they can't,

Sure, but that's why we have a ParseError constructor in our Question  
type.

> because they're not even known statically.  The 'apply1' function, of
> course, is statically checked in that 'f' is required to be a function
> with a single string argument.  Well, of the same type as the list
> passed.

But I feel rather cramped by x and y in apply2 being constrained to  
having the same type.

>
>> 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.
>
> Could you provide a more concrete example?  So far the simple example
> of int accepting functions with different arities is pretty easy to
> implement with a plain list,

Trivial, as long as you combine the components immediately. If you  
need to hold the components separately it becomes trickier.  
Specifically, you need to create a variadic wrapper for holding the  
components, at which point you lose extensibility.

Again, I'm sure this isn't the only way, but it's the one that my  
inexperienced mind sees immediately.

> so maybe you could provide a bit of
> python or something that does what you want and would be harder with
> static types?

Is the memoizer show above sufficient? If not I'll try to distil a  
minimal set of conflicting question types in the maths test example.

>>> Eventually, some invisible line is crossed and you have
>>> an EDSL for writing math tests.
>>
>> That is *exactly* where I am heading with this.
>
> Well, good, haskell's supposed to be good at EDSLs :)
>
>>> 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
>
> Well sure, the point is that neither of these types are even
> polymorphic, let alone existential.

Yes, it's completely irrelevant to the meat of the discussion.



More information about the Haskell-Cafe mailing list