Skip to content
New issue

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

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

file system #37

Open
alkubaig opened this issue Feb 19, 2018 · 17 comments
Open

file system #37

alkubaig opened this issue Feb 19, 2018 · 17 comments

Comments

@alkubaig
Copy link

Hello all!

Thank you in advance for any help!

I was wondering whether or not it is possible to read and write to files in Eff?

@matijapretnar
Copy link
Owner

Hi! No, this is currently not possible, but it would probably be easy to add in external.ml. What kind of operations do you imagine to have?

@alkubaig
Copy link
Author

Read and write!

@matijapretnar
Copy link
Owner

So the signatures would be read : string -> string where the parameter would be the filename, and write : string * string -> string, where the first parameter is the filename and second one is the string? For simplicity, each operation would open the file, perform the IO and close the file?

@alkubaig
Copy link
Author

Perfect! Thank you for your reply. It is absolutely appreciated

So are you saying I can use these read and write functions from external.ml?

is that the right one? https://github.com/ocaml/ocamlbuild/blob/master/testsuite/external.ml

and I am assuming I can write

#use "external.ml";;

in the top of my eff file to import it?

I followed these steps but it did not work, so I am not sure what I am doing wrong

FYI, I have Ocamal installed on my machine

@alkubaig
Copy link
Author

alkubaig commented Feb 20, 2018

I see that external.ml exist in eff/_build/src/runtime
But it gives a parse error when I use it

@alkubaig
Copy link
Author

alkubaig commented Feb 20, 2018

I am guessing I should do something like

external ( < ) : 'a -> 'a -> bool = "<"
external read : string -> string = "read"

@matijapretnar
Copy link
Owner

No, you should take a look at src/runtime/external.ml. But read and write don't exist there yet, someone would need to add them just like print.

@zigaLuksic
Copy link
Collaborator

zigaLuksic commented Feb 23, 2018

You can also add such effects in the same way that #Print was added, in src/runtime/eval.ml.

For instance, for a basic version of #Read_file you need to:

  • add to file pervasives.eff (around line 10 for instance) the effect type effect Read_file : string -> string

  • add to file src/runtime/eval.ml at the bottom in the function top_handle another case for your effect.
    You need to be careful to correctly transform types.

| V.Call ("Read_file", v, k) ->
    let filename = V.to_str v in
    let channel = open_in filename in
    let text = ref "" in
    (
    try
      while true do
        text := !text  ^ (input_line channel) ^ "\n"   (* Gather text into a single string. *)
      done; 
      close_in channel;
      top_handle (k (V.Const (Const.of_string !text)))   (* This is only for typechecking. *)
    with
      End_of_file ->
        close_in channel;
        top_handle (k (V.Const (Const.of_string !text)))
    ) 

seems to work for me. You should adapt the function to your needs (as you can see this adds an
unnecessary "\n" at the end, also gathering all the text in a single string is a bad idea for large files).

Also, don't forget to make eff after you have added the changes.

@alkubaig alkubaig reopened this Feb 23, 2018
@alkubaig
Copy link
Author

Thanks a lot!
I will try it and let you know

@alkubaig
Copy link
Author

alkubaig commented Feb 26, 2018

That worked! Thank you so much
I appreciate your help

I will work now to add write_to_file and also read_line

@alkubaig
Copy link
Author

alkubaig commented Feb 26, 2018

This is my implementation of write_to_file

I added this effect to pervasives.eff

effect Write_file : string * string -> unit

I added these two to src/runtime/value.mli

val first : value -> value
val second : value -> value

and these two src/runtime/value.ml

let first = function
  | Tuple (x::xs) -> x
  | _ -> Error.runtime "first value expected."
  
let second = function
  | Tuple (x::y::xs)-> y
  | _ -> Error.runtime "second value expected."

and added this case to top_handle in src/runtime/eval.ml

| V.Call ("Write_file", v , k) ->
	
     let filename =  V.to_str (V.first v) in
     let text =  V.to_str (V.second v) in
     let channel = open_out filename in
	fprintf channel "%s\n" text;
     top_handle (k V.unit_value)

I can now see why read_line might be complicated because we need to keep track of the file pointer

@matijapretnar
Copy link
Owner

Yes, you'd need a separate constant for file pointers (just like for booleans, strings, …)

@andrejbauer
Copy link
Collaborator

I think this issue presents a very realistic and nice test case for effects: what is the Correct Way of working with files?

@alkubaig
Copy link
Author

alkubaig commented Mar 5, 2018

