mentics
mentics

Reputation: 6999

Compose functions at runtime based on user input in Haskell

Let's say I have 100 functions of various simple signatures (start with monomorphic, but would like to make the polymorphic case work too) a -> b (Int -> Int, Int -> String, String -> Double, MyType -> MyOtherType, etc.)

Let's say I show a list of those to the user. The user selects one. I show a list of functions whose parameter is compatible with the selected function's output. The user selects one.

How could I now compose those two functions? The general case is a series of compositions, but this simple case I think covers the problem I'm working with.

I know I could try unsafeCoerce or Data.Dynamic, but I'm trying to see if I can avoid that, and those apparently are restricted to monomorphic types which is causing a problem.

I thought perhaps somehow I could create a data structure of all the functions and what they could be composed with, but I'm not sure about that. And when including polymorphic cases, it seems like it would be impossible.

Upvotes: 7

Views: 705

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 153172

The question boils down to this: How can I prove to the compiler that this input and that function and what I'm doing with that function's output all line up in a sensible way? There's a couple of answers.

  • Tell the compiler "just trust me". Use unsafeCoerce.
  • Tell the compiler "I'm sort of guessing, but I'm pretty sure I'm right -- so check me, but at runtime, not at compile-time.". Use Data.Dynamic.
  • Do your proof by case analysis on the types a and b in your function's a -> b type. Depending on just how many choices there are for a and b, this can be a bit of a drag.
  • Admit that you're writing an interpreter and type-checker for your own little language, and do it properly. Create a grammar, write a little parser, create an AST, do your type-checking, and evaluate. GADTs can help you leverage GHC's type-checker, reducing the amount of type-checking you have to do (but occasionally increasing the amount of keyboard typing you have to do).

As an example of the third option, suppose you have f :: Int -> String, g :: Double -> Bool, choice1 :: Int, choice2 :: Int, choice3 :: Int, and choice4 :: Double. You could write something like this:

main = prompt "f or g" >>= \ans -> case ans of
    "f" -> prompt "1, 2, or 3" >>= \ans -> case ans of
        "1" -> doSomethingWithString (f choice1)
        "2" -> doSomethingWithString (f choice2)
        "3" -> doSomethingWithString (f choice3)
    "g" -> prompt "4 is your only choice" >>= \ans -> case ans of
        "4" -> doSomethingWithBool (g choice4)

This approach can often be cleaned up a lot -- for example, all of the "f" cases could be collapsed.

Upvotes: 12

pat
pat

Reputation: 12749

I'm not sure what you intend to do with the composed function once it's been created since its arguments and result will have unknown type. I guess you could just keep applying arguments to it until it is no longer a function, but you wouldn't be able to do anything useful with the result unless you constrained it to be a member of some class (like Show).

Why do you want to avoid Data.Dynamic? It seems like this is an inherently dynamic problem. In order to be able to match compatible functions, you will need a way to represent types as data, so Data.Typeable seems appropriate.

This sounds like you want to do at runtime what the Haskell compiler does at compile time. Perhaps you could just dynamically construct the code and evaluate it on the fly with System.Eval.Haskell.

Upvotes: 1

Related Questions