
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.] 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 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 :)
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! :-)
Well, yes, that's the extreme. My point was that when you call f(*args) you are also reusing the interpreter at runtime. The fact that values carry their types around at runtime is one example of this. 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. But keep in mind you are implementing an interpreter. BTW, I've used eval(s, {}) on a number of occasions when I wanted to parse ints or strings and didn't want to write my own parser. It doesn't seem that much different from 'read' in haskell.
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, 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.
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.)
I meant to use typeclasses to make it easier to create a typechecking function, in the same way that 'apply[n]' creates a function that checks number of args. Then you can write 'apply1 f [x] = f (parse x)' and 'parse' will be 'parse_string' or 'parse_int' depending on the value 'f' expects. The end result is still a function '[String] -> Either ParseError Answer' and so can all go into the same container. You may even be able to overload 'apply' so it will dispatch on applyn depending on the arity. Then you just write [apply f, apply g, apply h] where 'f', 'g', and 'h' can all have different types. The bottom line is that you have to parse and typecheck the strings typed by the user at some point. In the python case, you reuse python's typechecker when you write f(*args). In the haskell case you have to check the types yourself, but typeclasses can probably make it pretty painless. If you want to reuse haskell's typechecker, then you can install hint and just eval the string directly. If you want to do something in between like python's f(*args)... well, I'm not aware of libraries that do that. You would have to construct an AST, unparse that to haskell code, and give that to hint. Sounds like a bother.
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, so maybe you could provide a bit of python or something that does what you want and would be harder with static types?
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.