How to get a particular key value pair from elm object - elm

I have a elm object which returns value are as follows
type alias Info =
{ name : String
, stdId : stdKey
, pemId : PermanentKey
}
This info lies in a page named as class.elm
in another page i want to use the std key alone to do a if else comparison.
i tied to assign the stdKey to a variable like as follows
uniKey = class.Info.stdId
But the elm doesn't accept this way .
Kindly help.

Assuming you intend to create a Info value in the Class.elm file and use it in an Other.elm file :
file Class.elm
module Class exposing (Info)
type alias Info =
{ name : String
, stdId : StdKey
, pemId : PermanentKey
}
type alias StdKey = String
type alias PermanentKey = String
info : Info
info =
{ name = "name"
, stdKey = "valueForStdKey"
, pemKey = "valueForPemKey"
}
file Other.elm
module Other exposing (..)
import Class
uniKey : Class.StdKey
uniKey = Class.info.stdKey
Alternative
file Alt.elm
module Alt exposing (..)
import Class exposing (StdKey, PermanentKey, info)
uniKey : StdKey
uniKey = info.stdKey

Elm automatically generates a .stdId function that works on any record with that field
.stdId info == info.stdId

Related

Dynamic form with composable-form

I'm trying to implement a dynamic form in Elm 0.19 using hecrj/composable-form.
I receive a json with the fields, their descriptions, etc, so I don't know beforehand how many fields it will have.
So the traditional way of defining a form:
Form.succeed OutputValues
|> Form.append field1
|> Form.append field2
doesn't work because I don't know the OutputValues structure beforehand.
I've seen there is a function Form.list which looks like a promising path, though it seems to expect all fields equal, which is not my case, I may have a text field and a select field for example.
Is there any straight forward way of doing this with this library?
Thank you.
The form library doesn't explicitly support what you're trying to do, but we can make it work!
tldr;
Here's my example of how you can take JSON and create a form: https://ellie-app.com/bJqNh29qnsva1
How to get there
Form.list is definitely the promising path. You're also exactly right that Form.list requires all of the fields to be of the same type. So let's start there! We can make one data structure that can hold them by making a custom type. In my example, I called it DynamicFormFieldValue. We'll make a variant for each kind of field. I created ones for text, integer, and select list. Each one will need to hold the value of the field and all of the extras (like title and default value) to make it show up nicely. This will be what we decode the JSON into, what the form value is, and what the form output will be. The resulting types looks like this:
type alias TextFieldRequirements =
{ name : String
, default : Maybe String
}
type alias IntFieldRequirements =
{ name : String
, default : Maybe Int
}
type alias SelectFieldRequirements =
{ name : String
, default : Maybe String
, options : List ( String, String )
}
type DynamicFormFieldValue
= TextField String TextFieldRequirements
| IntField Int IntFieldRequirements
| SelectField String SelectFieldRequirements
To display the form, you just need a function that can take the form value and display the appropriate form widget. The form library provides Form.meta to change the form based on the value. So, we will pattern match on the custom type and return Form.textField, Form.numberField, or Form.selectField. Something like this:
dynamicFormField : Int -> Form DynamicFormFieldValue DynamicFormFieldValue
dynamicFormField fieldPosition =
Form.meta
(\field ->
case field of
TextField textValue ({ name } as requirements) ->
Form.textField
{ parser = \_ -> Ok field
, value = \_ -> textValue
, update = \value oldValue -> TextField value requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
}
}
IntField intValue ({ name } as requirements) ->
Form.numberField
{ parser = \_ -> Ok field
, value = \_ -> String.fromInt intValue
, update = \value oldValue -> IntField (Maybe.withDefault intValue (String.toInt value)) requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
, step = Nothing
, min = Nothing
, max = Nothing
}
}
SelectField selectValue ({ name, options } as requirements) ->
Form.selectField
{ parser = \_ -> Ok field
, value = \_ -> selectValue
, update = \value oldValue -> SelectField value requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
, options = options
}
}
)
Hooking this display function up is a bit awkward with the library. Form.list wasn't designed with use-case in mind. We want the list to stay the same length and just be iterated over. To achieve this, we will remove the "add" and "delete" buttons and be forced to provide a dummy default value (which will never get used).
dynamicForm : Form (List DynamicFormFieldValue) (List DynamicFormFieldValue)
dynamicForm =
Form.list
{ default =
-- This will never get used
TextField "" { name = "", default = Nothing }
, value = \value -> value
, update = \value oldValue -> value
, attributes =
{ label = "Dynamic Field Example"
, add = Nothing
, delete = Nothing
}
}
dynamicFormField
Hopefully the ellie example demonstrates the rest and you can adapt it to your needs!

