I am playing with the Elm examples, and I noticed the field example gives Result types. After getting stuck, I came up with this simplified case:
import Html exposing (text)
import String
f: Int -> Int
f x = x + 1
g: Result String Int -> Result String Int
g x = (Result.map f) x
main =
text ( toString ( g (String.toInt 5 ) ))
The result displays OK 6 and I would rather it display just 6 -- I know that toString takes any type and returns a string representaton of it. So maybe I can modify toString
if result is OK then I can print the numerical result
if the result is Err then I would like do some custom error message
Possibly this is the reason for the andThen since the + 1 operation can fail.
andThen : Result e a -> (a -> Result e b) -> Result e b
andThen result callback =
case result of
Ok value -> callback value
Err msg -> Err msg
The definition of andThen is exactly what it does... and is an instance of case.
Either with andThen or plain old case how do I fix my example? Even if I fix it myself, it might not be the most Elm-like solution with good error handling. So I am posting the question.
When a function returns a Result, you have a choice - you can also return a Result, in which case you can return Err(something) or Ok(something). This percolates your errors up to the calling function, which can decide what to do. The other way is you can return something that isn't a result, like a String or Html. If you go this second route, then you need to handle both possibilities of the Result and still return your String or Html.
So for example this function takes a result and returns a string. It handles both possibilities, returning a string even if the result was an Err.
foo: Result String Err -> String
foo myres =
case myres of
Ok(str) -> str
Err(e) -> "there was an error! uh oh"
Its kind of a question of how far up the hierarchy you want to go with your Result. Do you want the errors to percolate all the way up to the top level? Maybe your top level function is like this:
View: Model -> Html
View model =
case makeMyHtml(model) of
Ok(htm) -> htm
Err(e) -> renderSpecialErrorHtmlPage(e)
At any rate, to get rid of the 'Ok' in this case you can do this:
main =
let res = g (String.toInt 5 )
text ( toString ( Result.withDefault "g returned an error!" res))
If g returns Ok(6) then you get "6", but if it returns error you get "g returned an error!".
Related
I call a function in a module to generate unique labels, eg.
MyMod.gensym
defined as
let gensym : string -> string =
let c = ref 0 in
fun s -> incr c; Printf.sprintf "!%s%d" s (!c)
But, I want to be able to get reproducible results at certain times from functions that use this gensym, eg.
let reproducible = SomeMod.call x
may return ["!a1"; "!a2"] the first time and ["!a3"; ...] the second
How can I ensure reproducible output in this case (eg. force ref to start from the same value), but without needing to change the implementation of gensym in its module?
You could add an optional argument to reset it:
let gensym : ?reset:bool -> string -> string =
let c = ref 0 in
fun ?(reset=false) s ->
if reset then
c := 1
else
incr c;
Printf.sprintf "!%s%d" s (!c)
I am trying to make a module that would allow to create a table in ocaml. It would do a query called project to limit the table's values. However on the last line of the definition of the function chooser I am getting syntax error.
module type TABLE =
sig
type database
type table
val create_table: string list * string list* (string list) list -> table
val printTable : table -> string
val listToString : string list -> string
val project : string list * table -> table
val chooser : string list * string list-> string list
end;;
module UsingTable : TABLE =
struct
type table = (string list * string list* (string list) list)
type database = table list
let create_table (a,b,c) = (a,b,c)
let chooser inputList = (
for i = 0 to (List.length trueFalseList-1) do
if List.nth trueFalseList i = "True"
then
(List.nth inputList i)::ans
done
List.rev ans;;)
let project (conditions, aTable)= (
let rec innerProc tmp= function
n,[],v->List.rev tmp
|n,cH::cT,v-> if List.mem cH conditions
then innerProc (["True"]::tmp) (n,cT,v)
else innerProc (["False"]::tmp) (n,cT,v)
in
let trueFalseList = innerProc [] aTable
let rec finalListCreator = match aTable with
n,[],[]->n,[],[]
|n,cH::cT,[]->n,chooser cH ::finalListCreator cT,[]
|n,c,h::t -> n,c,chooser h ::finalListCreator t
)
let rec listToString aList = match aList with
[] -> ""
| h::t -> "\t"^h^"\t"^listToString t
let rec printTable aTable = match aTable with
[],[],[] -> ""
| [],[],vH::vT -> "\n"^(listToString vH)^printTable ([],[],vT)
| [],cH::cT,v -> "\t"^cH^"\t"^printTable([],cT, v)
| n, c , v-> "\n"^(List.hd n)^"\n\n"^printTable([],c, v)
end;;
let atable =UsingTable.create_table (["Student"], ["Id";"Name";"Gender";"Course"],
[["001";"Jim";"M";"AlgoDS"];
["002";"Linnea";"F";"Databases"];
["003";"Anna";"F";"C#"];
["004";"Abby";"F";"C#"];
["005";"Arthur";"M";"JavaScript"]]);;
print_string (UsingTable.printTable atable) ;;
These lines have at least two syntax problems:
let chooser inputList = (
for i = 0 to (List.length trueFalseList-1) do
if List.nth trueFalseList i = "True"
then
(List.nth inputList i)::ans
done
List.rev ans;;)
First, the for .. done is one expression, and List.rev ans is another expression. You need a semicolon (;) between them.
Second, you should use ;; only when you want the input up to that point to be processed. But here if you process the input at the ;; you are missing a right parenthesis.
In my opinion, you should be entering ;; only at the toplevel. The best way to think of this token is as an instruction to the toplevel. It's not part of normal OCaml syntax.
These are only the first two errors. There are quite a few other errors in the code. It might be good to add one function at a time to the module so you can concentrate on a few problems at a time.
Update
The environment you're using is a little bit extra complicated because it has an Evaluate button that asks to evaluate what you've typed so far. This makes the ;; token much less useful.
It would be a good discipline to use this environment without using the ;; token at all. Just click the Evaluate button when you want an evaluation.
The main trick is if you want to evaluate a statement (a unit-valued expression in OCaml) at the outer level, like say Printf.printf "hello world\n". The usual idiom to avoid putting ;; before this is to make it into a declaration like so:
let () = Printf.printf "hello world\n"
That is the one non-obvious idiom that people use when writing source code (where the ;; almost never appears in my experience).
So what I want to do is to convert a string into an int and do some error catching on it. I would also like to know where I would put what I want it to do after it fails if it does.
I know how to convert, but I am not sure how to catch it and where the code will jump to after the error
I believe the method for converting it Int.fromString(x)
Thank you.
SML has two approaches to error handling. One, based on raise to raise errors and handle to catch the error, is somewhat similar to how error handling works in languages like Python or Java. It is effective, but the resulting code tends to lose some of its functional flavor. The other method is based on the notion of options. Since the return type of Int.fromString is
string -> int option
it makes the most sense to use the option-based approach.
An int option is either SOME n, where n is and integer, or it is NONE. The function Int.fromString returns the latter if it fails in its attempt to convert the string to an integer. The function which calls Int.fromString can explicitly test for NONE and use the valOf to extract the value in the case that the return value is of the form SOME n. Alternatively, and somewhat more idiomatically, you can use pattern matching in a case expression. Here is a toy example:
fun squareString s =
case Int.fromString(s) of
SOME n => Int.toString (n * n) |
NONE => s ^ " isn't an integer";
This function has type string -> string. Typical output:
- squareString "4";
val it = "16" : string
- squareString "Bob";
val it = "Bob isn't an integer" : string
Note that the clause which starts NONE => is basically an error handler. If the function that you are defining isn't able to handle such errors, it could pass the buck. For example:
fun squareString s =
case Int.fromString(s) of
SOME n => SOME (Int.toString (n * n))|
NONE => NONE;
This has type string -> string option with output now looking like:
- squareString "4";
val it = SOME "16" : string option
- squareString "Bob";
val it = NONE : string option
This would make it the responsibility of the caller to figure out what to do with the option.
The approach to error handling that John explains is elaborated in the StackOverflow question 'Unpacking' the data in an SML DataType without a case statement. The use-case there is a bit different, since it also involves syntax trees, but the same convenience applies for smaller cases:
fun squareString s = Int.fromString s >>= (fn i => SOME (i*i))
Assuming you defined the >>= operator as:
infix 3 >>=
fun NONE >>= _ = NONE
| (SOME a) >>= f = f a
The drawback of using 'a option for error handling is that you have to take into account, every single time you use a function that has this return type, whether it errored. This is not unreasonable. It's like mandatory null-checking. But it comes at the cost of not being able to easily compose your functions (using e.g. the o operator) and a lot of nested case-ofs:
fun inputSqrt s =
case TextIO.inputLine TextIO.stdIn of
NONE => NONE
| SOME s => case Real.fromString s of
NONE => NONE
| SOME x => SOME (Math.sqrt x) handle Domain => NONE
A workaround is that you can build this constant error handling into your function composition operator, as long as all your functions share the same way of expressing errors, e.g. using 'a option:
fun safeSqrt x = SOME (Math.sqrt x) handle Domain => NONE
fun inputSqrt () =
TextIO.inputLine TextIO.stdIn >>=
(fn s => Real.fromString s >>=
(fn x => safeSqrt x))
Or even shorter by applying Eta conversion:
fun inputSqrt () = TextIO.inputLine TextIO.stdIn >>= Real.fromString >>= safeSqrt
This function could fail either because of a lack of input, or because the input didn't convert to a real, or because it was negative. Naturally, this error handling isn't smart enough to say what the error was, so you might want to extend your functions from using an 'a option to using an ('a, 'b) either:
datatype ('a, 'b) either = Left of 'a | Right of 'b
infix 3 >>=
fun (Left msg) >>= _ = Left msg
| (Right a) >>= f = f a
fun try (SOME x) _ = Right x
| try NONE msg = Left msg
fun inputLine () =
try (TextIO.inputLine TextIO.stdIn) "Could not read from stdIn."
fun realFromString s =
try (Real.fromString s) "Could not derive real from string."
fun safeSqrt x =
try (SOME (Math.sqrt x) handle Domain => NONE) "Square root of negative number"
fun inputSqrt () =
inputLine () >>= realFromString >>= safeSqrt
And trying this out:
- inputSqrt ();
9
> val it = Right 3.0 : (string, real) either
- inputSqrt ();
~42
> val it = Left "Square root of negative number" : (string, real) either
- inputSqrt ();
Hello
> val it = Left "Could not derive real from string." : (string, real) either
- (TextIO.closeIn TextIO.stdIn; inputSqrt ());
> val it = Left "Could not read from stdIn." : (string, real) either
I am trying to convert a string to integer using String.toInt. However, when I want to bind the result to a variable and then do some simple math with it I get this error:
Function add is expecting the 2nd argument to be:
Int
But it is:
Result String Int
How can I just extract the integer part of the result?
Here's how to supply the conversion with a default value in case the parsing fails.
String.toInt "5" |> Result.toMaybe |> Maybe.withDefault 0
toInt can fail in parsing. You need to check it using a case statement:
case toInt str of
Err msg -> ... -- do something with the error message
Ok val -> ... -- val is an Int which you can add
More about Result here
The integer can also be pulled out using
Result.withDefault 0 (String.toInt "2")
You can read more about it here
According to the Elm String reference documentation, if you are extracting a number from some raw user input, you will typically want to use Result.withDefault to handle bad data in case parsing fails. You can chain this operation using pipes for cleaner code:
String.toInt "5" |> Result.withDefault 0
Maybe.withDefault 0 (String.toInt "42")
Use map:
answer = Result.map2 (+) (String.toInt "1") (String.toInt "2")
map2:
Apply a function to two results, if both results are Ok. If not, the
first argument which is an Err will propagate through.
to have the add result as a string
resultAsString r =
case r of
Err msg -> msg
Ok value -> toString value
resultAsString answer
to make things easier you can create an addStrings function:
addStrings : String -> String -> Result String Int
addStrings a b =
Result.map2 (+) (String.toInt a) (String.toInt b)
You can even get away with the Result type altogether:
addStrings : String -> String -> String
addStrings a b =
let
r =
Result.map2 (+) (String.toInt a) (String.toInt b)
in
case r of
Err msg ->
msg
Ok value ->
toString value
Testing
import Html exposing (Html, text)
main : Html msg
main =
text (addStrings "1" "2")
output 3
The withDefault method forces you to define a value that can be used for calculations but it is not always possible to establish a value that is significant for errors. Most often you need all the possible values, and default is not fit. Here I provide a result type check function you can use to decide if you use or not the converted value:
isErrorResult r =
case r of
Err msg ->
True
Ok value ->
False
You can use it like this:
r = String.toInt "20b"
if isErrorResult r then
-- not a valid Interger, abort or whatever
else
-- a good integer, extract it
a = Result.withDefault 0 r
-- and make good use of it
the default value (0 in this case) passed to withDefault is meaningless, because we made sure that r is not an Err.
You can do this as below.
---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
> parseInt string = String.toInt string
<function> : String -> Maybe Int
> resultParseInt string = \
| Result.fromMaybe ("error parsing string: " ++ string) (parseInt string)
<function> : String -> Result String Int
> resultParseInt "12"
Ok 12 : Result String Int
> resultParseInt "12ll"
Err ("error parsing string: 12ll") : Result String Int
>
I have changed guys answers a bit, since it appears to be of type Maybe
isErrorResult : String -> Bool
isErrorResult r =
case String.toInt r of
Nothing -> True
Just value -> False
I'm trying to understand why this is happening even given the limitations of DataArrays. Suppose you want to map over a DataArray of Int64s:
da = DataArray([1,2,3,4])
println(typeof(da))
println(typeof(map(a -> a^2, da))) # Returns an int for this input
println(typeof(map(a -> int(a^2), da))) # Cast the piecewise result to int
println(typeof(int(map(a -> a^2, da)))) # Cast the output DataArray{Any,1} to int
which results in
DataArray{Int64,1}
DataArray{Any,1}
DataArray{Any,1}
Array{Int64,1}
For an array, a = [1,2,3,4], map(a -> a^2, da) returns an Array of Int64s as expected. What is it about map and/or DataArrays that's causing type information to be lost here? Is there any solution to preserve type information when you're working with a type which doesn't have a constructor that converts DataArray{Any,1} to DataArray{ThatType,1}, like Dates.DateTime?
Edit: convert works fine to make a DataArray{Any,1} a DataArray{ThatType,1} (well at least for DateTime).
#which map(a -> a^2, da::DataArray{Int64, 1})
map(f::Function,dv::DataArray{T,1}) at /home/omer/.julia/v0.3/DataArrays/src/datavector.jl:114
Checking the source;
https://github.com/JuliaStats/DataArrays.jl/blob/master/src/datavector.jl
# TODO: should this be an AbstractDataVector, so it works with PDV's?
function Base.map(f::Function, dv::DataVector)
n = length(dv)
res = DataArray(Any, n)
for i in 1:n
res[i] = f(dv[i])
end
return res
end
It's creating the type DataArray{Any,1} to return.
res = DataArray(Any, n)
You can check the answer given by James Fairbanks (1 Apr 04:12 2015)
http://blog.gmane.org/gmane.comp.lang.julia.user/month=20150401