[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