Function with extensible record parameter passed as a parameter cannot be used from within a case statement

I'm trying to extract some information from some records using extensible records. If I create a function taking the extensible record type and returning a string and use that within a case statement then there are no issues (namedToString, in the below example). However, if I attempt to use a function passed as a parameter (stringFromNamed) I will get an error complaining:
This business value is a:
Business
But stringFromNamed needs the 1st argument to be:
Named a -> String (edited)
from the example code below:
type alias Named a =
{ a | name : String }
type alias Person =
Named { address : String }
type alias Business =
Named { employeeCount : Int }
type Change
= PersonUpdate Person
| BusinessUpdate Business
namedToString : Named a -> String
namedToString changeFields =
changeFields.name
changeToString : (Named a -> String) -> Change -> String
changeToString stringFromNamed change =
case change of
PersonUpdate person ->
-- This works
namedToString person
BusinessUpdate business ->
-- This will cause the error
stringFromNamed business
This example includes just the required code but a more complete example can be found at https://ellie-app.com/77nCPLh55j3a1
What is causing the issue and how can I achieve my goal of passing in a function which will extract some information from an extensible record?
I discovered this issue in the Elm compiler repo: https://github.com/elm/compiler/issues/1959
This appears to be a bug in the compiler which can be worked around if you just want to pass an external record of one type to a function parameter. You can do this by removing the type signature of the higher order function, changeToString in the example above.
Unfortunately, if we wanted to perform the same action from the function parameter, e.g. stringFromNamed, on every case the error will return so another workaround must be found.
The workaround I'm using is to create a new record type which includes exactly the fields from the extensible record and then creating an instance of this from the fields of the other records adhering to the extensible record type. Not a big problem with only a couple of cases and an extensible record with only one field but this doesn't scale especially well. Example below:
type alias Named a =
{ a | name : String }
type alias OnlyNamed =
Named {}
type alias Person =
Named { address : String }
type alias Business =
Named { employeeCount : Int }
type Change
= PersonUpdate Person
| BusinessUpdate Business
type Msg
= UpdateCurrentChange Change
namedToString : Named a -> String
namedToString changeFields =
changeFields.name
changeToString : (OnlyNamed -> String) -> Change -> String
changeToString stringFromNamed change =
case change of
PersonUpdate { name } ->
stringFromNamed { name = name }
BusinessUpdate { name } ->
stringFromNamed { name = name }
I think the issue is that the function you're passing in takes Named a, and Elm doesn't know what the a refers to, so it ends up deciding it's a type mismatch. If you change the type annotation to (Named Business -> String) -> Change -> String it will compile and work.

Elm: accessing common fields in union

I am trying to model a type as a union where each member of that union has properties in common with all other members.
I am currently achieving this like so:
type alias File = {
name : String
}
type CommonFileState extra = CommonFileState {
id : String
, file : File
} extra
type alias ValidFileState = CommonFileState {
validatedAt : Int
}
type alias InvalidFileState = CommonFileState {
reason : String
}
type alias LoadingFileState = CommonFileState {}
type FileState = Valid ValidFileState | Invalid InvalidFileState | Loading LoadingFileState
Now if I want to read one of those common properties on any given FileState, I must match against each member of the union:
getId : FileState -> String
getId fileState = case fileState of
Valid (CommonFileState {id} extra) -> id
Invalid (CommonFileState {id} extra) -> id
Loading (CommonFileState {id} extra) -> id
This feels wrong to me, because I have to duplicate the property access for each member. If I needed to manipulate this property somehow (e.g. concatenating something onto the string), I would also have to duplicate this.
I want to be able to easily access common properties of my union, and operate on those common properties.
When I started searching for other ways to do this, I found one alternative was to nest the union inside a record, which also holds the common properties:
type alias ValidCurrentFileState = {
validatedAt : Int
}
type alias InvalidCurrentFileState = {
reason : String
}
type alias LoadingCurrentFileState = {}
type CurrentFileState = Valid ValidCurrentFileState | Invalid InvalidCurrentFileState| Loading LoadingCurrentFileState
type alias File = {
name : String
}
type alias FileState = {
id : String
, file : File
, currentState : CurrentFileState
}
getId : FileState -> String
getId {id} = id
However this is awkward because I have to name the nested union, which adds a level of unnecessary indirection: "file state" and "current file state" are conceptually the same.
Are there any other ways of doing this which don't have the problems I mentioned?
I think you are thinking about this the wrong way around.
The purpose of modelling (in Elm) is capture the possible states of your data, and to exclude - in your model - 'impossible' states, so that the compiler can statically prevent the code every creating such states.
Once you're happy with your model, you write the helpers you need to make your core logic easy to express and to maintain.
I suspect I would normally go with your second approach, but I don't know all the issues you need to account for.

