Case expression in dependent type not working as expected in Idris - idris

I am playing around porting small bits of PureScript code to Idris where dependent types could be applied and stumbled across a situation where using case inside a dependent type is not working.
Since this is valid (simplified):
data ValidInvoice3 : (ft : FeeType) -> Type where
MkVI3 : ValidInvoice3 ft
why does the following not type check?
-- BROKEN
data ValidInvoice4 : (ft : FeeType) -> Type where
MkVI4 : case ft of
FeeMarkupHidden => ValidInvoice4 ft -- simplified;
FeeExplicit => ValidInvoice4 ft -- more elaborate example below
In case you are interested why I am looking into this: here a bit more elaborate sample code:
module DependentWithCase
data FeeType = FeeMarkupHidden | FeeExplicit
data Fee : (ft : FeeType) -> Type where
MkFee : Fee ft -- simplified
data ValidArticle : (ft : FeeType) -> Type where
MkVA : ValidArticle ft -- simplified
Here now is how the dependent type for “invoice” can be written successfully, indexed on FeeType, using a case expression to decide if a fee is added as an explicit parameter (in the actual code, in this case, the “article” type then has a “markup” part, which I have left out here; this way I can be sure the “markup” is only invoiced once):
data ValidInvoice : (ft : FeeType) -> Type where
MkVI : ValidArticle ft ->
case ft of FeeMarkupHidden => Unit; FeeExplicit => Fee ft;
->
ValidInvoice ft
So you see the invoice data type (and in the actual code, the article type too) depends on the fee type.
But instead of using a case expression buried inside the constructor, I would much rather have it look like this type synonym (which of course doesn’t have a constructor; but this is essentially how it reads in the PureScript code, but instead using a sum type with separate constructors instead of the dependent type here, indexed over FeeType). This is much more readable to me (especially in the actual code).
ValidInvoice2 : (ft : FeeType) -> Type
ValidInvoice2 FeeMarkupHidden = ValidArticle FeeMarkupHidden -> Unit -> ValidInvoice2 FeeMarkupHidden
ValidInvoice2 FeeExplicit = ValidArticle FeeExplicit -> Fee FeeExplicit -> ValidInvoice2 FeeExplicit
So why does ValidInvoice4 not type-check? Am I writing it wrong? Or expecting something to work that just can’t work?

It appears that Idris isn't noticing that either branch of your case statement results in a ValidInvoice4. It also doesn't evaluate functions in a similar position. You can almost always do something slightly different and get the same result though.
Looking over what you're trying accomplish, I'd recommend making Fee a FeeType-indexed Maybe - which simplifies the whole thing:
data FeeType = FeeMarkupHidden | FeeExplicit
data Fee : (ft : FeeType) -> Type where
HidFee : Fee FeeMarkupHidden
ExFee : Nat {- or w/e -} -> Fee FeeExplicit
data ValidArticle : (ft : FeeType) -> Type where
MkVA : ValidArticle ft
data ValidInvoice : (ft : FeeType) -> Type where
MkVI : ValidArticle ft -> Fee ft -> ValidInvoice ft

Related

Calculated date in WHERE condition of CDS view

I'm trying to get a list of valid system status for the notification object, in order to not check all the notifications in the table, I want to execute the selection by checking only the last 2 years of data.
Maybe there is a better solution to my problem, but I'm still curious about this technical limitation. To my knowledge, the system status in SAP are kind of hardcoded and can't be determined per object via any table (SAP could add new system status any moment).
I tried to create the below CDS view, but the function dats_add_months can't be used in the where condition, is there a solution to that? Notice that 7.50 doesn't have session parameter for system date so I use an environment variable:
define view ZNOTIF_SYS_STATUS
with parameters sydat : abap.dats #<Environment.systemField: #SYSTEM_DATE
as select distinct from qmel as notif
inner join jest as notif_status on notif_status.objnr = notif.objnr
and notif_status.inact = ''
inner join tj02t as sys_status on sys_status.istat = notif_status.stat
and sys_status.spras = $session.system_language
{
key sys_status.txt04 as statusID,
sys_status.txt30 as description
} where notif.erdat > dats_add_months($parameters.sydat, -12, '') //THIS CAN'T BE USED!!
Putting built-in functions in RHS position of WHERE is supported only since 7.51 and you have 7.50 as you said. That is why it works for Haojie and not for you.
What can be done here? Possible option is CDS table function which consumes AMDP-class. Consider this sample:
Table function
#EndUserText.label: 'table_func months'
define table function ZTF_MONTHS
with parameters
#Environment.systemField : #SYSTEM_DATE
p_datum : syst_datum
returns {
mandt : abap.clnt;
num : qmnum;
type : qmart;
}
implemented by method zcl_cds_qmel=>get_last_two_years;
AMDP
CLASS zcl_cds_qmel DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
TYPES: tt_statuses TYPE STANDARD TABLE OF qmel.
CLASS-METHODS get_last_two_years FOR TABLE FUNCTION ztf_months.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_cds_qmel IMPLEMENTATION.
METHOD get_last_two_years BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY.
twoyrs := add_months (CURRENT_DATE,-12)
RETURN SELECT mandt, qmnum AS num, qmart AS type FROM qmel WHERE erdat > twoyrs;
ENDMETHOD.
ENDCLASS.
It is very simplified compared to your original task but gives you the idea how to do this.

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

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.

Converting MATNR via conversion exit fails for custom table

