As pictured here, I've created a small sample program that has some controls to modify parts of the model.
What I've been unsuccessfully trying to do is to make a HTTP request to get the initial data (it's hardcoded right now), or later on replace the data with the response from said HTTP request when a Reset message is received. I did read the HTTP chaper of the introduction to Elm, but I can't seem to piece things together.
The goal is to have a loadTraits function that takes a String (SomeId) and returns a List of type TraitWithRelevance, so I can replace the model with that incoming data.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
( model, Cmd.none )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]
Here's an example response from the http server. I've chosen this format, because I thought it'd map easiest to the elm model. I can easily change the response if there's a better way to consume this data in elm.
{"traits":[["name for trait a","a",1],["this is the name for trait b,"b",50]]}
P.S. Even though there's plenty of lines of code, please be aware that I tried to strip down the problem as much as possible, while keeping enough context.
You need to have a message which returns the data. Assuming it's a simple list of Traits:
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error (List Traits))
Then you need a command to send the request, something like
fetchTraits : Cmd Msg
fetchTraits =
Http.get "http://localhost:4000/traits" traitListDecoder
|> Http.send OnFetchTraits
and you need to implement traitListDecoder, to decode your JSON into the list that is returned in the msg.
Then instead of returning Cmd.none in your update function where you are stuck, you return fetchTraits. Elm will then make the request and you will get an OnFetchTraits msg passed into update. You need a separate case to handle this. You unpack the Result type and extract your data if the request was successful, or handle the error if not.
So basically you need to do two things, the reset button should call a command to get the traits. Then, in the update you have to handle the response from the command. After getting the Result back you can use it to update your model.
Here is an update to your code. I added a person to the model which gets updated when the user presses the reset button.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode exposing (Decoder, string)
import Json.Decode.Pipeline exposing (decode, required)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- Commands
type alias Person =
{ name : String
, gender : String
}
decodePerson : Decoder Person
decodePerson =
decode Person
|> required "name" string
|> required "gender" string
getTraits =
let
url =
"http://swapi.co/api/people/1/"
request =
Http.get url decodePerson
in
Http.send GetTraitResponse request
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
, person : Person
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg"} Nothing {name = "", gender=""}, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| GetTraitResponse (Result Http.Error Person)
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
-- handle the response
GetTraitResponse resp ->
let
_ =
Debug.log "response" resp
person =
case resp of
Ok val ->
val
Result.Err e ->
{name = "", gender=""}
in
( {model | person = person }, Cmd.none )
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
-- call the command to get the traits
( model, getTraits )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
, text <| toString model
]
While both answers have been correct and helpful, it took me still another mile to reach my destination.
First off, I didn't want to rely on a non elm-lang package.
Json.Decode.Pipeline therefore doesn't meet that requirement.
With the examples posted, I still had the trouble of decoding the list of traits. What I ended up doing was changing the response from the server so that the traits list is not of type list, but of type object with name, someId, and relevance as keys and its values as strings. This helped me distinguish between what's coming back from the server and what's being represented internally in my elm model.
So the Reset functionality works nicely, the next step is to get those values into the initial state of the model without user interaction.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode exposing (Decoder)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
type alias TraitInfo =
{ traits :
List TraitObject
}
type Traits
= TraitInfoFromServer (List TraitObject)
type alias TraitObject =
{ name : String, someId : String, relevance : String }
fetchTraits : String -> Cmd Msg
fetchTraits someId =
Http.get
("http://localhost:8000/traits/" ++ someId)
decodeTraits
|> Http.send OnFetchTraits
decodeTraits : Decoder TraitInfo
decodeTraits =
Decode.map TraitInfo
(Decode.field "traits" (Decode.list decodeTrait))
decodeTrait : Decoder TraitObject
decodeTrait =
(Decode.map3 TraitObject
(Decode.field "name" Decode.string)
(Decode.field "someId" Decode.string)
(Decode.field "relevance" Decode.string)
)
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "someIdToStartWith"
, traits =
[ ( "trait a", "a", "1" )
, ( "trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error TraitInfo)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
( model, fetchTraits model.contentWithTraits.someId )
OnFetchTraits resp ->
let
newTraits =
case resp of
Ok val ->
val.traits
Result.Err e ->
[]
in
( { model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = List.map traitObjToTuple newTraits
}
}
, Cmd.none
)
traitObjToTuple : TraitObject -> TraitWithRelevance
traitObjToTuple obj =
( obj.name, obj.someId, obj.relevance )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text name
, valueRange "range" trait
, valueRange "number" trait
, text someId
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 100 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]
Related
I have a list that looks like this:
val myList = listOf(
Message(
id= 1,
info = listOf(1, 2)
),
Message(
id= 1,
info = listOf(3, 4)
),
Message(
id= 2,
info = listOf(5, 6)
)
)
How can I convert it so the elements with the same id are combined?
listOf(
Message
id= 1
info = listOf(1, 2, 3, 4)
),
Message
id= 2
info = listOf(5, 6)
)
)
I've tried the following, and it works
myList
.groupBy { it.id }
.map { entry ->
val infos = entry.value.fold(listOf<Int>()) { acc, e -> acc + e.info }
Message(
id = entry.key,
info = infos
)
}
But I was wondering if there was an easier/cleaner/more idiomatic way to merge these objects. It seems like I would be able to do this with a single fold, but I can't wrap my brain around it.
Thanks
Would also go for groupingBy but do it a bit differently via fold (compare also Grouping):
myList.groupingBy { it.id }
.fold({ _, _ -> mutableListOf<Int>() }) { _, acc, el ->
acc.also { it += el.info }
}
.map { (id, infos) -> Message(id, infos) }
This way you have only 1 intermediate map and only 1 intermediate list per key, which accumulates your values. At the end you transform it in the form you require (e.g. into a Message). Maybe you do not even need that? Maybe the map is already what you are after?
In that case you may want to use something as follows (i.e. narrowing the mutable list type of the values):
val groupedMessages : Map<Int, List<Int>> = myList.groupingBy { it.id }
.fold({ _, _ -> mutableListOf() }) { _, acc, el ->
acc.also { it += el.info }
}
You can groupingBy the ids, then reduce, which would perform a reduction on each of the groups.
myList.groupingBy { it.id }.reduce { id, acc, msg ->
Message(id, acc.info + msg.info)
}.values
This will of course create lots of Message and List objects, but that's the way it is, since both are immutable. But there is also a chance that this doesn't matter in the grand scheme of things.
If you had a MutableMessage like this:
data class MutableMessage(
val id: Int,
val info: MutableList<Int>
)
You could do:
myList.groupingBy { it.id }.reduce { _, acc, msg ->
acc.also { it.info.addAll(msg.info) }
}.values
A solution without using reduce or fold:
data class Message(val id: Int, val info: List<Int>)
val list = listOf(
Message(id = 1, info = listOf(1, 2)),
Message(id = 1, info = listOf(3, 4)),
Message(id = 2, info = listOf(5, 6))
)
val result = list
.groupBy { message -> message.id }
.map { (_, message) -> message.first().copy(info = message.map { it.info }.flatten() ) }
result.forEach(::println)
By extracting out a few functions which have a meaning of their own, You can make it readable to a great extent.
data class Message(val id: Int, val info: List<Int>) {
fun merge(that: Message): Message = this.copy(info = this.info + that.info)
}
fun List<Message>.mergeAll() =
this.reduce { first, second -> first.merge(second) }
fun main() {
val myList = listOf(
Message(
id = 1,
info = listOf(1, 2)
),
Message(
id = 1,
info = listOf(3, 4)
),
Message(
id = 2,
info = listOf(5, 6)
)
)
val output = myList
.groupBy { it.id }
.values
.map { it.mergeAll() }
println(output)
}
I'm trying to write a TabControl in Elm as follows
module TabControl exposing (..)
import Html
import Html.Attributes as Attributes
import Html.Events
import Array
type Msg
= OnSelectedTabChanged
| NoOp
type alias Model msg =
{ tabs : List (Tab msg)
, selectedIndex : Int
}
constructor : Model msg
constructor =
{ tabs = []
, selectedIndex = 0
}
type alias Tab msg =
{ title : String
, content : Html.Html msg
}
withTab : Tab msg -> Model msg -> (Model msg)
withTab tab model =
{ model | tabs = model.tabs ++ [tab] }
render : Model msg -> Html.Html msg
render model =
let
header = renderTabHeaders model
content = renderSelectedTabContent model
in
Html.div [] [ header, content ]
renderTabHeaders : Model msg -> Html.Html msg
renderTabHeaders model =
Html.div []
[
Html.ul []
(
model.tabs
|> List.map (\(tab) -> renderTabHeader tab)
)
]
renderTabHeader : Tab msg -> Html.Html msg
renderTabHeader tab =
Html.li [Html.Events.onClick OnSelectedTabChanged] [Html.text tab.title]
renderSelectedTabContent : Model msg -> Html.Html msg
renderSelectedTabContent model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
value.content
Nothing ->
Html.text ""
which is rendered by
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import TabControl
main =
Browser.sandbox { init = 0, update = update, view = view }
view model =
div []
[
TabControl.constructor
|> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
|> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
|> TabControl.render
]
However, I can't seem to get the return types correct.
If I refactor renderTabHeader : Tab msg -> Html.Html msg to renderTabHeader : Tab msg -> Html.Html Msg then
Something is off with the body of the `renderTabHeaders` definition:
This `div` call produces:
Html.Html Msg
But the type annotation on `renderTabHeaders` says it should be:
Html.Html msg
if I don't then
Something is off with the body of the `renderTabHeader` definition:
This `li` call produces:
Html.Html Msg
But the type annotation on `renderTabHeader` says it should be:
Html.Html msg
If I make every function return Html.Html Msg then
Something is off with the 1st branch of this `case` expression:
67| value.content
^^^^^^^^^^^^^
The value at .content is a:
Html.Html msg
But the type annotation on `renderSelectedTabContent` says it should be:
Html.Html Msg
How can I have elements in a div, some of which return Html.Html msg and some of which return Html.Html Msg? Or alternatively, how can I convert between Html.Html msg and Html.Html Msg
If you're wanting to expose your Msg type, you'll need the user of your module to supply a function which translates your Msg into their msg.
So, if the top-level Msg type looks like this:
type Msg
= NoOp
| TabControlMsg TabControl.Msg
| SomethingElse
Then render can take a function which accepts a TabControl.Msg and returns a Msg, which, in this case, would be the TabControlMsg constructor.
view model =
div []
[
TabControl.constructor
|> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
|> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
|> TabControl.render TabControlMsg
]
Then you would adjust the code in your module to use that function:
render : (Msg -> msg) -> Model msg -> Html.Html msg
render toMsg model =
let
header = renderTabHeaders model
content = renderSelectedTabContent toMsg model
in
Html.div [] [ header, content ]
renderSelectedTabContent : (Msg -> msg) -> Model msg -> Html.Html msg
renderSelectedTabContent toMsg model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
Html.map toMsg value.content
Nothing ->
Html.text ""
Ah.. so I can map value.content to return a Msg like so
renderSelectedTabContent : Model msg -> Html.Html Msg
renderSelectedTabContent model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
Html.map (\_ -> NoOp) value.content
Nothing ->
Html.text ""
I am trying to mock the awscala.dynamodbv2.DynamoDB.putConditionalMethod
How would one define an expects for a method which is curried and includes a repeated parameter:
putConditional(tableName: String, attributes: (String, Any)*)(cond: Seq[(String, aws.model.ExpectedAttributeValue)]): Unit
Here's what I've got working:
(mockClient.putConditional(_: String, _: (String, Any))(_: Seq[(String, ExpectedAttributeValue)]))
.expects("Data-Identity-Partitions",
*,
Seq(
"DatacenterId" -> exp.isNull,
"InstanceId" -> exp.isNull,
"TTL" -> exp.isNull
))
But this:
(mockClient.putConditional(_: String, _: (String, Any))(_: Seq[(String, ExpectedAttributeValue)]))
.expects("Data-Identity-Partitions",
Seq("DatacenterId" -> 1,
"InstanceId" -> 0,
"TTL" -> System.currentTimeMillis()),
Seq(
"DatacenterId" -> exp.isNull,
"InstanceId" -> exp.isNull,
"TTL" -> exp.isNull
))
results in the following compiler error:
[error] AwsPartitionActorSpec.scala:76: type mismatch;
[error] found : Seq[(String, Any)]
[error] required: org.scalamock.matchers.MockParameter[(String, Any)]
[error] Seq[(String, Any)]("DatacenterId" -> 1,
[error] ^
better late than never i suppose, here's my suggestion:
trait testtrait {
def foo(t: String, a: (String, Any) *): Int
}
"foo" should "work" in {
val m = mock[testtrait]
m.foo _ expects where {
case ("foo", Seq(("bar", 42L), ("baz", "mango"))) => true
case _ => false
} returns 5
m.foo("foo", ("bar", 42L), ("baz", "mango")) should be (5)
}
So I need to decode a json that contains a json array in elm. Here is my model:
type alias ValidationResult =
{ parameter : String
, errorMessage : String
}
type alias ErrorResponse =
{ validationErrors : List ValidationResult }
And here is an example of the json:
{"ValidationErrors": [{"Parameter": "param1","ErrorMessage": "message 1"},{"Parameter": "param2","ErrorMessage": "error message 2"}]}
I've tried to create a ValidationResult decoder, like:
decodeValidationResults : Decoder ValidationResult
decodeValidationResults =
map2 ValidationResult
(at [ "Parameter" ] Json.Decode.string)
(at [ "ErrorMessage" ] Json.Decode.string)
But I don't know how to proceed further.
I am using elm 0.18
You are almost there! You just need a decoder that decodes the ErrorResponse type. To do so, create another decoder that uses a list of the decoder you've already created, assuming the field name is "ValidationErrors":
import Json.Decode exposing (..)
decodeErrorResponse : Decoder ErrorResponse
decodeErrorResponse =
map ErrorResponse
(field "ValidationErrors" (list decodeValidationResults))
One bit of advice: You can use Json.Decode.field instead of Json.Decode.at when there is only a single level. You can rewrite decodeValidationResults as this:
decodeValidationResults : Decoder ValidationResult
decodeValidationResults =
map2 ValidationResult
(field "Parameter" string)
(field "ErrorMessage" string)
If I have a string that looks like the name of a field in a record, can I use it to get the data somehow? Something like :
."name".toKey bill
bill.(asSymbol "name")
-
song =
{ title = "foot", artist = "barf", number = "13" }
fieldsILike : List String
fieldsILike =
[ "title", "artist" ]
val song key =
.key song
foo = List.map (val song) fieldsILike --> should be ["foot", "barf"]
No, but you could use a Dict
import Dict exposing (get ,fromList)
song = fromList [ ("title", "foot"), ("artist", "barf") ]
get "title" song -- Just "foot"
get "artist" song -- Just "barf"
get "test" song -- Nothing
Not the way you want it but you can have a function that pattern matches on a string to access a part of a record. You need to be explicit about what it should do in case you give it something invalid.
import Html exposing (..)
type alias Song = { artist : String, number : String, title : String }
song : Song
song =
{ title = "foot", artist = "barf", number = "13" }
fieldsILike : List String
fieldsILike =
[ "title", "artist" ]
k2acc : String -> (Song -> String)
k2acc key =
case key of
"title" -> .title
"artist" -> .artist
"number" -> .number
_ -> always ""
val : Song -> String -> String
val = flip k2acc
-- `flip` was removed in elm 0.19, so you'll need to
-- do something like the following going forward:
-- val song field = (k2acc field) song
foo = List.map (val song) fieldsILike
main = text <| toString foo
I solved this broblem by adding attribute's name and explicit type declaration in AttrValue type:
type alias TypedRecord =
List Attr
type alias Attr =
( String, AttrValue )
type AttrValue
= String String
| Int Int
| Record (List Attr)
So now i can retrieve attributes by "key" (even "key.key" for nested) from my TypedRecord type:
getAttrByKey : String -> TypedRecord -> Maybe Attr
getAttrByKey searchKey item =
-- imitation of searching for attributes likewise in JS Record
let
checkAttrKey =
\k attr ->
first attr == k
in
case String.split "." searchKey of
[ key ] ->
List.head <| List.filter (checkAttrKey key) item
key :: rest ->
case List.head <| List.filter (checkAttrKey key) item of
Just attr ->
case attr of
( _, Record subAttr ) ->
getAttrByKey (String.join "." rest) subAttr
( _, _ ) ->
Nothing
Nothing ->
Nothing
[] ->
Nothing
And conver it to String by checking Attr type and calling respected Elm String module function:
attrToString : Maybe Attr -> String
attrToString is_attr =
case is_attr of
Nothing ->
""
Just attr ->
case second attr of
String value ->
value
Int value ->
String.fromInt value
Record attrs ->
List.map (\a -> Just a) attrs
|> List.map (\a -> attrToString a)
|> String.join " "
These examples for String Int and Record Elm types, but it is also can be extended fo Float Bool and Array.
You can check src/lib/TypedRecord.elm file for another functions and even Json Decoder in my example app repository