Get or Insert within a Transaction on Doobie in Scala

I'm reading through the Doobie documentation and trying to do a simple get or create within a transaction. I get an option off the first query and attempt to do a getOrElse and run an insert within the else, however I keep getting a value map is not a member of Any within the getOrElse call. What's the correct way to either get an existing or create a new row in instances and return that result in a transaction?
import doobie._
import doobie.implicits._
import cats._
import cats.effect._
import cats.implicits._
import org.joda.time.DateTime
import scala.concurrent.ExecutionContext
case class Instance(id : Int, hostname : String)
case class User(id : Int, instanceId: Int, username : String, email : String, created : DateTime)
class Database(dbUrl : String, dbUser: String, dbPass: String) {
implicit val cs = IO.contextShift(ExecutionContext.global)
val xa = Transactor.fromDriverManager[IO](
"org.postgresql.Driver", dbUrl, dbUser, dbPass
)
def getOrCreateInstance(hostname: String) = for {
existingInstance <- sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option
ensuredInstance <- existingInstance.getOrElse(sql"INSERT INTO instances(hostname) VALUES(?)".update.withGeneratedKeys[Instance]("id", "hostname"))
} yield ensuredInstance
}
I got the following answer thanks to the people on the #scala/freenode chatroom. I'm posting it here for completeness and if people are interested in doing this without the for comprehension in the other answer.
def getOrCreateInstance(hostname: String): ConnectionIO[Instance] =
OptionT(sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option)
.getOrElseF(sql"INSERT INTO instances(hostname) VALUES($hostname)".update.withGeneratedKeys[Instance]("id", "hostname").compile.lastOrError)
I believe something like this should work for you,
def getOrCreateInstance(hostname: String): ConnectionIO[Instance] = for {
existingInstance <- sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option
ensuredInstance <- existingInstance.fold(sql"INSERT INTO instances(hostname) VALUES($hostname)".update.withGeneratedKeys[Instance]("id", "hostname").take(1).compile.lastOrError)(_.pure[ConnectionIO])
} yield ensuredInstance
where you are compiling the fs2 Stream and also lifting the existing instance into a ConnectionIO in the case that it does already exist.

In Elm what is the correct way to implement my own toString

In Elm what is the correct way to take my Model and implement a toString function?
The type I am looking for would be toString : Model -> String, I am able to make a similar function with the type of toStr : Model -> String but I would think I would want the function to be called toString.
Example program (the Coin Changer kata):
module CoinChanger where
import Html exposing (..)
import StartApp.Simple as StartApp
import Signal exposing (Address)
import Html.Attributes exposing (..)
import Html.Events exposing (on, targetValue)
import String
---- MAIN ----
main =
StartApp.start
{
model = emptyModel
,update = update
,view = view
}
---- Model ----
type alias Model =
{
change : List Int
}
emptyModel : Model
emptyModel =
{
change = []
}
---- VIEW ----
toStr : Model -> String
toStr model =
model.change
|> List.map (\coin -> (toString coin) ++ "¢")
|> String.join ", "
view : Address String -> Model -> Html
view address model =
div []
[
input
[
placeholder "amount to make change for"
, on "input" targetValue (Signal.message address)
, autofocus True
-- style
]
[]
, div []
[
text (toStr model)
]
]
---- UPDATE ----
changeFor : Int -> List Int
changeFor amount =
[ 25, 10, 5, 1 ]
|> List.foldl
(\coin (change, amount)
-> ( change ++ List.repeat (amount // coin) coin
, amount % coin)
)
([], amount)
|> fst
update : String -> Model -> Model
update change model =
{ model | change =
case String.toInt change of
Ok amount
-> changeFor amount
Err msg
-> []
}
I would think the correct way to do this would be to call the function toString, but that gives me the following error from the compiler:
Detected errors in 1 module.
-- TYPE MISMATCH ----------------------------------------------- CoinChanger.elm
The type annotation for toString does not match its definition.
42│ toString : Model -> String
^^^^^^^^^^^^^^^ The type annotation is saying:
{ change : List Int } -> String
But I am inferring that the definition has this type:
{ change : List { change : List Int } } -> String
Renaming the function to toStr (or something not called toString) fixes the issue but seems wrong. What is the correct way to do this?
The problem is that, calling your function toString, you are overriding the toString function of the Basics module, which you are using at line 45.
To avoid this, you'll need to import the Basics module and use Basics.toString instead of simply toString to eliminare the ambiguity
The accepted answer is well out of date for anyone writing Elm 0.19+. The current solution is to write your own toString function for the type you want converted. There is a Debug.toString for use during development but its use in your code will prevent building for production.