I'm trying to select the latest date of a material movement from MSEG, but the material needs to be in stock and that is sourced from a bespoke table which uses unconverted Material names.
I've tried using the CALL FUNCTION 'CONVERSION_EXIT_MATN1_OUTPUT' (and INPUT) but I'm not sure how to properly utilize it in a select statement.
IF MSEG-BWART = '101'.
CALL FUNCTION 'CONVERSION_EXIT_MATN1_OUTPUT'
EXPORTING
INPUT = ZBJSTOCK-ZMAT10
IMPORTING
OUTPUT = WA2-MATNR.
SELECT MAX( BUDAT_MKPF )
FROM MSEG
INTO GRDT
WHERE MATNR = WA2-MATNR.
ENDIF.
Currently, WA2-MATNR seems to come out as blank and therefore is not pulling the data from MSEG.
You shouldn't use conversion exit here. Material number in SAP tables lays in internal (INPUT) format and you are converting it into readable format (OUTPUT) in order to query table. It is obvious you will not find anything.
Sample:
MATNR internal format (for OUT exits)
000000000000025567
MATNR external format (for IN exits)
25567
Conversions cases:
000000000000025567 -> CONVERSION_EXIT_MATN1_OUTPUT -> 25567 ✔️
25567 -> CONVERSION_EXIT_MATN1_OUTPUT -> 25567 ❌ nothing changes
25567 -> CONVERSION_EXIT_MATN1_INPUT -> 000000000000025567 ✔️
000000000000025567 -> CONVERSION_EXIT_MATN1_INPUT -> 000000000000025567 ❌ nng changes
Most likely, your bespoke table contains faulty material number so exit doesn't return anything. Or material number in format that exit doesn't expect, e.g. 19 characters instead of 18 and so on.
P.S.
Just for you info, you can use templates for conversion. It is the same as calling conversion FMs
SELECT SINGLE matnr FROM mara INTO #DATA(l_matnr) WHERE EXISTS ( SELECT * FROM mseg WHERE matnr = mara~matnr ).
l_matnr = | { l_matnr ALPHA = OUT } |. <<-- templating
SELECT SINGLE matnr, budat_mkpf
FROM mseg
INTO #DATA(l_mkpf)
WHERE matnr = #l_matnr.
In the above sample SELECT will not return anything, but if you comment out the template line it will.
Unless you’ve added code into the user exits in that function, it’s not going to do what you want. The standard purpose of that function is to format the material number for display on the screen.
The quickest way to do what you want is to select from your custom table to do the lookup.
That said, there is a user exit in that function where you can code the select to do the lookup. The extra benefit of doing that is that your users will be able to type in the legacy material number and the system will switch it with the new one.

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.

Partial SQL insert in haskelldb

I just started a new project and wanted to use HaskellDB in the beginning. I created a database with 2 columns:
create table sensor (
service text,
name text
);
..found out how to do the basic HaskellDB machinery (ohhh..the documentation) and wanted to do an insert. However, I wanted to do a partial insert (there are supposed to be more columns), something like:
insert into sensor (service) values ('myservice');
Translated into HaskellDB:
transaction db $ insert db SE.sensor (SE.service <<- (Just $ senService sensor))
But...that simply doesn't work. What also does not work is if I specify the column names in different order, which is not exactly conenient as well. Is there a way to do a partial insert in haskelldb?
The error codes I get are - when I just inserted a different column (the 'name') as the first one:
Couldn't match expected type `SEI.Service'
against inferred type `SEI.Name'
Expected type: SEI.Intsensor
Inferred type: Database.HaskellDB.HDBRec.RecCons
SEI.Name (Expr String) er
When using functional dependencies to combine
Database.HaskellDB.Query.InsertRec
(Database.HaskellDB.HDBRec.RecCons f (e a) r)
(Database.HaskellDB.HDBRec.RecCons f (Expr a) er),
etc..
And when I do the 'service' as the first - and only - field, I get:
Couldn't match expected type `Database.HaskellDB.HDBRec.RecCons
SEI.Name
(Expr String)
(Database.HaskellDB.HDBRec.RecCons
SEI.Time
(Expr Int)
(Database.HaskellDB.HDBRec.RecCons
SEI.Intval (Expr Int) Database.HaskellDB.HDBRec.RecNil))'
against inferred type `Database.HaskellDB.HDBRec.RecNil'
(I have a couple of other columns in the table)
This looks really like 'by design', unfortunately :(
You're right, that does look intentional. The HaskellDB.Query docs show that insert has a type of:
insert :: (ToPrimExprs r, ShowRecRow r, InsertRec r er) => Database -> Table er -> Record r -> IO ()
In particular, the relation InsertRec r er must hold. That's defined elsewhere by the recursive type program:
InsertRec RecNil RecNil
(InsertExpr e, InsertRec r er) => InsertRec (RecCons f (e a) r) (RecCons f (Expr a) er)
The first line is the base case. The second line is an inductive case. It really does want to walk every element of er, the table. There's no short-circuit, and no support for re-ordering. But in my own tests, I have seen this work, using _default:
insQ db = insert db test_tbl1 (c1 <<- (Just 5) # c2 << _default)
So if you want a partial insert, you can always say:
insC1 db x = insert db test_tbl1 (c1 <<- (Just x) # c2 << _default)
insC2 db x = insert db test_tbl2 (c1 << _default # c2 <<- (Just x))
I realize this isn't everything you're looking for. It looks like InsertRec can be re-written in the style of HList, to permit more generalization. That would be an excellent contribution.