ocaml-jupyter icon indicating copy to clipboard operation
ocaml-jupyter copied to clipboard

No standard input/output

Open laowantong opened this issue 3 years ago • 16 comments

No printing occurs with print_string, print_int, and so on:

image

I would expect an output like under the OCaml REPL:

image

Is this a known limitation, or am I doing something wrong ? In the first case, is there any workaround?

Thanks!

laowantong avatar Feb 14 '21 13:02 laowantong

Hello there,

I'm not sure if it's a known bug to the authors of ocaml-jupyter, but I did encounter it many times.

Have you tried to also use flush_all() (from Pervasives module, see documentation)? Or Printf.printf

See these two examples: Capture d’écran_2021-02-14_16-16-00 Capture d’écran_2021-02-14_16-18-40

It's quite weird, but print_endline forces a flush of the output.

Naereen avatar Feb 14 '21 15:02 Naereen

Thanks for this neat workaround. So, if I understand correctly:

  • print_endline s immediately prints s.
  • flush_all() prints all the characters of the buffer before the \n character.
image

laowantong avatar Feb 14 '21 15:02 laowantong

@laowantong yes it appears to be the behavior of jupyter-ocaml.

Naereen avatar Feb 14 '21 18:02 Naereen

By chance, do you know such a workaround for read_line() ?

image

laowantong avatar Feb 14 '21 18:02 laowantong

No, I honestly never used interactive inputs from any OCaml programs! Sorry

Naereen avatar Feb 14 '21 18:02 Naereen

No problem, thanks again for your kind answer!

laowantong avatar Feb 14 '21 19:02 laowantong

@Naereen Thank you for your answers. @laowantong read_line cannot be used on jupyter because it's not a console. Please use Jupyter_comm.Stdin.read_line as follows:

スクリーンショット 2021-02-23 10 50 28

https://akabe.github.io/ocaml-jupyter/api/jupyter/Jupyter_comm/Stdin/index.html

akabe avatar Feb 23 '21 01:02 akabe

Could it be possible for OCaml-jupyter kernel to patch such functions, like read_line, which are known to be unsupported functions in Jupyter environment? It would be helpful to at least print a message saying

Warning: read_line is unsupported on Jupyter, please use [solution]

I actually don't know if OCaml-jupyter kernel has the ability to load a custom .ocamlinit file? (see man ocaml)

INITIALIZATION When ocaml(1) is invoked, it will read phrases from an initialization file before giving control to the user. The default file is .ocamlinit in the current directory if it exists, otherwise .ocamlinit in the user's home directory. You can specify a different initialization file by using the -init file option, and disable initialization files by using the -noinit option.

Naereen avatar Feb 23 '21 02:02 Naereen

Could it be possible for OCaml-jupyter kernel to patch such functions, like read_line, which are known to be unsupported functions in Jupyter environment?

I have no idea.

I actually don't know if OCaml-jupyter kernel has the ability to load a custom .ocamlinit file? (see man ocaml)

You can specify a custom .ocamlinit file at $(opam config var share)/jupyter/kernel.json generated by ocaml-jupyter-opam-genspec command.

$ cat $(opam config var share)/jupyter/kernel.json
{
  "display_name": "OCaml default",
  "language": "OCaml",
  "argv": [
    "/bin/sh",
    "-c",
    "eval $(opam config env --switch=default) && /Users/aabe/.opam/default/bin/ocaml-jupyter-kernel \"$@\"",
    "ocaml-jupyter-kernel",
    "-init", "/Users/aabe/.ocamlinit",
    "--merlin", "/Users/aabe/.opam/default/bin/ocamlmerlin",
    "--verbosity", "app",
    "--connection-file", "{connection_file}"
  ]
}

Re-run jupyter kernelspec install [ --user ] --name ocaml-jupyter "$(opam var share)/jupyter" after editing a value for -init option.

akabe avatar Feb 23 '21 02:02 akabe

Awesome! Thank you @akabe for this workaround which works perfectly. I use Jupyter Notebook as an interactive environment in my FP course, and these IO functions are a must for programming games.

laowantong avatar Feb 23 '21 08:02 laowantong

@laowantong, great if that solves the issue for you.

But are you really planning to develop a game from Jupyter notebook? Using this Jupyter_comm.Stdin.read_line for a game will imply that the game cannot be played outside of Jupyter.

Have you had a look at https://github.com/mahsu/MariOCaml ? It's compiled to Javascript using js_of_ocaml (standard tool, it also provided the compiler interpreter at https://betterocaml.ml/editor/ or https://try.ocamlpro.com/. The MariOCaml shows what can be done in pure OCaml when you know the game frontend will be a HTML canvas.

Naereen avatar Feb 23 '21 09:02 Naereen

@Naereen In fact, I do not call directly this function, but define a mutable variable with a default of:

let read = ref read_line;

This works under console. Under Jupyter Notebook, I mutate it:

read := (function () -> Jupyter_comm.Stdin.read_line "")

And for the tests, I use a stream:

read := (let x = Stream.of_list ["-10"; "500"; "10"] in function () -> Stream.next x);

This is a simple text game (dominoes), nothing as fancy as Mario. Nevertheless, thanks for the links !

laowantong avatar Feb 23 '21 15:02 laowantong

Interesting! I don't know if it's possible to dynamically detect if the code is running inside Jupyter or not...

In Python/IPython, it's easy:

import IPython
def is_in_notebook(): return IPython.get_ipython() is not None

If this is possible in OCaml, it could be used to automatically change your read function.

Naereen avatar Feb 23 '21 18:02 Naereen

You can check whether a repl is on jupyter or not, by:

Filename.basename Sys.argv.(0) = "ocaml-jupyter-kernel"

This is not perfect, but I think enough in practical cases. Or you give the following .ocamlinit file (different from one for a standard repl):

module Sys = struct
  include Sys
  let read () = () [@@ocaml.deprecated "Use Jupyter_comm.Stdin.read_line instead"]
end

I don't recommend replace Sys.read with Jupyter_comm.Stdin.read_line because they have different types.

akabe avatar Mar 07 '21 03:03 akabe

Oh great for the hack on Filename.basename, that's smart!

For the [@@ocaml.deprecated "..."] hack, I saw that in recent versions of the OCaml codebase, but I guess it doesn't work on older versions, I'm under 4.05.0 here and it does nothing special...

Naereen avatar Mar 07 '21 09:03 Naereen

To be able to #use "topfind", I had to first do let () = Topdirs.dir_directory (Sys.getenv "OCAML_TOPLEVEL_PATH")

My OCAML_TOPLEVEL_PATH is /home/terence/.opam/default/lib/toplevel.

I see the kernel.json exports this env by running (opam config env --switch=default --shell=sh).

Is it normal I still need to use the Topdirs.dir_directory function?

terencode avatar May 01 '21 13:05 terencode