Alright!
This is working now!!!!

First I added in_channel and out_channel as types

to src/utils/const.ml

type t =
  | In_channel of in_channel
  | Out_channel of out_channel

let of_in_channel n = In_channel n
let of_out_channel n = Out_channel n

let print c ppf =
  | In_channel f -> Format.fprintf ppf ""
  | Out_channel f -> Format.fprintf ppf "" 

to src/utils/const.mli

type t = private
  | In_channel of in_channel
  | Out_channel of out_channel

val of_in_channel : in_channel -> t
val of_out_channel : out_channel -> t

to src/utils/typedSyntax.ml

let const ?loc c =
  | Const.Out_channel _ -> Type.out_channel_ty
  | Const.In_channel _ -> Type.in_channel_ty

to src/utils/tctx.ml

 ("in_channel", (OldUtils.trio_empty, Inline T.in_channel_ty));
 ("out_channel", (OldUtils.trio_empty, Inline T.out_channel_ty));

to src/utils/type.ml

let in_channel_ty = Basic "in_channel"
let out_channel_ty = Basic "out_channel"

to src/runtime/value.mli

val to_out_channel : value -> out_channel
val to_in_channel : value -> in_channel

to src/runtime/value.ml

let to_in_channel = function
   | Const (Const.In_channel b) -> b
   | _ -> Error.runtime "A in_channel value expected."

let to_out_channel = function
 | Const (Const.Out_channel b) -> b
 | _ -> Error.runtime "A out_channel value expected."

** I got a bunch of warnings because pattern matching is not complete in many functions but it compiles

Now I added to src/runtime/eval.ml these cases open_in , open_out, read_line, write_file, close_in and close_out

        (*  effect Open_in : string -> in_channel *)
	| V.Call ("Open_in", v , k) ->

   	let filename =  V.to_str v in
	let channel = open_in filename in
	(try
		top_handle (k (V.Const (Const.of_in_channel channel) ))
	with e ->                   
		close_in channel;
		top_handle (k (V.Const (Const.of_in_channel channel) ))
	)
	(* effect effect Open_out : string -> out_channel *)
	| V.Call ("Open_out", v , k) ->

   	let filename =  V.to_str v in
	let channel = open_out filename in
	(try
		top_handle (k (V.Const (Const.of_out_channel channel) ))
	with e ->                      (* some unexpected exception occurs *)
		close_out channel;
		top_handle (k (V.Const (Const.of_out_channel channel) ))
	)
        (* effect  effect Close_in : in_channel -> unit *)
	| V.Call ("Close_in", v , k) ->

   	let channel =  V.to_in_channel v in
	close_in channel;
        top_handle (k V.unit_value)
         (*  effect Close_out : out_channel -> unit *)
	 | V.Call ("Close_out", v , k) ->

         let channel =  V.to_out_channel v in
         close_out channel;
         top_handle (k V.unit_value)
        (* effect Write_file : (out_channel * string) -> unit *)
	| V.Call ("Write_file", v , k) ->

	let channel = V.to_out_channel (V.first v) in
	let text =  V.to_str (V.second v) in
	(try
		output_string channel text;
		flush channel;
		top_handle (k V.unit_value)
	with End_of_file ->                
		close_out channel;
		top_handle (k V.unit_value)
	)
	(* effect Read_line : in_channel -> string  *) 
	| V.Call ("Read_line", v, k) ->
	  let channel =  V.to_in_channel v in
	  let text = ref "" in
	  ( 
          try
	      text := !text  ^ (input_line channel); 
	      top_handle (k (V.Const (Const.of_string !text))) 
	  with  End_of_file ->
	      close_in channel;
	      top_handle (k (V.Const (Const.of_string !text)))
	  )

Thanks a lot for your help!
It is greatly appreciated
Will keep you updated with any future work on Eff

@andrejbauer
Copy link
Collaborator

  1. Rather than posting a lot of code in the comments, you should fork the repository, commit your code, and then we can look at your commit log. Now we can't even try out what you did, and we cannot easily comment on your code.
  2. Printing channels as empty strings seems wrong.
  3. The warning are there for good reasons, do not ignore them.

@andrejbauer
Copy link
Collaborator

But other than that, great work :-)

@alkubaig
Copy link
Author

alkubaig commented Mar 7, 2018

Thank you : )
I will fork and commit the code. Thank you for pointing that out!
I will also try to eliminate the warnings. I just ignored them because they were out of my scope but I will work on them now to have a clean commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants