Is there a way to pass the name of a field to a setter function? - record

Here I have several functions that all just set a single field on a model record.
In a more dynamic language, I'd just have a single setter function and pass it the name of the field (as a string) and the value that I want to set on the model object.
Is there a way to pass the name of the field in Elm?
What's the Elm way of doing something like this?
type alias Patient =
{ id : String
, name : String
, dateOfBirth : String
, sex : String
... other fields
}
setPatientName : Patient -> String -> Patient
setPatientName patient value =
{ patient | name = value }
setPatientDateOfBirth : Patient -> String -> Patient
setPatientDateOfBirth patient value =
{ patient | dateOfBirth = value }
setPatientSex : Patient -> String -> Patient
setPatientSex patient value =
{ patient | sex = value }
... many others
-- idx is the index of the patient in the model (which is an array of patients)
-- UpdateCell is a variant of my Msg type, like this: UpdateCell Int (Patient -> String -> Patient) String
onInputHandler : Int -> (Patient -> String -> Patient) -> String -> Msg
onInputHandler idx setter inputText =
UpdateCell idx setter inputText
-- idx is the index of the patient in the model (which is an array of patients)
createTableRow : Int -> Patient -> Html Msg
createTableRow idx patient =
...
, input [ type_ "text", onInput (onInputHandler idx setPatientName), value patient.name ] []
, input [ type_ "text", onInput (onInputHandler idx setPatientDateOfBirth), value patient.dateOfBirth ] []
...
I'm currently using each of these functions as an event handler for input elements. So I need a function that I can use for handling the input event. Ideally, I'd define just a single function and use that single one for all the input elements and pass it the field I want to update on the patient record.

The short answer is "no". But this seems a bit like an XY problem. It's not clear what benefit you are trying to achieve since the full application of such a function would be longer than the equivalent record update expression:
setField "name" patient value
-- vs
{ patient | name = value }
and as a partially applied function is only slightly shorter than the equivalent anonymous function with shortened argument names:
setField "name"
-- vs
\r x -> { r | name = x }
Although the latter is significantly noisier with all the symbols.
There is also a short-hand function for getting a record field:
.name
-- vs
\r -> r.name
So there is some precedent for having a dedicated syntax for setter functions too, but unfortunately there is not. Likely because it would complicate the language, and the syntax in particular, for relatively little benefit. I'm therefore curious about what you're actually trying to accomplish.
Edit after question update:
Putting functions in the Msg is a very bad idea because it goes against the Elm Architecture. It makes the state transition opaque and won't work very well with the debugger. When something goes wrong you can still see the state before and after, but you'll have trouble understanding what happened, and why it happened, because that information is encoded in an opaque function which probably isn't the one it should be.
You'll also have trouble factoring your logic. If you need something to happen only when a certain field updates, you might have to put the logic in the view, or special-case that field by putting the logic for that in update while the rest is in view, for example. Either way, you're on the path to a messy code base.
You should generally use names for messages that describe what happened, not what to do, because that tends to lead to an imperative mindset. Instead of UpdateCell you could call it InputChanged, for example. Then instead of the function you should have an identifier for the field. Ideally a custom type, like InputChanged Name, but even a string will work, though it will be much easier to miss a typo.
So instead of setter functions for each field you'll just case match the message and set the field in the update function:
InputChanged Name value ->
{ patient | name = value }
-- vs
setPatientName : Patient -> String -> Patient
setPatientName patient value =
{ patient | name = value }
Then if you need to clear the sex when the name changes, for example (because reasons...), you can simply do:
InputChanged Name value ->
{ patient | name = value, sex = "" }
The Elm Architecture is good because it makes changes easy and safe, not because it's concise and free of boiler-plate. Good Elm code often has a lot of copy-and-paste, but that's not always bad.

Related

READ TABLE WITH TABLE KEY does not find record

