3

I have many files that must be processed automatically. Each file holds the response of one student to an exercise which asks the student to give definitions for some functions given a type for each function.

My idea is to have an Haskell script that loads each student file, and verifies if each function has the expected type.

A constraint is that the student files are not defined as modules.

How can I do this?

My best alternative so far is to spawn a GHCi process that will read stdin from a "test file" with GHCi commands, for example:

:load student1.hs
:t g
... and so on ...

then parse the returned output from GHCi to find the types of the functions in the student file.

Is there another clean way to load an arbitrary Haskell file and introspect its code?

Thanks

mljrg
  • 4,430
  • 2
  • 36
  • 49
  • 3
    1) You can't. Types don't exist at runtime. — 2) that should be possible, I suppose. But in what form would you want that “variable“ to be? A [`TypeRep`](http://hackage.haskell.org/package/base/docs/Data-Typeable-Internal.html#t:TypeRep), or rather just something like `type Bla = {-# TYPEOF f #-}`? – leftaroundabout Oct 16 '15 at 10:02
  • @leftaroundabout See my update in the question's text. – mljrg Oct 16 '15 at 10:07
  • 4
    What do you want this *for*? Maybe there's some other way to achieve your goal. – MathematicalOrchid Oct 16 '15 at 10:10
  • @MathematicalOrchid See my comment to "soupi" below for the reasons I want this. – mljrg Oct 16 '15 at 11:36
  • @mljrg You should probably edit that into the question. Knowing that you're trying to grade exam submissions makes this question a lot more sensible than it otherwise appears. – MathematicalOrchid Oct 16 '15 at 11:46
  • @MathematicalOrchid Did so. What do you mean by "a lot more sensible"? – mljrg Oct 16 '15 at 11:50
  • @mljrg: actually I think if you based your question title on what you wrote in **UPDATE 2**, you'd get more and better attention. – Erik Kaplun Oct 16 '15 at 12:32
  • @mljrg Asking "how can I dynamically check that my code has the right type?" sounds a bit bizarre. If you wrote it, how can you not know it's type? And why isn't manually asking GHCi sufficient? Asking "how can I check that a student's code has the right type" makes much more sense. – MathematicalOrchid Oct 16 '15 at 13:26

5 Answers5

9

Haskell does not save type information at runtime. In Haskell, types are used for pre-runtime type checking at the static analysis phase and are later erased. You can read more about Haskell's type system here.

Is there a reason you want to know the type of a function at runtime? maybe we can help with the problem itself :)

Edit based on your 2nd edit:

I don't have a good solution for you, but here is one idea that might work:

Run a script that for each student module will:

  1. Take the name of the module and produce a file Test.hs:

    module Test where

    import [module-name]

    test :: a -> b -> [(b,a)]
    test = g
  1. run ghc -fno-code Test.hs
  2. check the output does not contain type errors
  3. write results into a log file
soupi
  • 1,013
  • 6
  • 6
  • The reason I want this is to check if a function named `g` in a file has some type. The goal is to automatically score students' responses to an exercise in which they are asked to give definitions for several functions given some type. My idea is to load the students' files and for each function verify if it has the expected type. This must be done automatically because there are many many students. – mljrg Oct 16 '15 at 11:30
  • ...but beware that this only checks that the student's term *can be given* the appropriate type, not that it has *identically that type*. For example, a module filled with `g = undefined` everywhere will pass this test. – Daniel Wagner Oct 16 '15 at 17:35
  • @DanielWagner Right! That's why the best solution I have found is to parse the output of a controlled GHCi session. Unfortunately, it seems that Haskell falls short on metaprogramming capabilities :-( – mljrg Oct 16 '15 at 17:57
5

I think if you have a dynamically determined number of .hs files, which you need to load, parse and introspect, you could/should use the GHC API instead.

See for example:

These might not be something you can use directly — and I haven't done anything like this myself so far either — but these should get you started.

See also:

Community
  • 1
  • 1
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
  • Thanks for the links. I was expecting much better documentation of GHC API, but I suppose this is ongoing work. – mljrg Oct 16 '15 at 18:13
  • I think with the help of `ghc-mod`, `stack-ide`, Leksah or FP Complete, you could possibly find your way through the GHC API with trial and error where the documentation is lacking. And I guess peeking at the source code of GHCJS (and possibly Haste?) and Stack IDE backend and similar projects might help, too. – Erik Kaplun Oct 17 '15 at 09:41
  • Sure, but the effort is huge. Anyway, in this [interesting page](http://www.well-typed.com/blog/2014/04/haskell-gets-static-typing-right/) the author says "Haskell gives you various degrees of selective run-time typing. If you really need it in places, you can explicitly attach run-time type information to values and then make type-based decisions. But you say where and when, making a conscious choice that you gain flexibility at the cost of safety.", which I believe it is what I am looking for. Can you give an example of this, or a link to an example? Thanks – mljrg Oct 17 '15 at 10:26
1

The closest Haskell feature to that is Data.Typeable.typeOf. Here's a GHCi session:

> import Data.Typeable
> typeOf (undefined :: Int -> Char)
Int -> Char
> typeOf (undefined :: Int -> [Char])
Int -> [Char]
> typeOf (undefined :: Int -> Maybe [Char])
Int -> Maybe [Char]
> :t typeOf
typeOf :: Typeable a => a -> TypeRep

Under the hood, the Typeable a constraint forces Haskell to retain some type tags until runtime, so that they can be retrieved by typeOf. Normally, no such tags exist at runtime. The TypeRep type above is the type for such tags.

That being said, having such information is almost never needed in Haskell. If you are using typeOf to implement something, you are likely doing it wrong.

If you are using that to defer type checks to run time, when they could have been performed at compile time, e.g. using a Dynamic-like type for everything, then you are definitely doing it wrong.

chi
  • 111,837
  • 3
  • 133
  • 218
  • I have already done `typeOf g` where `g` has type `a -> b -> [(b, a)]` and it fails. It seems that `typeOf` works only for non-polymorphic types. – mljrg Oct 16 '15 at 11:34
1

If the function is supposed to be exported with a specific name, I think probably the easiest way would be to just write a test script that calls the functions and checks they return the right results. If the test script doesn't compile, the student's submission is incorrect.

The alternative is to use either the GHC API (kinda hard), or play with Template Haskell (simpler, but still not that simple).

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • I cannot do that because I do not know the semantics of the functions, only the type they should have. Also, the GHC API is extremely poorly documented, unfortunately. – mljrg Oct 16 '15 at 17:58
1

Yet another possibility is to load the student's code into GHCi and use the :browse command to dump out everything that's exported. You can then grep for the term you're interested in. That should be quite easy to automate.

There's a catch, however: foo :: x -> x and foo :: a -> a are the same type, even though textually they don't match at all. You might contemplate trying to normalise the variable names, but it's worse: foo :: Int -> Int and foo :: Num x => x -> x don't look remotely the same, yet one type is an instance of the other.

...which I guess means I'm saying that my answer is bad? :-(

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • I don't think it's bad: it's a useful warning. Anyway, I found a [blog page](http://www.well-typed.com/blog/2014/04/haskell-gets-static-typing-right/) where the author says that it is possible to get type information at run-time: "Haskell gives you various degrees of selective run-time typing ...". I wonder if anyone here can give an example. – mljrg Oct 17 '15 at 11:08
  • @mljrg I think they're talking about `Typable` and `Dynamic`. Take a look at `Data.Typable` and `Data.Dynamic`, respectively. As you already discovered, this only works for monomorphic values. – MathematicalOrchid Oct 18 '15 at 10:44