Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compose functions at runtime based on user input in Haskell

Tags:

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.

like image 299
mentics Avatar asked Jul 27 '11 14:07

mentics


1 Answers

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.

like image 93
Daniel Wagner Avatar answered Sep 28 '22 01:09

Daniel Wagner