Scala Combinator Parser to Map problem - functional-programming

I have been beating my head against a wall, trying to figure out how to get this to work. The initial parser worked great, but when I try to get a Map out of it, it is only giving the initial number value, but not he value blocks.
The format is "float: [label float: [optinallabel float:] ...]" like:
412285.556: [Label 0.0:[Label1 1.0:][Label2 2.0:]
The parser to list is:
object ParseList extends JavaTokenParsers {
def sep : Parser[String] = ":"
def string : Parser[Any] = """[\.a-zA-Z0-9]+""".r
def num: Parser[Any] = floatingPointNumber <~ sep
def valueBlock: Parser[Any] = "["~>rep(valueBlock)<~"]" | string ~ floatingPointNumber <~ sep
def expr: Parser[Any] = num ~ rep(valueBlock )
def apply(in: String) = parseAll(expr,in)
}
Testing gives:
scala> ParseList("""412285.556: """)
res150: ParseList.ParseResult[Any] = [1.13] parsed: (412285.556~List())
scala> ParseList("""412285.556: [Label 1.0:]""")
res151: ParseList.ParseResult[Any] = [1.25] parsed: (412285.556~List(List((Label~1.0))))
scala> ParseList("""412285.556: [Label 0.0:[Label1 1.0:][Label2 2.0:]]""")
res152: ParseList.ParseResult[Any] = [1.51] parsed: (412285.556~List(List((Label~0.0), List((Label1~1.0)), List((Label2~2.0)))))
When I try to make it a Map, if only returns the number, but calls the member routine. See the debug output.
The Map parser:
object ParseMap extends JavaTokenParsers {
// Seperator
def sep : Parser[String] = ":"
// string
def string : Parser[String] = """[a-zA-Z][a-zA-Z0-9]+""".r
// Block within [] with label value: and option additional blocks
def valueBlock: Parser[(String,Any)] =
member <~ rep(obj)
// Member - value pair within a block
def member: Parser[(String, Any)] =
string ~ floatingPointNumber <~ sep ^^
{ case s ~ n => (s, n); println("In Member s=" +s+" n="+n); (s, n)}
def obj: Parser[Map[String,Any]] =
"["~> rep(valueBlock) <~"]" ^^ {Map() ++ _}
// Initial number value of the data
def num: Parser[(String, Any)] =
floatingPointNumber <~ sep ~ rep(obj) ^^
{ case floatingPointNumber => ("Num", floatingPointNumber) }
// order of operations
def value: Parser[Any] = (
num
| obj
| member
| floatingPointNumber
| string
)
def apply(in: String) = parseAll(value,in)
}
The testing gives:
scala> ParseMap("""412285.556: """)
res154: ParseMap.ParseResult[Any] = [1.13] parsed: (Num,412285.556)
scala> ParseMap("""412285.556: [Label 1.0:]""")
In Member s=Label n=1.0
res155: ParseMap.ParseResult[Any] = [1.25] parsed: (Num,412285.556)
scala> ParseMap("""412285.556: [Label 0.0:[Label1 1.0:][Label2 2.0:]]""")
In Member s=Label n=0.0
In Member s=Label1 n=1.0
In Member s=Label2 n=2.0
res156: ParseMap.ParseResult[Any] = [1.51] parsed: (Num,412285.556)
All my attempts to get a single Map out of it, has failed. Any help would be greatly appreciated.

Scala's parser combinators are not the easiest thing to use, and they're extraordinarily slow. Try fastparse instead.
I can't quite figure out what you want from that data (it's a very strange format!), but there's a nice guide to getting started.
Your core label-interpreter will probably look something like
val Pair = P(Label ~ " " ~ Num ~ ":")
val MapLine = P("[" Pair ~ ("[" ~ Pair ~ "]").rep ~ "]").
map{ case (pair, pairs) => pair :: pairs.toList }