I'm trying to use the class /ui5/cl_json_parser for parsing a JSON string.
The following code snippet reproduces the problem:
REPORT ztest_json_parse.
DATA: input TYPE string,
output TYPE string,
json_parser TYPE REF TO /ui5/cl_json_parser.
input = '{"address":[{"street":"Road","number":"545"},{"street":"Avenue","number":"15"}]}'.
CREATE OBJECT json_parser.
json_parser->parse( input ).
json_parser->print( ).
output = json_parser->value( path = '/address/1/street' ).
WRITE output.
The print method shows the correct parsed JSON string, but the output variable is always empty.
I have traced the code down to the method VALUE of the class /UI5/CL_JSON_PARSER, at line 15, which contains:
read table m_entries into l_entry with table key parent = l_parent name = l_name.
In the debugger, I can see that l_parent = '/address/1' and l_name = 'street', and that the internal table m_entries contains a record with parent = '/address/1' and name = 'street'. Nevertheless the READ statement always returns sy-subrc = 4 and does not find anything.
Can anyone help?
First: Do not use /ui5/cl_json_parser class, it is intended for internal use ONLY and has no reliable documentation
Secondly, here is the sample how you can fetch street value from the first element of your JSON:
DATA(o_json) = cl_abap_codepage=>convert_to( '{"address":[{"street":"Road","number":"545"},{"street":"Avenue","number":"15"}]' ).
DATA(o_reader) = cl_sxml_string_reader=>create( o_json ).
TRY.
DATA(o_node) = o_reader->read_next_node( ).
WHILE o_node IS BOUND.
DATA(op) = CAST if_sxml_open_element( o_node ).
LOOP AT op->get_attributes( ) ASSIGNING FIELD-SYMBOL(<a>).
DATA(attr) = <a>->get_value( ).
ENDLOOP.
IF attr <> 'street'.
o_node = o_reader->read_next_node( ).
ELSE.
DATA(val) = CAST if_sxml_value_node( o_reader->read_next_node( ) ).
WRITE: '/address/1/street =>', val->get_value( ).
EXIT.
ENDIF.
ENDWHILE.
CATCH cx_root INTO DATA(e_txt).
ENDTRY.
As far as I know, there is no class in ABAP that allows fetching single JSON attributes like XPath.
Certainly agree with Suncatcher on avoid UI5 Json parser.
If you dont control/know the structure of the source data, Suncatchers answer is good.
However,
if you know the basic structure of the source JSON and you must, if you plan to access the first address row, fieldname street .
AND you can have the source provided using uppercase variable names then you can use the so called identity transformation.
types: begin of ty_addr,
street type string,
number type string,
end of ty_addr.
types ty_addr_t type STANDARD TABLE OF ty_addr.
DATA: input TYPE string,
ls_addr TYPE ty_addr,
lt_addr type ty_addr_t.
input = '{"ADDRESS":[{"STREET":"Road","NUMBER":"545"},{"STREET":"Avenue","NUMBER":"15"}]}'.
CALL TRANSFORMATION id SOURCE XML input
RESULT address = lt_addr.
read table lt_addr index 1 into ls_addr.
WRITE ls_addr-street.

Filter result from database where row is null

I want to display all rows from database where row at specified column is empty (data is not inserted). To do that, in my onCreateLoader I wrote following code:
override fun onCreateLoader(p0: Int, p1: Bundle?): Loader<Cursor> {
val projection = arrayOf(
WalletEntry._ID,
WalletEntry.KEY_TITLE,
WalletEntry.KEY_MONEY,
WalletEntry.KEY_LAST_DATE,
WalletEntry.KEY_LAST_EXPENSE,
WalletEntry.KEY_LAST_TRANSACTION_TITLE,
WalletEntry.KEY_LOCALES,
WalletEntry.KEY_CURRENCY
)
val selection = "${WalletEntry.KEY_CURRENCY} = ?"
val selectionArgs = arrayOf("")
return applicationContext?.let { context ->
CursorLoader(context,
WalletEntry.CONTENT_URI,
projection,
selection,
selectionArgs,
null)
}!!
}
Where I want to display all results where WalletEntry.KEY_CURRENCY has no signed value, is empty. I tried to specify selectionArgs as null but it neither worked. So, how am I suppose to write selectionArgs to display all results where given row is empty?
To make my situation more clear I'll provide an app target. I'm learning kotlin and decided to write something like "bank" application where you can add different wallets and specify currencies. If you add a new currency it's being instantly added to the database to the column WalletEntry.KEY_CURRENCY. Then I have a list containing all "wallets", in which after adding a new currency an empty extra wallet appears. To avoid that I want to filter results and display only those, which do not have value passed in WalletEntry.CURRENCY column.
If you're looking for NULL values in that database column, I think you might be looking for:
val selection = "${WalletEntry.KEY_CURRENCY} IS NULL"
val selectionArgs = null
If the selectionArgs argument is not nullable, try setting it to emptyArray<String>()

Slick: Pass in column to update

