How to make a decoder with an optional element? - elm

I’m stuck with a decoder that should decode an array [ 9.34958, 48.87733, 1000 ] to a Point, where index 2 (elevation) is optional.
type alias Point =
{ elev : Maybe Float
, at : Float
, lng : Float
}
Therefore I created following decoder:
fromArrayDecoder : Decoder Point
fromArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.maybe)
(index 1 Decode.float)
(index 0 Decode.float)
My problem now is, that this decoder succeeds when index 2 is missing or is of any type like string etc. But I want it only to succeed if elev is missing, not if it has the wrong type. Is there any way of accomplishing this?

If by "missing" you mean the value can be null, you can just use nullable instead of maybe:
fromArrayDecoder : Decoder Point
fromArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.nullable)
(index 1 Decode.float)
(index 0 Decode.float)
If it's either a 3-element array or a two-element array you can use oneOf to try several decoders in order:
fromTwoArrayDecoder : Decoder Point
fromTwoArrayDecoder =
map3 (Point Nothing)
(index 1 Decode.float)
(index 0 Decode.float)
fromThreeArrayDecoder : Decoder Point
fromThreeArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.map Just)
(index 1 Decode.float)
(index 0 Decode.float)
fromArrayDecoder : Decoder Point
fromArrayDecoder =
oneOf
[ fromThreeArrayDecoder
, fromTwoArrayDecoder
]
Just remember to try the 3-element decoder first, as the 2-element decoder will succeed on a 3-element array as well, but the opposite does not.

I agree that the fact Json.Decode.maybe giving you Nothing on a wrong value rather than just a missing one is surprising.
elm-json-decode-pipeline can work in the way you want without getting too verbose.
> d = Decode.succeed Point
| |> optional "2" (Decode.float |> Decode.map Just) Nothing
| |> required "1" Decode.float
| |> required "0" Decode.float
|
> "[1, 2, \"3\"]" |> Decode.decodeString d
Err (Failure ("Json.Decode.oneOf failed in the following 2 ways:\n\n\n\n(1) Problem with the given value:\n \n \"3\"\n \n Expecting a FLOAT\n\n\n\n(2) Problem with the given value:\n \n \"3\"\n \n Expecting null") <internals>)
: Result Decode.Error Point
> "[1, 2, 3]" |> Decode.decodeString d
Ok { at = 2, elev = Just 3, lng = 1 }
: Result Decode.Error Point
> "[1, 2]" |> Decode.decodeString d
Ok { at = 2, elev = Nothing, lng = 1 }
: Result Decode.Error Point
(You can see from the error that under the hood it is using oneOf like in glennsl's answer.)
The only potentially surprising thing here is that you need to pass strings rather than int indexes, as there isn't a specific version for lists but you can access list indexes as though they are field names. This does mean that this version is subtly different in that it will not throw an error if you can an object with number field names rather than an array, but I can't imagine that really being an issue. The more real issue is it could make your error messages less accurate:
> "[0]" |> Decode.decodeString (Decode.field "0" Decode.int)
Ok 0 : Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.field "0" Decode.int)
Err (Failure ("Expecting an OBJECT with a field named `0`") <internals>)
: Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.index 0 Decode.int)
Err (Failure ("Expecting a LONGER array. Need index 0 but only see 0 entries") <internals>)
Note that you do still have to to avoid using Json.Decode.maybe. It may be tempting to write optional "2" (Decode.maybe Decode.float) Nothing which will result in the same behaviour as you originally got.

Related

Decode array of integers as Date in Elm

I'm trying to convert this json
{ "date": [2018, 2, 3] }
into this model
type alias MyModel = { date: Date }
I know how to decode it into a list
decoder =
decode MyModel (field "date" (list int))
but I can't figure out how to chain Decoders together.
You can use Json.Decode.index to pull out values at known indices. You'll want the values at indices 0, 1, and 2, and then you can convert them to a string for use in Date.fromString like this:
import Date exposing (Date)
import Html exposing (Html, text)
import Json.Decode exposing (..)
dateDecoder : Decoder Date
dateDecoder =
let
toDateString y m d =
String.join "-" (List.map toString [ y, m, d ])
in
map3 toDateString
(index 0 int)
(index 1 int)
(index 2 int)
|> andThen
(\str ->
case Date.fromString str of
Ok date ->
succeed date
Err err ->
fail err
)
You could use the decoder like this:
decoder =
decode MyModel (field "date" dateDecoder)

Disregard component of a Triple in a comparison

I am attempting to compare Triples while disregarding certain values of the Triple. The value I wish to disregard below is signified by _. Note the below code is for example purposes and does not compile because _ is an Unresolved reference.
val coordinates = Triple(3, 2, 5)
when (coordinates) {
Triple(0, 0, 0) -> println("Origin")
Triple(_, 0, 0)-> println("On the x-axis.")
Triple(0, _, 0)-> println("On the y-axis.")
Triple(0, 0, _)-> println("On the z-axis.")
else-> println("Somewhere in space")
}
I know you can use _ when destructuring if you would like to ignore a value but that doesn't seem to help me with the above issue:
val (x4, y4, _) = coordinates
println(x4)
println(y4)
Any ideas how I can achieve this?
Thank you!
Underscore for unused variables was introduced in Kotlin 1.1 and it is designed to be used when some variables are not needed in the destructuring declaration.
In the branch conditions of your when expression, Triple(0, 0, 0) is creating an new instance but not destructuring. So, using underscore is not permitted here.
Currently, destructuring in the branch conditions of when expression is not possible in Kotlin. One of the solutions for your case is to compare each of the component verbosely in each branch condition:
val (x, y, z) = Triple(3, 2, 5)
when {
x == 0 && y == 0 && z == 0 -> println("Origin")
y == 0 && z == 0 -> println("On the x-axis.")
x == 0 && z == 0 -> println("On the y-axis.")
x == 0 && y == 0 -> println("On the z-axis.")
else -> println("Somewhere in space")
}
Here is a discussion on destructuring in when expression.

Maximum in List of Records

Say I have a List of records in elm:
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
and I want to get the record with the greatest magnitude value (300). What is a good way of doing this?
The docs gives an example of using the "maximum" -method, but it uses a simple list of integers. How is it done with records?
Update based on recommendation from #robertjlooby
There is a function called maximumBy which does exactly this in elm-community/list-extra. Example:
List.Extra.maximumBy .magnitude list
Original Answer
There are a few ways to achieve this.
This first way is more concise but it involves sorting the whole list, reversing it, then taking the head.
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
List.head << List.reverse << List.sortBy field
If you want something that's more efficient and only traverses the list once, here's a more efficient version:
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
let f x acc =
case acc of
Nothing -> Just x
Just y -> if field x > field y then Just x else Just y
in List.foldr f Nothing
An example of it in use:
list =
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
main =
text <| toString <| maxOfField .magnitude list
Here is a version that uses foldl and a default record:
bigger =
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose {id = 0, magnitude = 0} items
Sebastian's answer add an arbitrary start value which could cause a problem if all your magnitudes were negative. I would adjust to
bigger items =
case items of
[] -> []
(h :: []) -> h
(h :: tail) ->
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose h tail

F# HashCode to enum conversion

I have an enum of bit-masked error codes with a string representation and an binary int representation:
type ErrorCodes =
| NoError = 0
| InvalidInputError = 1
| AuthenticationFailedError = 2
| InvalidArgumentError = 4
| ItemNotFoundError = 8
| UnknownError = 16
As I run through the program, I collect all the errors by using the bitwise OR operator (|||). So now I have something that looks like 01100. How can I print to the console: "InvalidArgumentError", and "ItemNotFoundError?"
I had an idea of just using:
for i = 0 to 32 do
if ((err.GetHashCode() % 2) = 1) then
Console.WriteLine("ErrorCode: {0}",err.GetHashCode())
But now I'm stuck on how to print the actual string
If you decorate your ErrorCodes type with the System.Flags attribute then .ToString will format as a list of value names.
[<System.Flags>]
type ErrorCodes = ...
let errors = ErrorCodes.InvalidInputError ||| ErrorCodes.UnknownError
printfn "%O" errors
If, for whatever reason, you don't want the default flags ToString implementation, you could do something like this:
let inline printFlags (flags: 'e) =
let ty = typeof<'e>
(Enum.GetValues ty :?> 'e[], Enum.GetNames ty)
||> Array.zip
|> Seq.filter (fun (v, _) -> v <> enum 0 && flags &&& v = v)
|> Seq.iter (snd >> printfn "%s")
printFlags (ErrorCodes.InvalidInputError ||| ErrorCodes.UnknownError)
Output:
InvalidInputError
UnknownError

How to format a number with padding in Erlang

I need to pad the output of an integer to a given length.
For example, with a length of 4 digits, the output of the integer 4 is "0004" instead of "4". How can I do this in Erlang?
adding a bit of explanation to Zed's answer:
Erlang Format specification is: ~F.P.PadModC.
"~4..0B~n" translates to:
~F. = ~4. (Field width of 4)
P. = . (no Precision specified)
Pad = 0 (Pad with zeroes)
Mod = (no control sequence Modifier specified)
C = B (Control sequence B = integer in default base 10)
and ~n is new line.
io:format("~4..0B~n", [Num]).
string:right(integer_to_list(4), 4, $0).
The problem with io:format is that if your integer doesn't fit, you get asterisks:
> io:format("~4..0B~n", [1234]).
1234
> io:format("~4..0B~n", [12345]).
****
The problem with string:right is that it throws away the characters that don't fit:
> string:right(integer_to_list(1234), 4, $0).
"1234"
> string:right(integer_to_list(12345), 4, $0).
"2345"
I haven't found a library module that behaves as I would expect (i.e. print my number even if it doesn't fit into the padding), so I wrote my own formatting function:
%%------------------------------------------------------------------------------
%% #doc Format an integer with a padding of zeroes
%% #end
%%------------------------------------------------------------------------------
-spec format_with_padding(Number :: integer(),
Padding :: integer()) -> iodata().
format_with_padding(Number, Padding) when Number < 0 ->
[$- | format_with_padding(-Number, Padding - 1)];
format_with_padding(Number, Padding) ->
NumberStr = integer_to_list(Number),
ZeroesNeeded = max(Padding - length(NumberStr), 0),
[lists:duplicate(ZeroesNeeded, $0), NumberStr].
(You can use iolist_to_binary/1 to convert the result to binary, or you can use lists:flatten(io_lib:format("~s", [Result])) to convert it to a list.)
Eshell V12.0.3 (abort with ^G)
1> F = fun(Max, I)-> case Max - length(integer_to_list(I)) of X when X > 0 -> string:chars($0, X) ++ integer_to_list(I); _ -> I end end.
#Fun<erl_eval.43.40011524>
2> F(10, 22).
"0000000022"
3> F(3, 22345).
22345