Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

multiline haskell/ghc improvement: send input lines between :{ :} #633

wants to merge 2 commits into


None yet
2 participants

yihui commented Oct 28, 2013

Sorry I forgot this PR. I'll double check it once I have got a chance. Could you point me to the reference of the syntax :{ :}? I really do not know much about Haskell. Thanks!

aavogt commented Oct 28, 2013

It is not in the standard, but it is a ghci thing http://www.haskell.org/ghc/docs/latest/html/users_guide/interactive-evaluation.html

Things would work almost as well by not treating :{ :} specially and asking for users to :set +m (described later in that section).


yihui commented Oct 29, 2013

Thanks, but this is probably buggy, e.g. if I have a code chunk


Your method will turn it into system("ghc -e ':{' -e '1+1\n 2+2' -e ':}'"), which does not seem to work:

> system("ghc -e ':{' -e '1+1\n 2+2' -e ':}'")

    No instance for (Num (a1 -> a0)) arising from the literal `1'
    Possible fix: add an instance declaration for (Num (a1 -> a0))
    In the expression: 1
    In the second argument of `(+)', namely `1 2'
    In the first argument of `(+)', namely `1 + 1 2'

whereas the original approach works:

> system("ghc -e '1+1' -e '2+2'")

Any ideas?

aavogt commented Oct 29, 2013

If you turn the statements into an expression, it works with my commit:

do print (1+1)
   print (2+2)

You can do layout/multiline stuff if you :set +m, but system('ghc -e ":set +m" -e "do\n print(1+1)\n print(2+2)\n\n"') somehow doesn't do the same thing as the following ghci session which supports both ways:

Prelude> :set +m
Prelude> do print(1+1)
Prelude|    print(2+2)
Prelude> 1+1
Prelude> 2+2

Another option is to avoid special-casing ghc/haskell at all, so users have to add their :{ :}. :set ... only seems to take effect for the following -e flag, so in that case you would not be able to specify those things outside of something like opts_chunk(engine.opts='-e ":set -XQuasiQuotes" -e "import RlangQQ"'), since each chunk is a separate session for now.

In short, ghc/ghci has limitations. I like the subset that my patch allows (expressions not statements) over what was previously allowed. But it would be nice to support both, but probably that would take too much work.


yihui commented Oct 29, 2013

Got you. Does it solve the problem if we write the code into a file and evaluate it instead of hacking at -e? (Again, I know little about haskell/ghc)

BTW, I hate evaluating code chunks in separate sessions, and I really hope I can solve this problem for some language engines; I experimented with Julia and bash a while ago: https://github.com/yihui/runr Perhaps you can do similar things in Haskell.

aavogt commented Oct 29, 2013

I have a solution as:

> write(':set +m\n1+1\nprint (2+2)\nlet x = 3 in x^x\n\ndo\n x <- return "xx"\n putStrLn x\n', file='tmp') 
> system('ghc -e ":script tmp"')

It should be pretty straightforward to do the same thing as
above in runr, except the :script tmp gets sent to ghci's

next steps

As-is, I wouldn't use knitr with ghc. I have the following proposal:

Probably the above behavior could be engine='ghci'. Something
else could be done for compiled code. Each engine='ghc' chunk
could become a separate module (and file) that gets imported into
the following chunks explicitly (or implicitly). There is a
difference between compiled and interpreted in terms of scoping
and speed. The following hypothetically supported Rmd document
has a bit of redundancy, in the sense that you have to keep
repeating M1 M2 in a couple places.

First we define a module M1. The definition
of x is not exported.

```{r file='M1.hs', engine='ghc'}
module M1 (y) where
x = 1 : y
y = map (+1) x

As an aside, let's load ghci to define a function `w`
```{r engine='ghci'}
let w a = take 5 a
:type w
w x
w y
The last two lines might print out `[1,2,3,4,5]`,
and `[2,3,4,5,6]`. Or maybe `M1` has to be loaded
explicitly. Also if `M1` is loaded as compiled code,
only the `w x` will work.

Can we make sense to add another line to `M1`?
```{r file='M1.hs', engine='ghc'}
0 = 0

Can we merge import and export lists between the different
chunks that correspond to `M1.hs`? This
multiple-chunks-per-module is probably hard to support
properly without using an actual haskell parser (it
is pretty straighforward to merge import and export lists
with haskell-src-exts, but that one can't parse everything
ghc does)
```{r file='M1.hs', engine='ghc'}
module M1 (y2) where
import Data.List (sort)
y2 = sort y

The above three chunks for M1.hs might have to generate a file

    module M1 (y, y2) where
    import Data.List (sort)
    x = 1 : y
    y = map (+1) x
    0 = 0
    y2 = sort y

Now we make another module. It defines
`z` in terms of `y` from `M1` 

```{r file='M2.hs', engine='ghc'}
module M2 where
import M1
z = zipWith (+) y y

Loading these files up into ghci
lets us inspect these values. Hopefully
they have been compiled.
```{r engine='ghci'}
import M1
import M2
w y -- w from the above ghci session?
take 5 y
take 10 z

Loading up M1 as an interpreted file gives
access to the non-exported `x`, of which we
can ask the type
```{r engine='ghci'}
:load *M1
:type x

yihui commented Oct 31, 2013

I see. Thanks for all the explanations and experiments.

I think I tend to use the approach :set +m + :script foo.hs. You can add :set +m to options$code, and write to a temp file, then ghc -e ':script tempfile'. I just did that in a38ed26; does that sound good to you?

We can leave your ghci ideas for future development if you want. I think the idea of modules is interesting.

aavogt commented Oct 31, 2013

Great. I didn't try your commit out, but it sounds like the right thing. I would leave the other things as a TODO for me or someone else.

@yihui yihui added this to the v1.6 milestone Mar 30, 2014

@yihui yihui closed this Mar 30, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment