5

My goal is to pipe some steps for ghci to run from a bash script and then exit cleanly. The commentary online says to use runhaskell for this.

This is the command I'm trying to run:

ghci> import System.Random 

ghci> random (mkStdGen 100) :: (Int, StdGen) 

With expected result similar to:

(-3633736515773289454,693699796 2103410263)

When I drop this into a file randomtest.hs and execute it with runhaskell I get the following error:.

randomtest.hs:3:1: error:
    Invalid type signature: random (mkStdGen 100) :: ...
    Should be of form <variable> :: <type>

It seems I can't use the runhaskell method to blindly execute ghci inputs.

Now the way to work around this is to add extra commands to the file that is passed to runhaskell:

main = do print (random (mkStdGen 100) :: (Int, StdGen))

My goal is to automate the running of ghci work for a haskell course I'm using. I want to be able to run the ghci command from a bash script - in the format that ghci expects, and have it cleanly exit from ghci (or whatever runs it).

My question is: Is there a way to script a ghci session?

hawkeye
  • 34,745
  • 30
  • 150
  • 304
  • 3
    Have you tried just piping the script into GHCi via the shell? E.g.: `ghci < filename`. If you just want to run some commands and then exit, that should work fine. – Jon Purdy Dec 10 '17 at 04:04
  • Thanks @JonPurdy - I tried that - and got the output, but in a long sequence of commands, without seeing the input it was difficult to see where you were up to - and sometimes excluded newlines. This would work in a pinch, but I'm open to something better. – hawkeye Dec 11 '17 at 00:25

2 Answers2

6

You'll want to use expect for that, it allows you to interactively control a REPL with simple commands. This script does what you want:

#!/usr/bin/env expect

log_user 0
spawn ghci
log_user 1

expect ".*> "
send ":set prompt \"ghci> \"\n"

expect "ghci> "
send "import System.Random\n"
expect "ghci> "
send "random (mkStdGen 100) :: (Int, StdGen)\n"

interact

Running this gives you the following:

$ ./ghci-interactive
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> :set prompt "ghci> "
ghci> import System.Random
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-3633736515773289454,693699796 2103410263)
ghci> 

Note: You might need to adjust this a bit to be resistant to users setting the prompt in ~/.ghci.

Silvan Mosberger
  • 1,786
  • 14
  • 19
0

Thanks @SilvanMosberger - you solved the issue so I'll leave yours as correct.

I wanted to generalise this approach so it could load instructions specified in a file - eg RandomPair.ghci with the following contents:

import System.Random 

random (mkStdGen 100) :: (Int, StdGen)  
random (mkStdGen 949488) :: (Float, StdGen)  
random (mkStdGen 949488) :: (Bool, StdGen)  
random (mkStdGen 949488) :: (Integer, StdGen) 

Now a script to run this looks like the file RandomPair-ghci.bash

#!/usr/bin/env expect

log_user 0
spawn ghci
log_user 1

expect ".*> "
send ":set prompt \"ghci> \"\n"

set f [open "RandomPair.ghci"]
set replcommands [split [read $f] "\n"]
close $f

foreach replcommand $replcommands {
    set replcommand1 [string map {"\"" "\\\""} $replcommand] 

    expect "ghci> "
    send -- "$replcommand1 \n"
}

expect "ghci> "
send ":quit\n"

interact

This gives a result similar to:

$ ./RandomPair-ghci.bash 
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> :set prompt "ghci> "
ghci> import System.Random  
ghci>  
ghci>  
ghci> random (mkStdGen 100) :: (Int, StdGen)   
(-3633736515773289454,693699796 2103410263)
ghci> random (mkStdGen 949488) :: (Float, StdGen)   
(0.3718226,1597344447 1655838864)
ghci> random (mkStdGen 949488) :: (Bool, StdGen)   
(False,1485632275 40692)
ghci> random (mkStdGen 949488) :: (Integer, StdGen)  
(9159618695640234475,587416689 2103410263)
ghci>  
ghci>  
ghci> :quit
Leaving GHCi.
$
hawkeye
  • 34,745
  • 30
  • 150
  • 304