I am trying to use purescript-lens to update a property of a nested record. However, when I compose lenses to get to the property, I get the following type error:
Warning: Error at src/Main.purs line 150, column 38 - line 152, column 3:
Error in declaration performAction
Cannot unify { description :: u24500 | u24501 } with Main.Item. Use --force to continue.
I'm relatively new to lenses and purescript, so it's probably something simple and obvious.
The relevant code that produces this error follows (yes, it's based on purescript-thermite-todomvc):
data Action
= NewItem String String String String String
| RemoveItem Index
| SetEditText String
| DoNothing
data Item = Item
{ description :: String
, keywords :: String
, link_title :: String
, profile :: String
, title :: String
}
data State = State
{ item :: Item
, editText :: String
}
_State :: LensP State { item :: _, editText :: _ }
_State f (State st) = State <$> f st
item :: forall r. LensP { item :: _ | r } _
item f st = f st.item <#> \i -> st { item = i }
editText :: forall r. LensP { editText :: _ | r } _
editText f st = f st.editText <#> \i -> st { editText = i }
itemDescription :: forall r. LensP { description :: _ | r } _
itemDescription f it = f it.description <#> \d -> it { description = d }
performAction :: T.PerformAction _ Action (T.Action _ State)
performAction _ action = T.modifyState (updateState action)
where
updateState :: Action -> State -> State
updateState (NewItem s1 _ _ _ _) = _State .. item .. itemDescription .~ s1
updateState (SetEditText s) = _State .. editText .~ s
updateState DoNothing = id
The property I'm trying to update is st.item.description and the above error refers to the line that starts "updateState (NewItem..." Curiously, the same error is also reported for the next line.
Any ideas on how to resolve the type error?
Thanks
I've "fixed" this by making the types of the lenses less general. I've also based the lenses on the syntax that Phil uses in his "24 days" review of purescript-lens. I find that syntax less opaque.
item :: LensP State Item
item = lens (\(State st) -> st.item) (\(State st) item -> State (st { item = item }))
editText :: LensP State String
editText = lens (\(State st) -> st.editText) (\(State st) editText -> State (st { editText = editText }))
itemDescription :: LensP Item String
itemDescription = lens (\(Item it) -> it.description) (\(Item it) description -> Item (it { description = description }))
Again, to keep the lens types simple, I've stripped out the use of the _State lens in performAction:
performAction :: T.PerformAction _ Action (T.Action _ State)
performAction _ action = T.modifyState (updateState action)
where
updateState :: Action -> State -> State
updateState (NewItem s1 _ _ _ _) = \st -> st # item..itemDescription.~ s1
updateState (SetEditText s) = \st -> st # editText.~ s
updateState DoNothing = id
I'm sure there's a more elegant, general, and complete solution, but that will have to wait until I understand purescript-lens better.
Related
I have a type defined as follows:
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
and an instance of this type:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
I want to extract the field names and values from this record type using reflection like the following:
let getFieldValueMappingOfARecordType (data: 'T) : seq<string * obj> =
let fieldValueMapping =
data.GetType()
|> FSharpType.GetRecordFields
|> Seq.map (
fun propertyInfo ->
(propertyInfo.Name, data |> propertyInfo.GetValue)
)
fieldValueMapping
Then invoking the above function with the instance of employee type
let mapping = getFieldValueMappingOfARecordType emp
|> Seq.toList
gives us:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", Some "E")]
So far it's working well with non-optional type. But in case of optional types, it's returning the value of the field as either Some value or None. What I would like to do is to get the value when the field has Some value or make it null when it's None.
Essentially like the follwing:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", "E")]
Or if the employee instance is like the following:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = None
}
Then,
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", null)]
This is what I have so far (non-working code):
open System
open Microsoft.FSharp.Reflection
open System.Reflection
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
let getSomeOrNull (t: Type) (o: obj) =
let opt = typedefof<option<_>>.MakeGenericType [| t |]
match (o :?> opt) with
| Some s ->
s
| None ->
null
let getValues (data: 'T) =
let values =
data.GetType()
|> FSharpType.GetRecordFields
|> Array.map (
fun propertyInfo ->
let value =
data |> propertyInfo.GetValue
let isOption =
propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>
match isOption with
| true ->
(propertyInfo.Name, (getSomeOrNull propertyInfo.PropertyType value))
| false ->
(propertyInfo.Name, value)
)
values
getValues emp
|> printfn "%A"
I think the only way to do this is with reflection:
let getSomeOrNull (t: Type) (o: obj) =
if isNull o then null
else t.GetProperty("Value").GetValue(o)
I think this should do the trick:
let getSomeOrNull (o: obj) =
match o with
| :? Option<string> as o -> a |> Option.toObj > box
| _ -> null
So, I've been fighting with the compiler on a type error.
This code was working as of a couple days ago.
Type misMatch for App level message
App.fs snippets
module App =
type Msg =
| ConnectionPageMsg of ConnectionPage.Msg
| CodeGenPageMsg of CodeGenPage.Msg
//...
let update (msg : Msg) (model : Model) =
match msg with
| ConnectionPageMsg msg ->
let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
{ model with ConnectionPageModel = m }, cmd
| CodeGenPageMsg msg ->
let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
{ model with CodeGenPageModel = m }, cmd
//...
let runner =
Program.mkProgram init update view
|> Program.withConsoleTrace
|> XamarinFormsProgram.run app
I've added explicit aliases and the original error :
Type mismatch. Expecting a
'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'
but given a
'App.Msg -> App.Model -> App.Model * Cmd<Msg>'
The type 'App.Msg' does not match the type 'Msg'
Became these:
App.fs(50,50): Error FS0001: The type 'PocoGen.Page.ConnectionPage.Msg' does not match the type 'PocoGen.Page.CodeGenPage.Msg' (FS0001) (PocoGen)
App.fs(32,32): Error FS0001: Type mismatch.
Expecting a 'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'
but given a 'App.Msg -> App.Model -> App.Model * Cmd<Msg>'
The type 'App.Msg' does not match the type 'Msg' (FS0001) (PocoGen)
Other remarks
Right before these errors started appearing I was working on converting a blocking syncronous call to a async command in the ConnectionTestPage and removed the calling code for the cmd hoping that would fix it. (It did not)
ConnectionPage.fs Messages
type Msg =
| UpdateConnectionStringValue of string
| UpdateConnectionStringName of string
| TestConnection
| TestConnectionComplete of Model
| SaveConnectionString of ConnectionStringItem
| UpdateOutput of string
ConnectionPage.fs update
let update (msg : Msg) (m : Model) : Model * Cmd<Msg> =
match msg with
| UpdateConnectionStringValue conStringVal ->
{ m with
ConnectionString =
{ Id = m.ConnectionString.Id
Name = m.ConnectionString.Name
Value = conStringVal }
CurrentFormState =
match hasRequredSaveFields m.ConnectionString with
| false -> MissingConnStrValue
| _ -> Valid }, Cmd.none
| UpdateConnectionStringName conStringName ->
{ m with
ConnectionString =
{ Id = m.ConnectionString.Id
Name = conStringName
Value = m.ConnectionString.Value }
CurrentFormState =
match hasRequredSaveFields m.ConnectionString with
| false -> MissingConnStrValue
| _ -> Valid }, Cmd.none
| UpdateOutput output -> { m with Output = output }, Cmd.none
| TestConnection -> m, Cmd.none
| TestConnectionComplete testResult -> { m with Output = testResult.Output + "\r\n" }, Cmd.none
| SaveConnectionString(_) -> saveConnection m, Cmd.none
I've played with the Fsharp Version (because incidentally I did update to 4.7.2 a bit before getting this error)
The Full Repo:
https://github.com/musicm122/PocoGen_Fsharp/tree/master/PocoGen
The two branches of the match inside App.update have different types. The first branch has type App.Model * Cmd<ConnectionPage.Msg> and the second page has type App.Model * Cmd<CodeGenPage.Msg>.
You can't generally do that. This, for example, wouldn't compile:
let x =
match y with
| true -> 42
| false -> "foo"
What type is x here? Is it int or is it string? Doesn't compute. A match expression has to have all branches of the same type.
To convert Cmd<ConnectionPage.Msg> into a Cmd<App.Msg> (by wrapping the message in ConnectionPageMsg) you can use Cmd.map:
let update (msg : Msg) (model : Model) =
match msg with
| ConnectionPageMsg msg ->
let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
{ model with ConnectionPageModel = m }, Cmd.map ConnectionPageMsg cmd
| CodeGenPageMsg msg ->
let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
{ model with CodeGenPageModel = m }, Cmd.map CodeGenPageMsg cmd
Let's pretend we have the following types:
type Message {
text : Option<string>
}
type Update {
msg : Option<Message>
}
How do I match it in one line, like in C# using null-conditional operator i.e update?.msg?.text ?
Is there a way to do it like this?:
match msg, msg.text with
| Some msg, Some txt -> ...
| None -> ...
because I don't want to be writing 2 nested match expressions.
You have two Record types (missing the "=" in your example). To match some variable of Update type, you could do as follows:
type Message = { text : Option<string> }
type Update = { msg : Option<Message> }
let u = {msg = Some({text = Some "text"})}
//all 3 possible cases
match u with
| {msg = Some({text = Some t})} -> t
| {msg = Some({text = None})} -> ""
| {msg = None} -> ""
I can't figure out why I'm receiving the following error when setting the ItemsSource property on a picker control:
"Object must implement IConvertible."
XAML
My XAML is as follows:
** Error: "Object must implement IConvertible." **
<Picker ItemsSource="{Binding Roles}" />
What's interesting, is that I can set the ItemsSource property for a listview control with no issues in the same file.
Different control but same ItemsSource binding in same XAML file:
** This works! **
<ListView ItemsSource="{Binding Roles}" />
In conclusion, why do I only receive an exception when setting the ItemsSource for a Picker but not for a ListView control?
Appendix:
//--------------------------------
// Initializing viewmodel instance
//--------------------------------
var viewmodel = new Dashboard(user, _account.Query);
var page = new DashboardPage() { BindingContext = viewmodel };
await viewmodel.LoadAsync();
.
.
.
//---------------------
// Viewmodel definition
//---------------------
type Dashboard(user:AuthenticatedUser, query:Query) =
inherit ViewModelBase()
let mutable name = sprintf "%s %s" user.FirstName user.LastName
let mutable primaryRole = user.Role
let mutable roles = seq []
let mutable role = ""
let mutable positions = seq []
let mutable position = ""
let mutable incidents = seq []
let mutable incident = ""
let mutable accountabilities = seq []
let mutable accountability = ""
member x.Name with get() = name
and set(v) = name <- v
base.NotifyPropertyChanged(<# x.Name #>)
member x.PrimaryRole with get() = primaryRole
and set(v) = primaryRole <- v
base.NotifyPropertyChanged(<# x.PrimaryRole #>)
member x.Roles with get() = roles
and set(v) = roles <- v
base.NotifyPropertyChanged(<# x.Roles #>)
member x.Role with get() = role
and set(v) = role <- v
base.NotifyPropertyChanged(<# x.Role #>)
member x.Positions with get() = positions
and set(v) = positions <- v
base.NotifyPropertyChanged(<# x.Positions #>)
member x.Position with get() = position
and set(v) = position <- v
base.NotifyPropertyChanged(<# x.Position #>)
member x.Incidents with get() = incidents
and set(v) = incidents <- v
base.NotifyPropertyChanged(<# x.Incidents #>)
member x.Incident with get() = incident
and set(v) = incident <- v
base.NotifyPropertyChanged(<# x.Incident #>)
member x.Accountabilities with get() = accountabilities
and set(v) = accountabilities <- v
base.NotifyPropertyChanged(<# x.Accountabilities #>)
member x.Accountability with get() = accountability
and set(v) = accountability <- v
base.NotifyPropertyChanged(<# x.Accountability #>)
member x.LoadAsync() =
async {
do! async {
match! user |> query.Roles with
| Error _ -> failwith "Query for 'roles' failed"
| Ok result -> x.Roles <- result
}
do! async {
match! user |> query.Positions with
| Error _ -> failwith "Query for 'positions' failed"
| Ok result -> x.Positions <- result
}
do! async {
match! user |> query.Incidents with
| Error _ -> failwith "Query for 'incidents' failed"
| Ok result -> x.Incidents <- result
}
do! async {
match! user |> query.Accountabilities with
| Error _ -> failwith "Query for 'accountabilities' failed"
| Ok result -> x.Accountabilities <- result
}
} |> Async.StartAsTask
I had to use an ObservableCollection instead of IEnumerable (i.e. seq).
Note: I'm not sure why ListView worked without this.
let mutable roles = ObservableCollection<string>() // Updated
...
member x.LoadAsync() =
async {
do! async {
match! user |> query.Roles with
| Error _ -> failwith "Query for 'roles' failed"
| Ok result -> x.Roles <- ObservableCollection<string>(result) // Updated
}
Trying to learn Elm, and it's pretty hard :)
What I'm trying to accomplish:
I have a model that's a record with a number of key value pairs. I want to populate those keys with values from a list of strings.
module Main exposing (..)
import List
import Html exposing (Html, program, div, text)
type alias Model =
{ one: String
, two: String
, three: String
}
fakeData: List String
fakeData = ["foo", "bar", "baz", "bad", "baf"]
populate: Model -> List String -> Model
populate model data =
case List.head data of
Just str ->
case model.one of
"" ->
let updatedModel =
{ model | one = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
case model.two of
"" ->
let updatedModel =
{ model | two = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
case model.three of
"" ->
let updatedModel =
{ model | three = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
model
Nothing ->
model
init: ( Model, Cmd Msg)
init =
( populate { one = "", two = "", three = "" } fakeData, Cmd.none )
type Msg =
NoOp
view: Model -> Html Msg
view model =
div []
[ text (toString model) ]
update: Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
subscriptions: Model -> Sub Msg
subscriptions model =
Sub.none
main: Program Never Model Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
This program prints out:
{ one = "foo", two = "bar", three = "baz" }
I guess I have a hard time figuring out how to make this code less repetitive and easier to reason about. What if I have 20 keys in the model that all need to be populated? The above code would get pretty insane.
You could use pattern matching on the list instead:
populate : Model -> List String -> Model
populate model data =
case data of
one :: two :: three :: _ ->
{ model | one = one, two = two, three = three }
_ ->
model