The main problem is your use of the <~ combinator in the num method - this throws away all parsed data that follows it (including the rep(obj) parse result that you want for your map). Modify that line to something like:
def num: Parser[(String, Any)] =
floatingPointNumber ~ sep ~ rep(obj) ^^
{ case floatingPointNumber ~ sep ~ objs => ("Num", (floatingPointNumber, objs)) }
and you start getting results like:
scala> ParseMap("""412285.556: [Label 0.0:[Label1 1.0:][Label2 2.0:]]""")
In Member s=Label n=0.0
In Member s=Label1 n=1.0
In Member s=Label2 n=2.0
res3: ParseMap2.ParseResult[Any] = [1.51] parsed: (Num,(412285.556,List(Map(Label -> 0.0))))
which, while it probably doesn't look quite like what you might be wanting, should give you a starting point to progress further.

Related

How to make input[][] return a Just Int instead of String in Elm?

I'm a complete beginner to Elm an I'm struggling with types. I've been struggling a lot with types and I couldn't figure out a convenient way of fixing this problem. The piece of code in question is this:
view model =
div []
[ h1 [] [ text "Robot" ]
, input [ onInput SetX, value model.x ] []
, input [ onInput SetY, value model.y ] []
, input [ type_ "Int" , onInput SetCommands, value model.x] []
, button [ onClick ButtonPressed] []
, input [ readonly True ] []
]
X and Y are Ints used in the following function:
execute_orders : Int -> Int -> String -> String -> String -> { x : Int, y : Int, dir : String }
execute_orders x y commands lang dir =
let
moves =
case lang of
"English" ->
"RLF"
"French" ->
"HVG"
"Swedish" ->
"DGT"
_ ->
Debug.todo "No Language Found"
fw =
right 1 moves
directions =
facing dir
rght =
left 1 moves
lft =
slice 1 2 moves
first_move =
left 1 (toUpper commands)
rest =
slice 1 (length commands) (toUpper commands)
in
if first_move == "" then
{ x = x, y = y, dir = fromJust (head directions) }
else if first_move == fw then
if dir == "N" then
execute_orders x (y - 1) rest lang dir
else if dir == "E" then
execute_orders (x + 1) y rest lang dir
else if dir == "S" then
execute_orders x (y + 1) rest lang dir
else
execute_orders (x - 1) y rest lang dir
else if first_move == lft then
execute_orders x y rest lang (fromJust (head (turn_left directions)))
else if first_move == rght then
execute_orders x y rest lang (fromJust (head (turn_right directions)))
else
Debug.todo "branch '_' not implemented"
I need x and y to be Int's but casting them from String results in the Maybe Integer type...
There is no way to simply "cast" a String to an Int because there's no total mapping in that direction. There is, for example, no obvious integer representation for the string "banana". What you are struggling with isn't "types", but different things being different for different reasons, and having to make a decision to consolidate those differences.
String.toInt returns a Maybe Int because it's up to you to decide what to do when there's no reasonable and obvious integer representation for a given string. It's your choice whether you want to just default to 0, -1, or perhaps display the string - instead. These are all reasonable choices for different scenarios, but since String.toInt can't possibly know which scenario you're in, you have to make that decision.
If you simply want to fall back to a default integer, or you're certain that any given string will have a valid integer representation, you can use Maybe.withDefault:
String.toInt "42" |> Maybe.withDefault 0 -- 42
String.toInt "banana" |> Maybe.withDefault 0 -- 0
Or you can use a case expression to do something completely different:
case String.toInt s of
Just n ->
span [] [ text (n + 1 |> String.fromInt) ]
Nothing ->
button [ onClick ... ] [ text "-" ]
Since the HTML input event always deals with strings, you need to parse the string you receive at some point. There are three options that I see.
First, the payload of your event message can be a String, and then you parse that string in your update function.
type Msg
= SetX String
type alias Model =
{ x : Int }
update msg model =
case msg of
SetX xString ->
case String.toInt xString of
Nothing ->
model
Just x ->
{ model | x = x }
A variation of the above would be to store x as a Maybe String on your model if that makes sense, and/or storing some value for tracking an error state if you want to show a message when an invalid value is sent. This is the simplest and most flexible approach.
A second option is to have the payload of your event message be a Maybe Int. Html.Events.onInput is a function which takes a "tagger" function which accepts a String and outputs a message. While you can just give a message constructor that takes a String payload, you can also give a function which does some processing before creating a Msg value.
type Msg
= SetY (Maybe Int)
view model =
input [ type_ "number", onInput (\value -> String.toInt value |> SetY) ] []
The third option is to have the payload of your event message be an Int. This will require you to create your own version of onInput using Html.Events.on. on takes a Json.Decode.Decoder msg instead of the (String -> msg) function that onInput takes. The message will only be produced if the decoder succeeds; that is, events which produce a failed decoder will be ignored. This allows you to handle the case where the parsing fails within the event handler.
type Msg
= SetY Int
view model =
input [ type_ "number", onNumericInput SetY ] []
-- based on https://github.com/elm/html/blob/97f28cb847d816a6684bca3eba21e7dbd705ec4c/src/Html/Events.elm#L115-L122
onNumericInput : (Int -> msg) -> Attribute msg
onNumericInput toMsg =
let
alwaysStop : a -> ( a, Bool )
alwaysStop x =
( x, True )
failIfNothing : Maybe a -> Decode.Decoder a
failIfNothing maybe =
case maybe of
Nothing ->
Decode.fail "Parsing failed"
Just a ->
Decode.succeed a
decoder : Decode.Decoder msg
decoder =
Events.targetValue
|> Decode.map String.toInt
|> Decode.andThen failIfNothing
|> Decode.map toMsg
in
Events.stopPropagationOn "input" (decoder |> Decode.map alwaysStop)
You can see the last two options in a full example at https://ellie-app.com/h6fstFTyjXDa1

