I am a relative beginner in Elm. I learned how to recur over records, and to create extensible records. My target application has tree-shaped data, that is to be rendered in several tree views.
In order for Elm to allow recursive models, one needs
a Union type for the children field,
a Maybe in that type definition, and
the lazy anonymous function construct if Json decoding is needed.
So far so good, but I cannot combine recursion with extension. Here below is a minimized version of how far I got. This program is capable of rendering either one of the two example types (Folder and Note), but not both. (see lines commented with -- NOTE and -- FOLDER).
The problem is with the kids function. Elm doesn't allow it to produce two different output types. I am stuck with either duplicating the code, or do without record extensions. Both seems like show-stoppers.
Is there a way to get this working with both, extension and recursion, and without code duplication?
Run on Ellie
module Main exposing (main)
import Html exposing (..)
import Maybe
-- MAIN
main = Html.beginnerProgram
{ model = init
, update = update
, view = view
}
-- MODEL
type alias Model =
{ folder : Folder
, note : Note
}
type alias Node a =
{ a | name : String
, children : Children a
}
type alias Folder =
{ name : String
, children : ChildFolders
}
type alias Note =
{ name : String
, children : ChildNotes
}
type Children a = Children a (Maybe (List (Node a)))
type ChildFolders = ChildFolders (Maybe (List Folder))
type ChildNotes = ChildNotes (Maybe (List Note))
-- INIT
init : Model
init = Model
(Folder "Parent F" someFolders)
(Note "Parent N" (ChildNotes Nothing))
someFolders : ChildFolders
someFolders = ChildFolders
( Just
( [ Folder "Child F1" (ChildFolders Nothing)
, Folder "Child F2" (ChildFolders Nothing)
, Folder "Child F3" (ChildFolders Nothing)
]
)
)
-- UPDATE
type Msg = NoOp
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp -> model
-- VIEW
view : Model -> Html msg
view model =
div []
[ viewBranch model.folder -- FOLDER
-- , viewBranch model.note -- NOTE
]
-- viewBranch : (?) -> Html msg
viewBranch node =
uli
( text node.name
:: ( node
|> kids
|> List.map viewBranch
)
)
uli : List (Html msg) -> Html msg
uli items = ul [] [ li [] items ]
-- kids : (?) -> (?)
kids { children } =
case children of
(ChildFolders data) -> Maybe.withDefault [] data -- FOLDER
-- (ChildNotes data) -> Maybe.withDefault [] data -- NOTE
Related
I'm trying to build a UI that allows a user to manipulate a recursive data structure. For example, imagine a visual schema editor or database table editor in which you have plain old types (strings and integers) and compound types made up of those plain types (arrays, structs). In the example below, a Struct_ is like a JavaScript object, where the keys are strings and the values are any type, including nested Array_s and Struct_s.
-- underscores appended to prevent confusion about native Elm types. These are custom to my application.
type ValueType
= String_
| Int_
| Float_
| Array_ ValueType
| Struct_ (List (String, ValueType))
type alias Field =
{ id : Int
, label : String
, hint : String
, hidden : Bool
, valueType : ValueType
}
type alias Schema = List Field
Now to go about building a UI for this I can make a simple recursive function:
viewField : Field -> Html Msg
viewField field =
div []
[ input [ type_ "text", value field.label ] []
, viewValueType field.valueType
]
viewValueType : ValueType -> Html Msg
viewValueType valueType =
let
structField : (String, ValueType) -> Html Msg
structField (key, subtype) =
div []
[ input [type_ "text", placeholder "Key", value key, onInput EditStructSubfieldKey] []
, viewValueType subtype
]
options : List(Html Msg)
options = case valueType of
String_ -> -- string ui
Int_ -> -- int ui
Float_ -> -- float ui
Array_ subtype ->
[ label [] [ text "subtype" ]
, viewValueType subtype
]
Struct_ fields ->
[ label [] [ text "subfields" ]
, List.map structField fields
, button [ onClick AddStructSubfield ] [ text "Add subfield" ]
]
in
div [] options
My issue arises when trying to manipulate my state with this recursive structure. What data structure in a Msgs would accommodate user edits to this structure, adding new fields, subfields, and editing their properties? How would I properly decode that in my update loop?
For example...
type alias Model =
{ fields : List Field }
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
AddStructSubfield _???_ ->
({model | fields = ???}, Cmd.none)
EditStructSubfieldKey _???_ ->
({model | fields = ???}, Cmd.none)
What kind of data would you attach to that AddStructSubfield or EditStructSubfieldKey message (that's passed with the onClick handler to the button above) to properly update your state, specifically when the Struct_ is say, nested inside of another Struct_, nested inside of an Array_? EditStructSubfieldKey, for example, will only contain the new string that the user has entered, but not enough information to address a deeply-nested item.
We do exactly this in our code base, but haven't open sourced the 'library' that supported this. But the answer to your question is that you need to add the notion of a Path to your code and messages.
type Path
= Field: String
| Index: Int
Then your view has to keep updating the path as you descend [Field "f1", Index 3, ...], and your update function needs to be supported by insert, delete,... that take a Path and the existing structure and return you a new one.
I ended up solving this by passing an updater function down the recursive chain. I've simplified this example as much as possible while showing the recursive nature of the updating. This allows for updating infinitely nested structures and lists without worrying about encoding/decoding a path. The downside, I believe, is that my single update Msg will always replace the entire model. I'm not sure about the semantics of how this will affect Elm's equality checking, and if that will produce performance issues in certain applications.
This example can be copy/pasted into https://elm-lang.org/try as-is to see it in action.
import Html exposing (Html, div, input, ul, li, text, select, button, option)
import Html.Attributes exposing (value, type_, selected)
import Html.Events exposing (onInput, onClick)
import Browser
type ValueType
= String_
| Int_
| Array_ ValueType
| Struct_ (List Field)
type alias Field =
{ label : String
, valueType : ValueType
}
type alias Model = Field
main = Browser.sandbox { init = init, update = update, view = view }
init : Model
init =
{ label = "Root Field", valueType = String_ }
type Msg
= UpdateField Field
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateField field ->
field
view : Model -> Html Msg
view model =
let
updater : Field -> Msg
updater field =
UpdateField field
in
div [] [ viewField updater model ]
viewField : (Field -> Msg) -> Field -> Html Msg
viewField updater field =
let
updateLabel : String -> Msg
updateLabel newLabel =
updater {field | label = newLabel}
updateValueType : ValueType -> Msg
updateValueType newValueType =
updater {field | valueType = newValueType}
in
li []
[ input [ type_ "text", value field.label, onInput updateLabel ] [ ]
, viewTypeOptions updateValueType field.valueType
]
viewTypeOptions : (ValueType -> Msg) -> ValueType -> Html Msg
viewTypeOptions updater valueType =
let
typeOptions = case valueType of
String_ ->
div [] []
Int_ ->
div [] []
Array_ subtype ->
let
subUpdater : ValueType -> Msg
subUpdater newType =
updater <| Array_ newType
in
div [] [ div [] [ text "Subtype" ], viewTypeOptions subUpdater subtype ]
Struct_ fields ->
let
fieldAdder : Msg
fieldAdder =
updater <| Struct_ ({label = "", valueType = String_} :: fields)
fieldUpdater : Int -> Field -> Msg
fieldUpdater index newField =
updater <| Struct_ <| replaceInList index newField fields
in
div []
[ ul [] (List.indexedMap (\i -> (viewField <| fieldUpdater i)) fields)
, button [ onClick fieldAdder ] [ text "+ Add Field" ]
]
isArray t = case t of
Array_ _ -> True
_ -> False
isStruct t = case t of
Struct_ _ -> True
_ -> False
stringToType str = case str of
"string" -> String_
"int" -> Int_
"array" -> Array_ String_
"struct" -> Struct_ []
_ -> String_
changeType str =
updater <| stringToType str
in
div []
[ select [ onInput changeType ]
[ option [ value "string", selected <| valueType == String_ ] [ text "String" ]
, option [ value "int", selected <| valueType == Int_ ] [ text "Integer" ]
, option [ value "array", selected <| isArray valueType ] [ text "Array" ]
, option [ value "struct", selected <| isStruct valueType ] [ text "Struct" ]
]
, typeOptions
]
replaceInList : Int -> a -> List a -> List a
replaceInList index item list =
let
head = List.take index list
tail = List.drop (index+1) list
in
head ++ [ item ] ++ tail
My main program has an update function of
update : Msg -> Model -> ( Model, Cmd Msg )
To communicate with sub-components we can add another variant and wrap our messages in a new message
type alias Model =
{ ...
, child : Child.Model
}
type Msg
= ...
| ChildMsg Child.Msg
update msg model =
case msg of
...
ChildMsg childMsg ->
let
( childModel, cmd ) =
Child.update childMsg model.child
updatedModel =
{ model | child = childModel }
childCmd =
Cmd.map ChildMsg cmd
in
( updatedModel, childCmd )
However this seem challenging if the type of my sub-component's update function does not match the parent. Consider a child with a polymorphic update function:
-- PolymorphicChild.elm
update : Msg a -> Model -> ( Model, Cmd (Msg a) )
When running a command from this module, I must wrap it
PolymorphicChild.someCommand : Cmd (Msg Foo)
PolymorphicChild.someCommand
|> Cmd.map PolymorphicChild
However, this produces a Msg (PolymorphicChild.Msg Foo), not the Msg PolymorphicChild.Msg my App is expecting.
The right side of (|>) is causing a type mismatch.
(|>) is expecting the right side to be a:
Cmd (PolyMorphicChild.Msg Foo) -> a
But the right side is:
Cmd Polymorphic.Msg -> Cmd Msg
I tried adding a polymorphic parameter to App.Msg
-- App.elm
type Msg a =
= ..
| PolymorphicChildMsg (PolymorphicChild.Msg a)
But it basically blows up my entire program. Every function involving App.Msg needs to somehow be changed to work with the new child component.
How can I unify the two types and get the two components working together?
I think the problem is that you're leaking too much information in your publicly exposed Msg type. Your use of the type parameter of Msg a seems limited to a known set of types, either an Author, Category, Post, or Tag. From skimming your code, it looks like it will never be anything but one of those four, so the fact that you are abstracting things in this manner should be kept inside of this module rather than exposing it and burdening any other code that may be pulling this in.
I think you need to move the abstraction down a level to avoid parameterizing your public Msg type. I would suggest having four concrete constructors for Msg instead of parameterizing it, and shift the abstraction down to a helper LoadInfo a type:
type alias LoadInfo a =
{ worker : Worker a
, url : Url
, result : Result Http.Error ( Int, List a )
}
type Msg
= LoadPost (LoadInfo Post)
| LoadCategory (LoadInfo Category)
| LoadTag (LoadInfo Tag)
| LoadAuthor (LoadInfo Author)
Assume u have the following recursive record type
type Parent = {
Name : string
Age : int
Children : Child list }
and Child = {
Name : string
Parent : Parent option }
I can easily create instances with
module Builder =
let create name kids =
let rec makeChild kid = { kid with Parent = parent |> Some }
and parent =
{
Name = name
Age = 42
Children = children
}
and children = kids |> List.map makeChild
parent
let createChild name =
{ Child.Name = name; Parent = None }
But when i try to "transform" an existing adult into a parent using "with" like that:
module Builder2 =
let createAdult name age =
{ Parent.Name = name; Age = age; Children = [] }
let create name kids =
let rec makeChild kid = { kid with Parent = parent |> Some }
and parent =
{ (createAdult name 42) with
Children = children
}
and children = kids |> List.map makeChild
parent
let createChild name =
{ Child.Name = name; Parent = None }
I get:
error FS0040: This and other recursive references to the object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning may be suppressed by using '#nowarn "40"' or '--nowarn:40'.
and "Children = children" in the "parent" definition is highlighted.
What am i doing wrong?
Edit:
One more point: when i move the "Builder" (which worked) into a different assembly (e.g. the test assembly) it immediately stops working with:
error FS0261: Recursive values cannot be directly assigned to the non-mutable field 'Children' of the type 'Parent' within a recursive binding. Consider using a mutable field instead.
Edit:
Based on the comments I tried
let create name kids =
let rec makeChild kid = { kid with Parent = parent |> Some }
and adult = createAdult name 42
and parent =
{ adult with Children = children }
and children = kids |> List.map makeChild
but still no luck - the compiler still does not see this usecase similar to the working one :(
First of all, the message you posted in your question is just a warning - it tells you that you can only initialize a recursive value if the construction does not evaluate the entire value immediately (this cannot be done when the first value depends on the second and vice versa).
You can sometimes just ignore the warning, but in your case, the values are actually mutually dependent, so the following gives an error:
Builder2.create "A" [Builder2.createChild "B"]
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
One way to introduce some form of delay is to change the parent to include children as a lazy sequence seq<'T> rather than a fully evaluated list list<'T>:
type Parent = {
Name : string
Age : int
Children : Child seq }
and Child = {
Name : string
Parent : Parent option }
Then you also need to change Builder2 to use Seq.map (to keep things lazy):
let create name kids =
let rec makeChild kid = { kid with Parent = parent |> Some }
and parent =
{ (createAdult name 42) with
Children = children
}
and children = kids |> Seq.map makeChild
Now you still get the warning (which you can turn off), but the following works and creates a recursive value:
let p = Builder2.create "A" [Builder2.createChild "B"]
As an aside, I think it is probably better to avoid recursive values - I suspect that one way reference (parent referencing children, but not the other way round) would let you do what you need - and your code would likely be simpler.
cartermp found and posted the solution here:
https://github.com/Microsoft/visualfsharp/issues/4201
I published a repro here
https://github.com/plainionist/DevNull/tree/master/src/FSharpCopyRecordRecursive
and of course the proposed solution works like a charm
I have a tree like structure of text nodes that might have another text nodes as children, and I need to update one value in it. What's the easiest way to update text node that's somewhere deep in that tree (or it is not in that tree at all)?
In a non-immutable language, I would simply change a value of that item, and that's it, but it's quite tricky in an immutable language like Elm.
type alias Item =
{ id: String
, text: String
, children: ChildItems
}
type ChildItems = ChildItems (List Item)
type alias Model =
{ rootItem: Item
}
updateItem: Item -> Item -> Item
updateItem: rootItem item =
-- TODO
...
update model =
case msg of
UpdateItem item updatedText ->
let
updatedItem = { item | text = updatedText }
in
({ model | rootItem = (updateItem model.rootItem updatedItem) }, Cmd.none)
this is what I came up with
updateItem: Item.Item -> Item.Item -> Item.Item
updateItem rootItem updatedItem =
if rootItem.id == updatedItem.id then
updatedItem
else
case rootItem.children of
Item.ChildItem [] ->
rootItem
Item.ChildItem children ->
let
updatedChildren =
case children of
[] ->
[]
children ->
List.map (\item ->
updateItem rootItem item) children
in
{ rootItem | children = Item.ChildItem updatedChildren }
but I'm getting an Maximum call stack size exceeded error
The reason you're getting the stack overflow is because you are returning rootItem instead of [] in the Item.ChildItems [] case.
I'm going to modify your code a bit because there are some common patterns we can pull out. First off, let's take the underlying tree-ish structure and make that more generic so that it could fit any type of thing, not just Item:
type Node a
= Node a (List (Node a))
That gives us a structure that always has a root node and may have any number of children, each of who may also have any number of children.
If we think about the algorithm you were going for, we can extrapolate a familiar pattern. You have a structure with multiple items and you want an algorithm that visits each item and optionally changes it. That sounds a lot like List.map. It's such a common idiom that it's a good idea to call our generalized function map:
map : (a -> b) -> Node a -> Node b
map f (Node item children) =
Node (f item) (List.map (map f) children)
(Side note: We've just stumbled into functors!)
Since I've taken the idea of children and placed it into the Node type, we need to modify the alias Item like so:
type alias Item =
{ id: String
, text: String
}
Now that we have an Item, it will be helpful to have a function that can update it if the id matches a certain value. Since in the future, you may have more update functions you want to perform, it's best to keep the lookup and ID matching portion separate from the function you actually want to perform:
updateByID : String -> (Item -> Item) -> Item -> Item
updateByID id f item =
if item.id == id then
f item
else
item
Now to perform an update on an item matching the id, anywhere within the tree, you can simply do this:
map (updateByID "someID" (\x -> { x | text = "Woohoo!" })) rootNode
In elm, is something like the below possible
foo : Int -> Html
foo inputNum =
addToNumHistory inputNum ;
display inputNum
where the aim of the above is to execute multiple lines of code?
If not, is this because the above is an example of a side effect?
If something like the above syntax is not possible, how would one go about executing two functions/lines of code simultaneously, as in the above, or as a result of a given input (case branch) ?
Edit
The above is a bad example. The following uses the Elm Architecture:
--Model
type alias Model =
{ number : Int
, numberHistory : List Int
}
type Action
= Number Int
--Update
update : Action -> Model
update action =
case action of
Number num->
addToNumHistory num
addToNumHistory : Int -> Model -> Model
addToNumHistory num modelHistory =
{ model
| number = num
, numberHistory = num :: model.numberHistory
}
--View
view : Signal.Address Action -> Model -> Html
view action model =
div []
[ field
"text"
address
Number model.number
"Enter lucky number here pal!"
model.number
]
Given this, am I right in presuming that to 'execute multiple lines' in such a fashion as to alter an underlying model, one would simply use/extend the model - for example, to effect a change analogous to the following:
--Update
update : Action -> Model
update action =
case action of
Number num->
addToNumHistory num;
addToChangeHistory
one would simply extend the model as follows:
--Model
type alias Model =
{ number : Int
, numberHistory : List Int
, changeHistory : List Date
}
--Update
update : Action -> Model
update action =
case action of
Number num->
addToNumHistoryWithChangeHistory num
addToNumHistoryWithChangeHistory : Int -> Model -> Model
addToNumHistory num modelHistory =
{ model
| number = num
, numberHistory = num :: model.numberHistory
, changeHistory = getCurrentDate :: model.changeHistory
}
getCurrentDate : Date
In this specific case, you don't need to have side-effects.
I've had to add two utility functions to create a functioning example.
onInput to handle 'input' event
parseInt to retrieve Int from a String
The rest is a basic Elm Architecture lifecycle as of 0.16
Please consider this minimal example I made for use with StartApp.Simple:
import Html exposing (text, input, div, Html, Attribute)
import Html.Attributes exposing (value)
import Html.Events exposing (on, targetValue)
import String
import Signal exposing (Address)
import StartApp.Simple as StarApp
--Utils
onInput : Address a -> (String -> a) -> Attribute
onInput address f =
on "input" targetValue (\v -> Signal.message address (f v))
parseInt : String -> Int
parseInt string =
case String.toInt string of
Ok value ->
value
Err error ->
0
--Model
type alias Model =
{ number : Int
, numberHistory : List Int
}
initModel : Model
initModel =
{ number = 0
, numberHistory = []
}
--Update
type Action
= UpdateNumber String
update : Action -> Model -> Model
update action model =
case action of
UpdateNumber num ->
addToNumHistory (parseInt num) model
addToNumHistory : Int -> Model -> Model
addToNumHistory num model =
{ model
| number = num
, numberHistory = num :: model.numberHistory
}
--View
view : Signal.Address Action -> Model -> Html
view address model =
div
[]
[ input
{- On every 'input' event,
grab the value of input field and send to UpdateNumber
-}
[ onInput address UpdateNumber, value (toString model.number) ]
[]
, div [] [ text (toString model.number) ]
, div
[]
( model.numberHistory
|> List.reverse
|> List.map (toString)
|> List.map text
)
]
main : Signal Html
main =
StarApp.start
{ view = view
, update = update
, model = initModel
}