I'm very new to SML and I'm trying to add some items to a list
fun foo(inFile : string, outFile : string) = let
val file = TextIO.openIn inFile
val outStream = TextIO.openOut outFile
val contents = TextIO.inputAll file
val lines = String.tokens (fn c => c = #"\n") contents
val lines' = List.map splitFirstSpace lines
fun helper1(lis : string list) =
case lis of
[] => ( TextIO.closeIn file; TextIO.closeOut outStream)
| c::lis => ( TextIO.output(outStream, c);
helper1(lis))
fun helper(lis : (string * string) list, stack : string list) =
case lis of
[] => stack
| c::lis => ( act(#1 c, #2 c)::stack;
helper(lis, stack))
val x = helper(lines', [])
in
helper1(x)
end;
I'm getting a blank output file whenever I run the code and I'm having trouble figuring out why but I do know that the helper function is getting the proper values from the "act" function because I tested it by using print(action(...))
Thanks
The problem is with this part:
( act(#1 c, #2 c)::stack; helper(lis, stack) )
This is creating a new list and then immediately throwing it away before performing the recursive call. What you want to do instead is
helper(lis, act(#1 c, #2 c)::stack)
Additional hint: both your helper functions can be replaced by simple uses of List.app and List.foldl.
Edit: Further hint: In fact, you can write that as just
helper(lis, act(c)::stack)
because a function with "two arguments" is simply a function taking a pair.
Related
I'am writing this function for a MOOC. It's job is to remove a string from the list and return that list without the string as a SOME or return NONE is the string is not there.
I wrote the code below but whenever I try to run it I get the following error: Error: non-constructor applied to argument in pattern: -.
exception NotFound
fun all_except_option (str : string, strs : string list) =
let
fun remove_str (strs : string list) =
case strs of
[] => raise NotFound
| str'::strs' => if same_string(str, str') then strs' else str'::remove_str strs'
in
SOME (remove_str strs) handle NotFound => NONE
end
And where's one test to run it:
val test01-01 = all_except_option ("string", ["string"]) = SOME []
edit
forgot to include the same_string function that was provided to us to simplify types
fun same_string(s1 : string, s2 : string) =
s1 = s2
Figured out the problem. Seems like SML doesn't like hyphens, like the one I had in the test:
val test01-01 = all_except_option ("string", ["string"]) = SOME []
I changed to underscore instead and now it works.
val test01_01 = all_except_option ("string", ["string"]) = SOME []
Since you've already solved this task, here's a way to write it without using exceptions:
fun all_except_option (_, []) = NONE
| all_except_option (t, s :: ss) =
if s = t
then SOME ss (* don't include s in result, and don't recurse further *)
else case all_except_option (t, ss) of
SOME ss' => SOME (s :: ss')
| NONE => NONE
Having a recursive function return t option rather than t makes it more difficult to deal with, since upon every recursive call, you must inspect if it returned SOME ... or NONE. This can mean a lot of case ... of ... s!
They can be abstracted away using the library function Option.map. The definition is found in the standard library and translates into:
fun (*Option.*)map f opt =
case opt of
SOME v => SOME (f v)
| NONE => NONE
This bit resembles the case ... of ... in all_except_option; rewriting it would look like:
fun all_except_option (_, []) = NONE
| all_except_option (t, s :: ss) =
if s = t
then SOME ss (* don't include s in result, and don't recurse further *)
else Option.map (fn ss' => s :: ss') (all_except_option (t, ss))
I am trying to write the command line arguments from my SML program into a file, each on a separate line. If I were to run sml main.sml a b c easy as 1 2 3 on the command line, the desired output would be to have a file with the contents:
a
b
c
easy
as
1
2
3
However, I am getting the following output from SML:
$ sml main.sml a b c easy as 1 2 3
val filePath = "/Users/Josue/Desktop/espi9890.txt" : string
val args = ["a","b","c","easy","as","1","2","3"] : string list
main.sml:4.21 Error: syntax error: inserting EQUALOP
/usr/local/smlnj/bin/sml: Fatal error -- Uncaught exception Compile with "syntax error" raised at
../compiler/Parse/main/smlfile.sml:15.24-15.46
With this code:
val filePath = "/Users/Josue/Desktop/espi9890.txt";
val args = CommandLine.arguments();
fun writeListToFile x =
val str = hd x ^ "\n";
val fd = TextIO.openAppend filePath;
TextIO.output (fd, str);
TextIO.closeOut fd;
writeListToFile (tl x);
| fun writeListToFile [] =
null;
writeListToFile args;
Am I missing something?
The correct syntax for nested value declarations is:
fun writeListToFile (s::ss) =
let val fd = TextIO.openAppend filePath
val _ = TextIO.output (fd, s ^ "\n")
val _ = TextIO.closeOut fd
in writeListToFile ss end
| writeListToFile [] = ()
That is,
(Error) You're forgetting the let ... in ... end.
(Error) Your second pattern, [], will never match because the first one, x, is more general and matches all input lists (including the empty one). So even if your syntax error was fixed, this function would loop until it crashes because you are trying to take the hd/tl of an empty list.
(Error) When a function has multiple match cases, only the first one must be prepended with fun and the rest must have a | instead. (You can decide freely how to indent this.)
(Error) There are two kinds of semicolons in SML: One is for separating declarations, and one is an operator that discards the value (but not the effect) of its first operand. The first kind that separates declarations can always be avoided. The second kind is the one you are trying to employ in order to chain multiple expressions that each have a desired (file I/O) effect (and is equivalent to having a let-expressions with multiple effectful declarations in a row, like above).
But... at the top-level (e.g. in a function body), SML is unable to tell the difference between the two kinds of semicolons, since they could both occur there. After all, the first kind that we want to avoid marks the ending of the function body while the second kind just marks the end of a sub-expression in the function body.
The way to avoid this ambiguity is to wrap the ; operator where no declarations are allowed, e.g. between in and end, or inside a parenthesis.
(Error) There is no point in having this function return null. You were probably thinking nil (the empty list, aka []), but val null : 'a list -> bool is a function! Really, it is nonsensical to have a return value for this function. If anything, it could be a bool indicating if the lines were written successfully (in which case you probably need to handle IO exceptions). The closest you get to a function that does not return anything is a function that returns the type unit (with the value ()).
(Suggestion) You can use hd/tl to split the list, but you can also use pattern matching. Use pattern matching, like the examples I've given.
(Suggestion) You can use semi-colons instead of the val _ = ... declarations; also; it's just a matter of taste. E.g.:
fun writeListToFile (s::ss) =
let val fd = TextIO.openAppend filePath
in TextIO.output (fd, s ^ "\n")
; TextIO.closeOut fd
; writeListToFile ss
end
| writeListToFile [] = ()
(Suggestion) It is rather silly that every time the function calls itself, it opens the file, appends, and closes the file. Ideally you only open and close the file once:
fun writeListToFile lines =
let val fd = TextIO.openAppend filePath
fun go [] = TextIO.closeOut fd
| go (s::ss) = ( TextIO.output (fd, s ^ "\n") ; go ss )
in go lines end
(Suggestion) Since you are doing the same thing to each element in a list, you may also consider using a higher-order function that generalizes the iteration. Normally, that would be a val map : ('a -> 'b) -> 'a list -> 'b list, but since TextIO.output returns a unit, the very similar val app : ('a -> unit) -> 'a list -> unit is even better:
fun writeListToFile lines =
let val fd = TextIO.openAppend filePath
in List.app (fn s => TextIO.output (fd, s ^ "\n")) lines
; TextIO.closeOut fd
end
(Suggestion) Lastly, you may want to call this function appendListToFile, or simply appendLines, and take filePath as an argument to the function, since filePath implies that it is to a file, and the function does add linebreaks to each s. Names matter.
fun appendLines filePath lines =
let val fd = TextIO.openAppend filePath
in List.app (fn s => TextIO.output (fd, s ^ "\n")) lines
; TextIO.closeOut fd
end
I am trying to create stack in sml, I have tried using list; but I am having trouble to add elements into the list. I'm trying to read lines from the input file, say that if the line says:
push 5
push 9
add
quit
Then I want the output file to be:
14
since 5+9 is 14.
So far, I was able to create boolean functions that recognizes if the line is push or has numeric.
fun is_digit (c) = #"0" <= c andalso c <= #"9";
fun is_Push (c) = String.isSubstring "push" c;
fun stack(inFile : string, outFile : string) =
let
val ins = TextIO.openIn inFile;
val outs = TextIO.openOut outFile;
val readLine = TextIO.inputLine ins;
val it = []: string list;
fun helper(readLine : string option) =
case readLine of
NONE => ( TextIO.closeIn ins; TextIO.closeOut outs)
| SOME(c) => (
if is_Push c
then
let
val number = String.sub(c,5);
val numbChar = Char.toString number;
in
val myList = nil :: numbChar;
TextIO.output(outs, Int.toString(length myList))
end
else
TextIO.output(outs, "aaa\n");
helper(TextIO.inputLine ins))
in
helper(readLine)
end
I would recommend pushing and popping to take place at the front of the list, with the actual pushing and popping implemented by pattern matching, with the (modified) stack passed around as an argument.
Say you have a list of strings which look like e.g.
["push 5", "push 9", "add", "quit"]
and you want to process this string according to the following rules:
1) If the string is of the form "push d" (where d is a single digit) then
push the integer value of d onto the stack
2) If the string is of the form "add" then pop the first two elements off
the stack, add them, and push the sum back on
3) If the string is "quit" then return the top of the stack
In case 3, you actually return a value, in the other cases -- call the processing function on the tail of the list of lines and with an appropriately modified stack. Something like:
fun process ([], stack) = 0
| process ("quit"::lines, i::stack) = i
| process ("add"::lines, i::j::stack) = process(lines,(i+j)::stack)
| process (s::lines, stack) =
let
val d = String.sub(s,5)
val i = Char.ord d - Char.ord(#"0")
in
process(lines,i::stack)
end;
I threw in a basis case of returning 0 on an empty list of lines, but provided no real error checking. In particular -- it will crash with a run time error if "add" is encountered when the stack has fewer than 2 elements, and "quit" will cause a crash if called with an empty stack.
To use this, call it with the list of lines and an empty stack:
- process (["push 5", "push 9", "add", "quit"],[]);
val it = 14 : int
Use # to add to a list. E.g. it = it # [number];
By the way, I'd suggest you rename c as s or line because c is normally used for single characters, not strings of characters. It's confusing to a human reader of your program.
I would split the problem into separate concerns; in particular, separate the functions that perform I/O from pure functions. This makes them more testable and easier to compose later on.
Define an abstract representation of stack commands and convert your file into this abstract representation. Define an exception type for handling incorrect stack commands. This means that your data representation is sanitized.
datatype StackCommand = Push of int
| Pop
| Add
| Quit
exception InvalidCommand of string * string list
Create reusable helper functions for reading lines from files.
fun isLinebreak c = c = #"\n"
fun inputLines filename =
let val fd = TextIO.openIn filename
val content = TextIO.inputAll fd
val _ = TextIO.closeIn fd
in String.tokens isLinebreak content
end
Separate the concern of parsing a single command into one function, and make it so that it requires the right number of arguments for the right command.
fun parseCommand line =
case String.tokens Char.isSpace line of
["push", s] => (case Int.fromString s of
SOME i => Push i
| NONE => raise InvalidCommand (push, s))
| ["pop"] => Pop
| ["add"] => Add
| ["quit"] => Quit
| (cmd::args) => raise InvalidCommand (cmd, args)
val parseCommands = map parseCommand
val inputCommands = parseCommands o inputLines
Define your stack as a list of integers. Given a stack, evaluate a list of commands by iterating this list and update stack according to the command.
type stack = int list
exception StackError of stack * StackCommand
fun evalCommand stack (Push i) = i::stack
| evalCommand (_::stack) Pop = stack
| evalCommand (i::j::stack) Add = i+j::stack
| evalCommand stack Quit = stack
| evalCommand stack cmd = raise StackError (stack, cmd)
fun evalCommands stack [] = stack
| evalCommands stack (Quit::_) = stack
| evalCommands stack (cmd::cmds) =
evalCommands (evalCommand stack cmd) cmds
You can then use either evalCommands on the return value of inputCommands, or you can use evalCommand in a REPL that reads interactively from standard input.
I have a character list [#"h", #"i", #" ", #"h", #"i"] which I want to get the first word from this (the first character sequence before each space).
I've written a function which gives me this warning:
stdIn:13.1-13.42 Warning: type vars not generalized because of value
restriction are instantiated to dummy types (X1,X2,...)
Here is my code:
fun next [] = ([], [])
| next (hd::tl) = if(not(ord(hd) >= 97 andalso ord(hd) <= 122)) then ([], (hd::tl))
else
let
fun getword [] = [] | getword (hd::tl) = if(ord(hd) >= 97 andalso ord(hd) <= 122) then [hd]#getword tl else [];
in
next (getword (hd::tl))
end;
EDIT:
Expected input and output
next [#"h", #"i", #" ", #"h", #"i"] => ([#"h", #"i"], [#" ", #"h", #"i"])
Can anybody help me with this solution? Thanks!
This functionality already exists within the standard library:
val nexts = String.tokens Char.isSpace
val nexts_test = nexts "hi hi hi" = ["hi", "hi", "hi"]
But if you were to build such a function anyway, it seems that you return ([], []) sometimes and a single list at other times. Normally in a recursive function, you can build the result by doing e.g. c :: recursive_f cs, but this is assuming your function returns a single list. If, instead, it returns a tuple, you suddenly have to unpack this tuple using e.g. pattern matching in a let-expression:
let val (x, y) = recursive_f cs
in (c :: x, y + ...) end
Or you could use an extra argument inside a helper function (since the extra argument would change the type of the function) to store the word you're extracting, instead. A consequence of doing that is that you end up with the word in reverse and have to reverse it back when you're done recursing.
fun isLegal c = ord c >= 97 andalso ord c <= 122 (* Only lowercase ASCII letters *)
(* But why not use one of the following:
fun isLegal c = Char.isAlpha c
fun isLegal c = not (Char.isSpace c) *)
fun next input =
let fun extract (c::cs) word =
if isLegal c
then extract cs (c::word)
else (rev word, c::cs)
| extract [] word = (rev word, [])
in extract input [] end
val next_test_1 =
let val (w, r) = next (explode "hello world")
in (implode w, implode r) = ("hello", " world")
end
val next_test_2 = next [] = ([], [])
I would like to build a string list by prompting the user for input. My end goal is to be able to parse a string list against a simple hash table using a simple routine.
`let list_find tbl ls =
List.iter (fun x ->
let mbr = if Hashtbl.mem tbl x then "aok" else "not found"
in
Printf.printf "%s %s\n" x mbr) ls ;;`
Building a string list is accomplished with the cons operator ::, but somehow I am not able to get the prompt to generate a string list. A simpe list function returns anything that is put into it as a list:
`let build_strlist x =
let rec aux x = match x with
| [] -> []
| hd :: tl -> hd :: aux tl
in
aux x ;;`
Thus far, I have been able to set the prompt, but building the string list did not go so well. I am inclined to think I should be using Buffer or Scanning.in_channel. This is what I have thus far:
`#load "unix.cma" ;;
let prompt () = Unix.isatty Unix.stdin && Unix.isatty Unix.stdout ;;
let build_strlist () =
let rec loop () =
let eof = ref false in
try
while not !eof do
if prompt () then print_endline "enter input ";
let line = read_line () in
if line = "-1" then eof := true
else
let rec build x = match x with
| [] -> []
| hd :: tl -> hd :: build tl
in
Printf.printf "you've entered %s\n" (List.iter (build line));
done
with End_of_file -> ()
in
loop () ;;`
I am getting an error the keyword "line" has the type string, but an expression was expected of type 'a list. Should I be building the string list using Buffer.create buf and then Buffer.add_string buf prepending [ followed by quotes " another " and a semicolon? This seems to be an overkill. Maybe I should just return a string list and ignore any attempts to "peek at what we have"? Printing will be done after checking the hash table.
I would like to have a prompt routine so that I can use ocaml for scripting and user interaction. I found some ideas on-line which allowed me to write the skeleton above.
I would probably break down the problem in several steps:
get the list of strings
process it (in your example, simply print it back)
1st step can be achieved with a recursive function as follow:
let build_strlist' () =
let rec loop l =
if prompt () then (
print_string "enter input: ";
match read_line () with
"-1" -> l
| s -> loop (s::l)
) else l
in loop [];;
See how that function loops on itself and build up the list l as it goes. As you mentioned in your comment, I dropped the imperative part of your code to keep the functional recursion only. You could have achieved the same by keeping instead the imperative part and leaving out the recursion, but recursion feels more natural to me, and if written correctly, leads to mostly the same machine code.
Once you have the list, simply apply a List.iter to it with the ad hoc printing function as you did in your original function.