Let's say we have a FoodTable with the following columns: Name, Calories, Carbs, Protein. I have an entry for Name = Chocolate, Calories = 100, Carbs = "10g", and Protein = "2g".
I'm wondering if there's a way to pass in a column name and a new value to update with. For example, I want a method that's like
def updateFood(food, columnName, value):
table.filter(_.name === food).map(x => x.columnName).update(value)
It seems like dynamic columns are not possible with Slick? I want to avoid writing a SQL query because that could lead to security flaws or bugs in the code. Is there really no way to do this?
I also don't want to have to pass in the entire object to update, since ideally, it should be:
I want to update column X to value Y. I should only need to pass in the id of the object, the column, and the value to update to.
I'm wondering if there's a way to pass in a column name and a new value to update with
This depends a little bit on what you want the "column name" to be. To maintain safety, what I'd suggest is having the "column name" be a function that can select a column in your table.
At a high level that would look like this:
// Won't compile, but we'll fix that in a moment
def updateFood[V](food: Food, column: FoodTable => Rep[V], value: V): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
...which we'd call like this:
updateFood(choc, _.calories, 99)
Notice how the "column name" is a function from FoodTable to a column of some value V. Then you provide a value for the V and we do a normal update.
The problem is that Slick knows how to map certain types of values (String, Int, etc) into SQL, but not any kind of value. And the code above won't compile because V is unconstrained.
We can sort of fix that my adding a constraint on V, and it mostly will work:
// Will compile, will work for basic types
def updateFood[V : slick.ast.BaseTypedType](food: Food, column: FoodTable => Rep[V], value: V): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
However, if you have custom column mappings, they won't match the constraint. We need to go another step on and have an implicit shape in scope:
def updateFood[V](food: Food, column: FoodTable => Rep[V], value: V)(implicit shape: Shape[_ <: FlatShapeLevel, Rep[V], V, _]): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
I think of Shape as an extra level of abstraction in Slick, above Rep[V]. The mechanisms of the "shape levels" and other details are not something I can explain because I don't understand them yet! (There is a talk that goes into the design of Slick called "Polymorphic Record Types in a Lifted Embedding" which you can find at http://slick.lightbend.com/docs/)
A final note: if you really want the column name to be a String or something like that, I'd suggest pattern matching the string (or validate in some way) to a FoodTable => Rep function and use that in your SQL. That's going to be tricky because your value V is going to have to match the type of the column you want to update.
Off the top of my head, that could look something like this:
def tryUpdateFood(food: Food, columnName: String, value: String): DBIO[Int] =
columnName match {
case "calories" => updateFood(food, _.calories, value.toInt)
case "carbs" => updateFood(food, _.carbs, value)
// etc...
case unknown => DBIO.failed(new Exception(s"Don't know how to update $unknown columns"))
}
I can imagine better error handling, safer or smarter parsing of the value, but in outline the above could work.
For hints at other ways to approach dynamic problems, take a look at the talk "Patterns for Slick database applications" (also listed at: http://slick.lightbend.com/docs/), and towards the end of the presentation there's a section on "Dynamic sorting".

Force FsCheck to generate NonEmptyString for discriminating union fields of type string

I'm trying to achieve the following behaviour with FsCheck: I'd like to create a generator that will generate a instance of MyUnion type, with every string field being non-null/empty.
type MyNestedUnion =
| X of string
| Y of int * string
type MyUnion =
| A of int * int * string * string
| B of MyNestedUnion
My 'real' type is much larger/deeper than the MyUnion, and FsCheck is able to generate a instance without any problem, but the string fields of the union cases are sometimes empty. (For example it might generate B (Y (123, "")))
Perhaps there's some obvious way of combining FsCheck's NonEmptyString and its support for generating arbitrary union types that I'm missing?
Any tips/pointers in the right direction greatly appreciated.
Thanks!
This goes against the grain of property based testing (in that you explicitly prevent valid test cases from being generated), but you could wire up the non-empty string generator to be used for all strings:
type Alt =
static member NonEmptyString () : Arbitrary<string> =
Arb.Default.NonEmptyString()
|> Arb.convert
(fun (nes : NonEmptyString) -> nes.Get)
NonEmptyString.NonEmptyString
Arb.register<Alt>()
let g = Arb.generate<MyUnion>
Gen.sample 1 10 g
Note that you'd need to re-register the default generator after the test since the mappings are global.
A more by-the-book solution would be to use the default derived generator and then filter values that contain invalid strings (i.e. use ==>), but you might find it not feasible for particularly deep nested types.

NHibernate Like with integer

I have a NHibernate search function where I receive integers and want to return results where at least the beginning coincides with the integers, e.g.
received integer: 729
returns: 729445, 7291 etc.
The database column is of type int, as is the property "Id" of Foo.
But
int id = 729;
var criteria = session.CreateCriteria(typeof(Foo))
criteria.Add(NHibernate.Criterion.Expression.InsensitiveLike("Id", id.ToString() + "%"));
return criteria.List<Foo>();
does result in an error (Could not convert parameter string to int32). Is there something wrong in the code, a work around, or other solution?
How about this:
int id = 729;
var criteria = session.CreateCriteria(typeof(Foo))
criteria.Add(Expression.Like(Projections.Cast(NHibernateUtil.String, Projections.Property("Id")), id.ToString(), MatchMode.Anywhere));
return criteria.List<Foo>();
Have you tried something like this:
int id = 729;
var criteria = session.CreateCriteria(typeof(Foo))
criteria.Add(NHibernate.Criterion.Expression.Like(Projections.SqlFunction("to_char", NHibernate.NHibernateUtil.String, Projections.Property("Id")), id.ToString() + "%"));
return criteria.List<Foo>();
The idea is convert the column before using a to_char function. Some databases do this automatically.
AFAIK, you'll need to store your integer as a string in the database if you want to use the built in NHibernate functionality for this (I would recommend this approach even without NHibernate - the minute you start doing 'like' searches you are dealing with a string, not a number - think US Zip Codes, etc...).
You could also do it mathematically in a database-specific function (or convert to a string as described in Thiago Azevedo's answer), but I imagine these options would be significantly slower, and also have potential to tie you to a specific database.