How to encapsulate logic within children like component frameworks? - f#

I'm trying to understand how to create reusable components using the Elmish architecture within F# Bolero by WebSharper (e.g. a reusable validated form input). From all of the examples I've seen, the top level Parent must handle all messages/updates and logic, while children are simply for views. I'm wondering if there's a way around this, whether by having a child handle its own state + messages, and propagating certain messages to the parent (which I've attempted in code below), or if there's another design to handle this.
In my specific case, I'm trying to create a form input component for a users name that validates neither field is empty. I don't like the idea of having a parent handle updating the individual fields FirstName and LastName, it should only care about picking up the Submit message. Handling every message a child produces would results in a ton of boilerplate if you use the child more than once
Note: The code I've provided does not compile as I'm struggling to understand how to implement my intended design
open Elmish
open Bolero
open Bolero.Html
module NameInput =
type Model = { FirstName : string; LastName : string }
type Message =
| ChangeFirstName of string
| ChangeLastName of string
| Submit of Model
let update model msg =
match msg with
| ChangeFirstName s ->
{ model with FirstName = s}, Cmd.none
| ChangeLastName s ->
{ model with LastName = s}, Cmd.none
| Submit m ->
m, Cmd.ofMsg (Submit m)
type Component() =
inherit ElmishComponent<Message, Model>()
let invalidField s = s <> ""
override this.View model dispatch =
let fnClass = if (invalidField model.FirstName) then "invalid" else "valid"
let lnClass = if (invalidField model.LastName) then "invalid" else "valid"
div [] [
label [] [ text "First Name: " ]
input [
attr.``class`` fnClass
on.change (fun e -> update model (ChangeFirstName (unbox e.Value)))
]
label [] [ text "Last Name: " ]
input [
attr.``class`` lnClass
on.change (fun e -> update model (ChangeLastName (unbox e.Value)))
]
button [ on.click (fun _ -> update model (Submit model)) ] [ text "Submit" ]
]
type Message =
| NameSubmitted of NameInput.Message.Submit
type Model = { UserName : NameInput.Model }
let initModel = { UserName = { FirstName = ""; LastName = "" } }
let update msg model =
match msg with
| NameSubmitted name ->
// Greet the user
{ model with UserName = name }, Cmd.none
let view model dispatch =
concat [
ecomp<NameInput.Component,_,_>
model.Username dispatch
]
type MyApp() =
inherit ProgramComponent<Model, Message>()
override this.Program =
Program.mkProgram (fun _ -> initModel, Cmd.none) update view

Thank you #rmunn and #hvester for the references, it helped me get a better understanding of Elmish and was able to come up with a solution. As a reference for anyone else who may stumble across this, here is the solution. InternalMessage does not need to private, it just hides those cases from the main program's update function so one can easily see which messages they need to handle. If it is public though, compiler will give an error if you try to match on an InternalMessage case without first unwrapping the Message into an InternalMessage (so the programmer still easily knows which messages are internal)
module NameInput =
type Model = { FirstName : string; LastName : string }
type private InternalMessage =
| ChangeFirstName of string
| ChangeLastName of string
type Message =
| Internal of InternalMessage
| Submit of Model
let update msg model =
match msg with
| ChangeFirstName s ->
{ model with FirstName = s }
| ChangeLastName s ->
{ model with LastName = s }
type Component() =
inherit ElmishComponent<Model, Message>()
let invalidField s = s <> ""
override this.View model dispatch =
let fnClass = if (invalidField model.FirstName) then "invalid" else "valid"
let lnClass = if (invalidField model.LastName) then "invalid" else "valid"
div [] [
label [] [ text "First Name: " ]
input [
attr.``class`` fnClass
on.change (fun e -> dispatch << Internal << ChangeFirstName <| unbox e.Value)
]
label [] [ text "Last Name: " ]
input [
attr.``class`` lnClass
on.change (fun e -> dispatch << Internal << ChangeLastName <| unbox e.Value)
]
button [ on.click (fun _ -> dispatch <| Submit model) ] [ text "Submit" ]
]
type Model = { Name : NameInput.Model }
let initModel = { Name = { FirstName = ""; LastName = "" } }
type Message =
| NameInput of NameInput.Message
let update message model =
match message with
| NameInput ni ->
match ni with
| NameInput.Internal i ->
{ model with Name = model.Name |> NameInput.update i}
| NameInput.Submit n ->
{ model with Name = n }

Related

In F#, how to update optional nested records?

(Newbie question).
I have been struggling with updating nested records with options in F#. How is this done?
Please assume:
module Visit =
type Model =
{
Id: int
Name: string
}
let initWithTime (t:DateTime) =
{
Id = 0
Name = sprintf "Time is %A" t
}
module Cell =
type Model =
{
Id: int
Visit: Visit.Model option
}
let setVisitFromInteger (i:int, m:Model) =
let appointmentTime =
DateTime.Today + TimeSpan.FromMinutes(float i)
{ m with Visit = { m.Visit
match m.Visit with
| Some x -> x with Name = sprintf "New appointment time %A" appointmentTime
| None -> Visit.initWithTime appointmentTime
}
}
Clearly, setVisitFromInteger is all wrong. How is a nested optional record correctly updated?
TIA
I think you just have a little confusion with the syntax. This is a correct version:
open System
module Visit =
type Model =
{
Id: int
Name: string
}
let initWithTime (t:DateTime) =
{
Id = 0
Name = sprintf "Time is %A" t
}
module Cell =
type Model =
{
Id: int
Visit: Visit.Model option
}
open Cell
let setVisitFromInteger (i:int, m:Model) =
let appointmentTime =
DateTime.Today + TimeSpan.FromMinutes(float i)
{ m with
Visit =
match m.Visit with
| Some x -> { x with Name = sprintf "New appointment time %A" appointmentTime }
| None -> Visit.initWithTime appointmentTime
|> Some
}
Note that the Visit in the record update expression is an option, not a record, so it doesn't need record syntax. However, the record syntax is required inside the pattern match since you're trying to do a nested record update expression.

Is there a way to get Record fields by string in F#?

I would like to get the value of a field in a Record by looking it up with a string.
type Test = { example : string }
let test = { example = "this is the value" }
let getByName (s:string) =
???? //something like test.GetByName(s)
Standard .net reflection should be working fine for such scenario. Record fields are exposed as properties, so you can just query the type with reflection API.
It could look like this:
let getByName (s:string) =
match typeof<Test>.GetProperties() |> Array.tryFind (fun t -> t.Name = s)
with
| Some pi -> Some(pi.GetValue(test))
| None -> None

dispatch method in view for parent child composition

I am trying to understand the way fable is supposed to work with parent child composition. Things are quite easy when it comes to update method, init, and the definition of commands. But the view method and its dispatch method are tricky to find out
In my code, the child is:
module DeploymentView
type DeploymentTypeView =
| DeployContainerView
type Model = {
CurrentView : DeploymentTypeView option
}
type Msg =
| ShowDeployContainer
let init () : Model =
let initialModel = {
CurrentView = None
}
initialModel
let update (msg : Msg) (currentModel : Model) : Model * Cmd<Msg> =
match msg with
| ShowDeployContainer ->
let nextModel = {
currentModel with CurrentView = Some DeployContainerView
}
nextModel, Cmd.none
| _ -> currentModel, Cmd.none
let view (model : Model) (dispatch : Msg -> unit) =
[
Content.content [ Content.Modifiers [ Modifier.TextAlignment (Screen.All, TextAlignment.Left) ] ]
[
Heading.h3 [] [ str ("Deployments: ") ]
]
Columns.columns []
[
Column.column [] [ button "deploy container" (fun _ -> dispatch ShowDeployContainer) ]
]
]
And following this documentation about parent child processing I have define a parent like this one:
module Client
type PortalView =
| DeploymentView of DeploymentView.Model
| ProductAdministrationView
type Model = {
CurrentPortal : PortalView option
}
// The Msg type defines what events/actions can occur while the application is running
// the state of the application changes *only* in reaction to these events
type Msg =
| ShowDeployment
| ShowAdministration
| DeployContainerView of DeploymentView.Msg
// defines the initial state and initial command (= side-effect) of the application
let init () : Model * Cmd<Msg> =
let initialModel = {
CurrentPortal = None
}
initialModel, Cmd.none
let update (msg : Msg) (currentModel : Model) : Model * Cmd<Msg> =
match msg with
| ShowDeployment ->
let nextModel = {
currentModel with CurrentPortal = Some <| DeploymentView(DeploymentView.init())
}
nextModel, Cmd.none
| ShowAdministration ->
let nextModel = {
currentModel with CurrentPortal = Some ProductAdministrationView
}
nextModel, Cmd.none
| DeployContainerView msg' ->
let res, cmd =
match currentModel.CurrentPortal with
| Some(DeploymentView(m)) -> DeploymentView.update msg' m
| _ -> DeploymentView.init(), Cmd.none
{ currentModel with CurrentPortal = Some(DeploymentView(res)) }, Cmd.map DeployContainerView cmd
So far so good, my issue comes when it goes to the rendering of the view itself.
The client view uses a function as follows:
let view (model : Model) (dispatch : Msg -> unit)
where Msg is of type DeploymentView.Msg whereas in the parent view I have access to a dispatch of type Client.Msg -> unit. how can I decompose the parent dispatch to map it to the child dispatch signature?
You can very easily create a dispatch function that conforms to what the child expects by using the >> operator:
DeploymentView.view deploymentViewModel (DeployContainerView >> dispatch)
which is equivalent to doing:
DeploymentView.view deploymentViewModel (fun msg -> msg |> DeployContainerView |> dispatch)
That is, it wraps the child's message in DeployContainerView, then passes that to dispatch.
On another note, it is a common and good convention to use a Msg suffix on constructors used to wrap child msg types. You may want to consider renaming DeployContainerView to DeploymentContainerMsg.

polymorphism with types for common fields

Is this question solvable through functional idiomatic approach, could generics or discriminated unions be the answer?
Is it possible to have polymorphism with passing different types to a function while the function is consuming some common fields.
Idea is to be able to call and reuse the function with different types and use the common attributes/fields.
type Car = {
Registration: string
Owner: string
Wheels: int
customAttribute1: string
customAttribute2: string
}
type Truck = {
Registration: string
Owner: string
Wheels: int
customField5: string
customField6: string
}
let SomeComplexMethod (v: Car) =
Console.WriteLine("Registration" + v.Registration + "Owner:" + v.Owner + "Wheels" + v.Wheels
// some complex functionality
Use
SomeComplexMethod(car)
SomeComplexMethod(truck)
Edit
Following the answer. Is it possible to specify the type of the incoming v since JSON serializer asks for the associated type. If Car was supplied as input, Car will be output, If truck as input truck will be output.
let inline someComplexFun v =
let owner = (^v: (member Owner: string)(v))
let registration = (^v: (member Registration: string)(v))
// process input
use response = request.GetResponse() :?> HttpWebResponse
use reader = new StreamReader(response.GetResponseStream())
use memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadToEnd()))
(new DataContractJsonSerializer(typeof<Car>)).ReadObject(memoryStream) :?> Car
if truck was the input v
(new DataContractJsonSerializer(typeof<Truck>)).ReadObject(memoryStream) :?> Truck
This looks like the classical use case for object inheritance, or perhaps an interface. The difference is that an interface provides only methods (including properties, as they are methods under the hood), while a base object (abstract or concrete) can also provide fields.
In your case an interface might be appropriate:
type IVehicle =
abstract Registration : string
abstract Owner : string
abstract Wheels : int
type Car (_r, _o, _w) =
member customAttribute1 : string
member customAttribute2 : string
interface IVehicle with
member Registration = _r
member Owner = _o
member Wheels = _w
type Truck (_r, _o, _w) =
member customField5 : string
member customField6 : string
interface IVehicle with
member Registration = _r
member Owner = _o
member Wheels = _w
let someComplexMethod (v : IVehicle) =
stdout.WriteLine "Registration: " + v.Registration +
"\nOwner: " + v.Owner +
"\nWheels: " + v.Wheels
EDIT: You can do it without OOP by using a discriminated union, and several record types:
type VehicleBase =
{ Registration : string
Owner : string
Wheels : int }
type CarAttributes =
{ customAttribute1 : string
customAttribute2 : string }
type TruckAttributes =
{ customField5 : string
customField6 : string }
type Vehicle =
| Car of VehicleBase * CarAttributes
| Truck of VehicleBase * TruckAttributes
let processVehicle v =
stdout.WriteLine ("Registration: " + v.Registration +
"\nOwner: " + v.Owner +
"\nWheels: " + v.Wheels)
let someComplexMethod = function
| Car (v, _) -> processVehicle v
| Truck (v, _) -> processVehicle v
What you want is usually called structural (or duck) typing. It can be done via interfaces and object expressions in F# (the accepted way) or via SRTPs. The link #CaringDev provided gives you a quick rundown, but obviously you can find many more examples. Please read this and this. For your specific example it will depend how much control you have over the original types.
It's easy to define another type that includes the fields that you might want. Then your function (I found it interesting btw, that you so much want to go for a functional solution but named it a method...) should just take ANY type that has the required fields/properties. Generics won't work for you in this case, as you need to constrain to a subset of types. But once you have such a function, which uses statically resolved type parameters (SRTPs) you're good to go:
type Car = {
Registration: string
Owner: string
Wheels: int
customAttribute1: string
customAttribute2: string
}
type Truck = {
Registration: string
Owner: string
Wheels: int
customField5: string
customField6: string
}
type Bike = {
Owner: string
Color: string
}
type Vehicle = {
Registration: string
Owner: string
}
let inline someComplexFun v =
let owner = (^v: (member Owner: string)(v))
let registration = (^v: (member Registration: string)(v))
{Registration = registration; Owner = owner}
let car = {Car.Registration = "xyz"; Owner = "xyz"; Wheels = 3; customAttribute1= "xyz"; customAttribute2 = "xyz"}
let truck = {Truck.Registration = "abc"; Owner = "abc"; Wheels = 12; customField5 = "abc"; customField6 = "abc"}
let bike = {Owner = "hell's angels"; Color = "black"}
someComplexFun car //val it : Vehicle = {Registration = "xyz";
//Owner = "xyz";}
someComplexFun truck //val it : Vehicle = {Registration = "abc";
//Owner = "abc";}
someComplexFun bike //error FS0001:
The Vehicle type is defined but it could be anything. Then someConplexFun is defined that can take any type, that has Owner and Registration. It has to be inline and its type signature is:
val inline someComplexFun :
v: ^v -> Vehicle
when ^v : (member get_Owner : ^v -> string) and
^v : (member get_Registration : ^v -> string)
You can pass any type that has Owner and Registration fields, and it will return a Vehicle but of course you can just print it out or return a tuple, etc. For the Bike type, since it doesn't have Registration this function will fail.
There are multiple ways how to solve this problem. Besides the already shown solution, i would use lambda functions or a new datatsructure, to solve this problem.
The idea with a lambda is. Instead of a value you expect a function as an argument that returns the needed value. As an example:
let printInformation registration owner wheels obj =
let reg = registration obj
let owner = owner obj
let wheels = wheels obj
printfn "Registration: %s Owner: %s Wheels: %d" reg owner wheels
let car = {Registration="CAR"; Owner="Me"; Wheels=4; customAttribute1="A"; customAttribute2="B"}
let truck = {Registration="TRUCK"; Owner="You"; Wheels=6; customField5="A"; customField6="B"}
printInformation
(fun (obj:Car) -> obj.Registration)
(fun obj -> obj.Owner)
(fun obj -> obj.Wheels)
car
printInformation
(fun (obj:Truck) -> obj.Registration)
(fun obj -> obj.Owner)
(fun obj -> obj.Wheels)
truck
But the whole idea is that you create such a function once for each type, and use partial application.
let printCar =
printInformation
(fun (obj:Car) -> obj.Registration)
(fun obj -> obj.Owner)
(fun obj -> obj.Wheels)
let printTruck =
printInformation
(fun (obj:Truck) -> obj.Registration)
(fun obj -> obj.Owner)
(fun obj -> obj.Wheels)
printCar car
printTruck truck
Based on this you can create a dispatch function, if you wish
let print obj =
match box obj with
| :? Car as c -> printCar c
| :? Truck as t -> printTruck t
| _ -> failwith "Not Car or Truck"
print car
print truck
print "FooBar"
Now the first ttwo works, but you lose type-safety. The third one compiles, but creates a runtime exception.
Working with lambdas is good enough if you only have 1-3 values, but if you need more values, then it can become cumbersome. Another idea would be to create your own data-type for your function, and provide conversation functions instead.
type Information = {
Registration: string
Owner: string
Wheels: int
}
let printInfo (info:Information) =
printfn "Registration: %s Owner: %s Wheels: %d" info.Registration info.Owner info.Wheels
The advantage. Now you relay on data instead of types. You can basically print any type as long you can create an Information record out of it. So its just a matter of providing a single transformation function for each type.
let carToInformation (car:Car) : Information =
{Registration=car.Registration; Owner=car.Owner; Wheels=car.Wheels}
let truckToInformation (truck:Truck) : Information =
{Registration=truck.Registration; Owner=truck.Owner; Wheels=truck.Wheels}
printInfo (carToInformation car)
printInfo (truckToInformation truck)
Sure, you can once again create a dispatch function based on this idea. Its up to you, but my personal approach i would create an Information type and use explicit conversation instead.
In my opinion it is the easiest to understand, and also the most useful one, as you can easily print any type this way as long you somehow can provide the needed data. And your different types don't need to share a common interface with pre-defined fields that have some special logic in it.