Result Builder that accumulates Errors

I'm trying to build Result Builder that accumulates Errors (in my case they are named Failures as I'm following some code from https://fsharpforfunandprofit.com/). It's current implementation returns first encountered Failure when ideally I'd prefer it to either return Success with desired value or a Failure with a list of all missing/corrupted values. Unfortunately current implementation it's a bit verbose.
Boilerplate code
module Rop
type RopResult<'TSuccess, 'TMessage> =
| Success of 'TSuccess * 'TMessage list
| Failure of 'TMessage list
/// create a Success with no messages
let succeed x =
Success (x,[])
/// create a Success with a message
let succeedWithMsg x msg =
Success (x,[msg])
/// create a Failure with a message
let fail msg =
Failure [msg]
/// A function that applies either fSuccess or fFailure
/// depending on the case.
let either fSuccess fFailure = function
| Success (x,msgs) -> fSuccess (x,msgs)
| Failure errors -> fFailure errors
/// merge messages with a result
let mergeMessages msgs result =
let fSuccess (x,msgs2) =
Success (x, msgs # msgs2)
let fFailure errs =
Failure (errs # msgs)
either fSuccess fFailure result
/// given a function that generates a new RopResult
/// apply it only if the result is on the Success branch
/// merge any existing messages with the new result
let bindR f result =
let fSuccess (x,msgs) =
f x |> mergeMessages msgs
let fFailure errs =
Failure errs
either fSuccess fFailure result
Builder code
module ResultComputationExpression
open Rop
type ResultBuilder() =
member __.Return(x) = RopResult.Success (x,[])
member __.Bind(x, f) = bindR f x
member __.ReturnFrom(x) = x
member this.Zero() = this.Return ()
member __.Delay(f) = f
member __.Run(f) = f()
member this.While(guard, body) =
if not (guard())
then this.Zero()
else this.Bind( body(), fun () ->
this.While(guard, body))
member this.TryWith(body, handler) =
try this.ReturnFrom(body())
with e -> handler e
member this.TryFinally(body, compensation) =
try this.ReturnFrom(body())
finally compensation()
member this.Using(disposable:#System.IDisposable, body) =
let body' = fun () -> body disposable
this.TryFinally(body', fun () ->
match disposable with
| null -> ()
| disp -> disp.Dispose())
member this.For(sequence:seq<_>, body) =
this.Using(sequence.GetEnumerator(),fun enum ->
this.While(enum.MoveNext,
this.Delay(fun () -> body enum.Current)))
member this.Combine (a,b) =
this.Bind(a, fun () -> b())
let result = new ResultBuilder()
Use case
let crateFromPrimitive (taskId:int) (title:string) (startTime:DateTime) : RopResult<SomeValue,DomainErrror> =
result {
// functions that, at the end, return "RopResult<TaskID,DomainError>" therefore "let! id" is of type "TaskID"
let! id = taskId |> RecurringTaskId.create |> mapMessagesR mapIntErrors
// functions that, at the end, return "RopResult<Title,DomainError>" therefore "let! tt" is of type "Title"
let! tt = title|> Title.create |> mapMessagesR mapStringErrors
// functions that, at the end, return "RopResult<StartTime,DomainError>" therefore "let! st" is of type "StartTime"
let! st = startTime|> StartTime.create |> mapMessagesR mapIntErrors
// "create" returns "RopResult<SomeValue,DomainErrror>", "let! value" is of type "SomeValue"
let! value = create id tt st
return value
}
I could possibly split it to first validate taskId, title and startTime and then eventually call create but is it possible to do in one go?
I found this answer but I have no idea how to translate it to my case or if it's even related.
UPDATE: Solution
Just like brainbers comment and solution says, and! solves my problem. What still troubles me is the idea of automatically de-toupling (namely, when does it happen and on what rules?). In any case, I expect people will be more than able to put two and two together but the working solution for my problem is:
Builder part
...
member _.MergeSources(result1, result2) =
match result1, result2 with
| Success (ok1,msgs1), Success (ok2,msgs2) ->
Success ((ok1,ok2),msgs1#msgs2 )
| Failure errs1, Success _ -> Failure errs1
| Success _, Failure errs2 -> Failure errs2
| Failure errs1, Failure errs2 -> Failure (errs1 # errs2) // accumulate errors
...
Use Case
let crateFromPrimitive taskId title startTime duration category description (subtasks:string list option) (repeatFormat:RepeatFormat option) =
result {
let strintToSubTask = (Subtask.create >> (mapMessagesR mapStringErrors))
let sListToSubtaskList value = List.map strintToSubTask value
|> RopResultHelpers.sequence
let! id = RecurringTaskId.create taskId |> mapMessagesR mapIntErrors
and! tt = Title.create title |> mapMessagesR mapStringErrors
and! st = StartTime.create startTime |> mapMessagesR mapIntErrors
and! dur = Duration.create duration |> mapMessagesR mapIntErrors
and! cat = Category.create category |> mapMessagesR mapStringErrors
and! desc = Description.create description |> mapMessagesR mapStringErrors
and! subtOption = someOrNone sListToSubtaskList subtasks |> RopResultHelpers.fromOptionToSuccess
//let! value = create id tt st dur cat desc subtOption repeatFormat
return! create id tt st dur cat desc subtOption repeatFormat
}
I searched around a bit and didn't find any validators that use the new and! syntax and accumulate errors, so I decided to write a quick one myself. I think this does what you want, and is much simpler. Note that I'm using Result<_, List<_>> to accumulate a list of errors, rather than creating a new type.
type AccumValidationBuilder() =
member _.BindReturn(result, f) =
result |> Result.map f
member _.MergeSources(result1, result2) =
match result1, result2 with
| Ok ok1, Ok ok2 -> Ok (ok1, ok2) // compiler will automatically de-tuple these - very cool!
| Error errs1, Ok _ -> Error errs1
| Ok _, Error errs2 -> Error errs2
| Error errs1, Error errs2 -> Error (errs1 # errs2) // accumulate errors
let accValid = AccumValidationBuilder()
And here it is in action:
let validateInt (str : string) =
match Int32.TryParse(str) with
| true, n -> Ok n
| _ -> Error [ str ]
let test str1 str2 str3 =
let result =
accValid {
let! n1 = validateInt str1
and! n2 = validateInt str2
and! n3 = validateInt str3
return n1 + n2 + n3
}
printfn "Result : %A" result
[<EntryPoint>]
let main argv =
test "1" "2" "3" // output: Ok 6
test "1" "red" "blue" // output: Error [ "red"; "blue" ]
0

How to read pair by pair from a file in SML?

I want to read N pairs from a file and store them as a tuples in a list.For example if i have these 3 pairs : 1-2 , 7-3, 2-9 i want my list to look like this -> [(1,2),(7,3),(2-9)]
I tried something like this:
fun ex filename =
let
fun readInt input = Option.valOf (TextIO.scanStream (Int.scan StringCvt.DEC) input)
val instream = TextIO.openIn filename
val T = readInt instream (*number of pairs*)
val _ = TextIO.inputLine instream
fun read_ints2 (x,acc) =
if x = 0 then acc
else read_ints2(x-1,(readInt instream,readInt instream)::acc)
in
...
end
When i run it i get an exeption error :/ What's wrong??
I came up with this solution. I reads a single line from the given file. In processing the text it strips away anything not a digit creating a single flat list of chars. Then it splits the flat list of chars into a list of pairs and in the process converts the chars to ints. I'm sure it could be improved.
fun readIntPairs file =
let val is = TextIO.openIn file
in
case (TextIO.inputLine is)
of NONE => ""
| SOME line => line
end
fun parseIntPairs data =
let val cs = (List.filter Char.isDigit) (explode data)
fun toInt c =
case Int.fromString (str c)
of NONE => 0
| SOME i => i
fun part [] = []
| part [x] = []
| part (x::y::zs) = (toInt x,toInt y)::part(zs)
in
part cs
end
parseIntPairs (readIntPairs "pairs.txt");

Extracting the name of a variable

How can we build a function in F# that outputs the name of the variable passed in? For example:
let someVar1 = "x"
getVarname someVar1 //output would be "someVar1"
let someVar2 = "y"
getVarname someVar2 //output would be "someVar2"
let f toString = fun a -> printfn "%s: %d" (toString a) a
let x = 1
f getVarname x //output would be: "x: 1"
I found a similar question in C# here (get name of a variable or parameter), but I was unable to make it work in F#.
If you use quotations and static methods, you can already capture the name of the variable in F# 4 using the ReflectedDefinition attribute. The Demo.GetVarName static method in the following example returns the name of the variable used as an argument together with the value:
open Microsoft.FSharp.Quotations
type Demo =
static member GetVarName([<ReflectedDefinition(true)>] x:Expr<int>) =
match x with
| Patterns.WithValue(_, _, Patterns.ValueWithName(value, _, name)) ->
name, value :?> int
| _ -> failwithf "Argument was not a variable: %A" x
let test ()=
let yadda = 123
Demo.GetVarName(yadda)
test()
This works for local variables as in the test() function above. For top-level variables (which are actually compiled as properties) you also need to add a case for PropertyGet:
match x with
| Patterns.WithValue(_, _, Patterns.ValueWithName(value, _, name)) ->
name, value :?> int
| Patterns.WithValue(value, _, Patterns.PropertyGet(_, pi, _)) ->
pi.Name, value :?> int
| _ -> failwithf "Argument was not a variable: %A" x
The nameof implementation has the operator in F# core, but the F# 5 compiler bits haven't shipped yet.
When it does, you can use it to get the name of a symbol.
let someVar1 = None
let name = nameof someVar1 // name = "someVar1"
For now, we can maybe abuse the dynamic operator to get us a shim which you can eventually replace with nameof
let name = ()
let (?) _ name = string name
Usage:
let someVar1 = None
let name = name?someVar1
It doesn't read too bad, and you get some degree of auto-completion.
If you really want to be able to retrieve the local name and value at the call-site, there's quotations.
let printVar = function
| ValueWithName(value, _type, name) -> printfn "%s = %A" name value
| _ -> ()
The usage is a bit noisy, though.
let someVar1 = 12
printVar <# someVar1 #> //prints someVar1 = 12

Type error on the function passed to Dict.map

I am reletively new to functional programming, to be honest about 2 days. I am trying to print out values from a Dict Int Int, but I can't seem to figure out how to pass this Dict Int Int to a function.
The error I am receiving is below.
The 1st argument to map is not what I expect:
32| div [] (Dict.map toLiDict dict)
^^^^^^^^ This toLiDict value is a:
Dict Int Int -> Html msg
But map needs the 1st argument to be:
Dict Int Int -> b
The function toHtmlDict and toLiDict is the one's causing me this issue, they are currently commented out below. Also I am calling this from the view, div [] [ toHtmlDict model.uniqueValues ], I have this commented out as well.
Here is what I am currently working with; I posted the whole code as it would be easier if you should need anything else.
Here's a link on Ellie here that can be ran.
module Main exposing (main)
import Browser
import Dict exposing (Dict)
import Html.Attributes
import Html exposing (Html, button, div, text, strong, p, li, ul)
import Html.Events exposing (onClick)
type alias Model =
{ currentNumber : Int, clicks : Int, outputList : List(String), uniqueValues : Dict Int Int }
--{ currentNumber : Int, clicks : Int, history : String, outputList : List(String) }
initialModel : Model
initialModel =
{ currentNumber = 0, clicks = 0, outputList = [""], uniqueValues = Dict.fromList [(1,1)] } --Dict.empty should be default here...
--{ currentNumber = 0, clicks = 0, history = "Current outputs ...", outputList = ["Current outputs ...", " "] }
-- applies a new div for each element in the list
toHtmlList : List String -> Html msg
toHtmlList strings =
div [] (List.map toLi strings)
-- creates a div along with the text to be shown
toLi : String -> Html msg
toLi s =
div [] [ text s ]
-- --applies a new div for each element in the dictionary
-- toHtmlDict : Dict Int Int -> Html msg
-- toHtmlDict dict =
-- div [] (Dict.map toLiDict dict)
-- -- creates a div along with the text to be shown
-- toLiDict : Dict Int Int -> Html msg
-- toLiDict k =
-- div [] [ text "What here?" ]
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
-- Note: when assigning the model.currentNumber and then concatenating the value, it will not be updated...
Increment ->
{ model | currentNumber = model.currentNumber + 1, clicks = model.clicks + 1, outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1)], uniqueValues = model.uniqueValues }
--{ model | currentNumber = model.currentNumber + 1, clicks = model.clicks + 1, history = model.history ++ addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1), outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1)] }
Decrement ->
{ model | currentNumber = model.currentNumber - 1, clicks = model.clicks + 1, outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1)]}
--{ model | currentNumber = model.currentNumber - 1, clicks = model.clicks + 1, history = model.history ++ addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1), outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1)]}
view : Model -> Html Msg
view model =
Html.div []
[ button [ onClick Increment ] [ strong [Html.Attributes.style "color" "black"] [ text "+1" ]]
, button [ onClick Decrement ] [ strong [Html.Attributes.style "color" "red"] [ text "-1" ]]
, p [] []
--, div [] [ text <| "The current number is: ", strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.currentNumber ], text " and it's ", text (oddOrEven model.currentNumber) ]
, div [] [ text <| "The current number is: ", strong [Html.Attributes.style "color" <| evenOddColor model.currentNumber] [ text <| String.fromInt model.currentNumber ], text " and it's ", strong [Html.Attributes.style "color" <| evenOddColor model.currentNumber ] [ text <| oddOrEven model.currentNumber ] ]
, div [] [ text "Total clicks: ", strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.clicks ]]
, p [] []
, div [] [ strong [Html.Attributes.style "color" "Blue"] [ text "Unique values ..." ]]
, p [] []
--, div [] [ toHtmlDict model.uniqueValues ]
--, div [] [ text <| "The current number is: " ++ String.fromInt model.currentNumber ++ " and it's " ++ oddOrEven model.currentNumber ]
--, div [] [ text "Total clicks: ", strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.clicks ]]
--, p [] []
, div [] [ strong [Html.Attributes.style "color" "Blue"] [ text "History ..." ]]
, p [] []
--, div [] [ text <| model.history ] - TEMPORARY
, div [] [ toHtmlList model.outputList ]
]
-- appendToList string number =
-- "Number was " ++ String.fromInt number ++ " and it was " ++ string
addToPage string number =
"Number was " ++ String.fromInt number ++ " and it was " ++ string
-- determines if number is even or odd
oddOrEven number =
if modBy 2 number == 0 then
"even"
else
"odd"
-- call another function with param
evenOddColor number =
if oddOrEven(number) == "even" then
"green"
else
"red"
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Dict.map is a function that will transform the values of a Dict and return another Dict with the new values. This is not what you want here, but lets try to grok its type anyway, because it's a useful learning experience.
Dict.map
A Dicts full type is Dick k v, meaning that the type variables correspond to the type of its keys and values respectively. Dict.map, according to the documentation, has the type (k -> a -> b) -> Dict k a -> Dict k b. As its first argument it takes a function of two arguments, k and a, and that returns a b. maps second argument is a Dict k a, and it returns a Dict k b.
We can see that the k is the same in both the input and return Dicts, which means its type will remain the same, but the type variable for the values is different, a in the input and b in the return Dict. And the function similarly takes a as one of its inputs, along with a k, and returns a b. So for each of the key-value pairs in the input Dict, the mapping function will be called with its key, 'k', and value 'a', and is expected to return a b value.
For a Dict Int Int as you have, both k and v are Ints. If we substitute these in the type of Dict.map we get (Int -> Int -> b) -> Dict Int Int -> Dict Int b. We don't know what b is yet since that will be determined by the function we pass it, but we can at least see that the function expects two Int arguments`.
Meanwhile, the function you give it, toLiDict, has the type Dict Int Int -> Html msg which takes one argument that isn't an Int. This is what the error message is clumsily trying to convey. We could rewrite toLiDict to conform to what Dict.map expects, as a function Int -> Int -> Html msg, but that would have Dict.map return a Dict Int (Html msg), which isn't what you want. So let's drop that.
In general, map functions conventionally transform the elements of a collection without changing the type of collection.
Dict.foldl
If you want instead is to transform the elements of a collection into something else entirely, and there isn't something more specific to use, a "fold" is usually the right tool. Dict provides foldl and foldr, which basically does the same thing but in different order, foldl iterates over the elements from the "left" and foldr from the "right". If the order doesn't matter, use foldl because it's more efficient.
The type signature of Dict.foldl is (k -> v -> b -> b) -> b -> Dict k v -> b. That is, the transformation function now takes three arguments, the key, k, value, v, and a b which we'll call the accumulator, and returns a b. foldl itself also takes an additional argument, again a b, which will be the initial b value passed to the transformation function. The third argument is the Dict, and the return value is again a b.
For each key-value pair in the input Dict, foldl will, just like map, call the transformation function with its key and value. But it also provides a b which initially is the b value passed to foldl itself, but for subsequent iterations will be the b value returned from the transformation function. In that way the "accumulator" accumulates the return value.
Let's rewrite your code to use Dict.foldl instead:
toHtmlDict : Dict Int Int -> Html msg
toHtmlDict dict =
div [] (Dict.foldl toLiDict [] dict)
toLiDict : Int -> Int -> List (Html msg) -> List (Html msg)
toLiDict k v acc =
div [] [ text "What here?" ] :: acc
Here, toHtmlDict remains largely the same, but uses Dict.foldl instead of Dict.map and provides it an initial value of an empty list, [].
toLiDict sees bigger changes. Its type has changed to Int -> Int -> List (Html msg) -> List (Html msg), mneaning it takes the arguments: The key and value, both of which are Ints, and the accumulator is a List (Html msg), as is the return value.
But the implementation has barely changed. Instead of just returning an element directly, it's appended to the accumulator with :: acc.
And that's all there is to it. The result of the fold is the accumulated list of Html elements, as expected. If you plop the above code into yours, it will work.
Dict.values and Dict.toList
Finally, I noted earlier that foldl is a good choice if there aren't more appropriate specialized function. And since the end result you want is a list, either Dict.values or Dict.toList, as #bdukes has suggested, probably are. These aren't as efficient as a fold, since you'll iterate trough the elements twice, once to convert to a list and then to map them, but this rarely matters in practice. Specialized functions are also more descriptive and documents your intent better, so use them if you can.
The definition of Dict.map is (k -> a -> b) -> Dict k a -> Dict k b. So it takes a function and a Dict and returns a new Dict. That mapping function takes the key and value, and returns a new value.
In your case, you're looking to return a List (Html Msg), rather than a Dict of anything. So, rather than using Dict.map, I would call Dict.values to get a List of the values, and then use List.map to transform those values into Html Msg. If you need both the key and value to generate the Html Msg, use Dict.toList instead, to get a List (k, v) (i.e. a List of tuples, where the tuple has the key and value).
toHtmlDict : Dict Int Int -> Html Msg
toHtmlDict dict =
div [] (List.map viewDictEntry (Dict.toList dict))
viewDictEntry : (Int, Int) -> Html Msg
viewDictEntry (key, value) =
li [] [ text (String.fromInt key), text " = ", text (String.fromInt value) ]

Resources