How to get the selected options of a multiselect in Elm?

I've seen what is required for a getting the selected index of a single select but I'm interested in getting all of the selected options from a multi select. I haven't been able to work out how to do this.
I've attempted the following but I suspect the Json decoder is failing. I'm not 100% sure of that though, because the decoding happens in the virtual dom code and any errors there are thrown away.
type Msg
= SetMultipleInts (List Int)
-- I'm not seeing the SetMultipleInts message when I click on the multiselect
view model =
div []
[ select (onSelect SetMultipleInts) (List.map myOption [1..4]) ]
myOption : Int -> Html Msg
myOption id =
option [ value (toString id) ] [ text <| "Option " ++ (toString id) ]
-- I'm not seeing anything happen in the onchange
onMultiSelect : (List Int -> msg) -> List (Html.Attribute msg)
onMultiSelect msg =
[ on "change" (Json.map msg targetSelectedOptions), multiple True ]
targetSelectedOptions : Json.Decoder (List Int)
targetSelectedOptions =
Json.at [ "target", "selectedOptions" ] (Json.list (Json.at [ "value" ] Json.int))
Can I do this without having to use ports?
The decoder fails because event.target.selectedOptions is not a
javascript array. When you cannot use Json.Decode.list, you
can use Json.Decode.keyValuePairs.
Here is the example how you can use it.
You may want to change extractValues below depending
on how you want to react to empty selection and such.
targetSelectedOptions : Json.Decoder (List String)
targetSelectedOptions =
let
maybeValues =
Json.at [ "target", "selectedOptions" ]
<| Json.keyValuePairs
<| Json.maybe ("value" := Json.string)
extractValues mv =
Ok (List.filterMap snd mv)
in Json.customDecoder maybeValues extractValues
In case someone need a multiselect in Elm, I rewrote a fully working example in Elm 0.19:
https://ellie-app.com/g7WrS9cV4zVa1
module Main exposing (main)
import Browser
import Html exposing (..)
import Html.Attributes
import Html.Events
import Json.Decode
type alias Model =
{ value : List ( String, Maybe String ) }
init : Model
init =
{ value = [] }
type Msg
= SetMultipleInts (List ( String, Maybe String ))
update : Msg -> Model -> Model
update msg model =
case msg of
SetMultipleInts value ->
{ model | value = value }
view : Model -> Html Msg
view model =
div []
[ select
[ Html.Events.on "change"
(Json.Decode.map SetMultipleInts targetSelectedOptions)
, Html.Attributes.multiple True
]
(List.map myOption (List.range 1 4))
, div []
[ text <|
Debug.toString
(model
|> .value
|> List.map Tuple.second
|> List.filterMap identity
)
]
]
targetSelectedOptions : Json.Decode.Decoder (List ( String, Maybe String ))
targetSelectedOptions =
Json.Decode.at [ "target", "selectedOptions" ] <|
Json.Decode.keyValuePairs <|
Json.Decode.maybe (Json.Decode.at [ "value" ] Json.Decode.string)
myOption : Int -> Html Msg
myOption id =
option [ Html.Attributes.value (String.fromInt id) ]
[ text <| "Option " ++ String.fromInt id ]
main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}

Resources