Elm : initialise set in the model - elm

I am new to Elm. What I want to do is I am trying to initialise model with a set instead of a List, but given elm doesn't have any initialisers for sets (which is a shame, it'd be good if it was #{1,2,3}, as in Clojure), it is problematic.
With the code (Elm tutorial code, a little bit modified), I am trying to
main =
App.program
{ init = init "cats"
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ topic : String
, gifUrl : String
, error : String
, history : Set String
}
init : String -> (Model, Cmd Msg)
init topic =
( Model topic "waiting.gif" "" Set.fromList([topic])
, getRandomGif topic
)
Which throws me a compiler error of:
Function `Model` is expecting 4 arguments, but was given 5.
Which is strange because this doesn't throw an error in Elm repl and is a valid code in most cases.
How would I achieve this?

The problem originates from your use of parentheses.
Model topic "waiting.gif" "" Set.fromList([topic])
-- is the same as:
Model topic "waiting.gif" "" Set.fromList [topic]
You don't need to enclose arguments in parentheses in Elm, but you do need to enclose the entire fourth parameter of Set.fromList [topic] so that the compiler knows it's a single parameter. Change it to this and you should be all set:
Model topic "waiting.gif" "" (Set.fromList [topic])

Related

Update record field from Collection

I am playing a little bit with Elm these days, but I stuck with a simple case where I want to update a record field. My code is like this:
-- MODEL
initialModel : Model
initialModel =
{ selectedLanguage = "german"
, allCards = Card.cards
}
type alias Msg =
{ description : String
, data : String
, id : String
}
The update function
update : Msg -> Model -> Model
update msg model =
case List.head (model.allCards) of
Just card ->
{ card | fliped = True }
Nothing -> model
but I see this:
Something is off with the 1st branch of this `case` expression:
50| { card | fliped = True }
^^^^^^^^^^^^^^^^^^^^^^^^
The 1st branch is a record of type:
{ back : String, fliped : Bool, front : String, id : String }
But the type annotation on `update` says it should be:
Model
Hint: Seems like a record field typo. Maybe back should be allCards?
Hint: Can more type annotations be added? Type annotations always help me give
more specific messages, and I think they could help a lot in this case!
Detected errors in 1 module.
I think I should always return a model from update function like my type says, but cannot figure out how. Any advice here?
You'll have update the allCards field of model too. You can nest the card update inside the model update if the former returns a list instead of just a single card:
update : Msg -> Model -> Model
update msg model =
{ model
| allCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
}
Or you can bind the new allCards to a name if you prefer:
update : Msg -> Model -> Model
update msg model =
let
newAllCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
in
{ model | allCards = newAllCards }
I pattern match directly on the list here instead of using List.head, as that also gives me the remainder of the list and I don't have to deal with an intermediary Maybe value (or two actually, since List.tail returns a Maybe as well). The card::rest branch hits if allCards contains at least one card, so the only remaining case is therefore [], which is easy enough to handle.
Also, flipped is spelled with two ps ;)

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.

TYPE MISMATCH - This function cannot handle the argument sent through the (|>) pipe:

I am a super elm begginer and trying to make app.
Currently I am struggling to make landing page and http request to a server.
But, I am stuck here...
I have init function something like this below.
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
Model key TopPage
|> goTo (Route.parse url)
The definition of my Model is below.
-- MODEL
type alias Model =
{ key : Nav.Key
, page : Page
, name : String
, tags : List Tag
, jwt : String }
and, goTo function is below.
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Nothing ->
( { model | page = NotFound }, Cmd.none )
Just Route.Top ->
( { model | page = TopPage }, Cmd.none )
...
type Route is below.
type Route
= Top
| User String
| Repo String String
parse : Url -> Maybe Route
parse url =
Url.Parser.parse parser url
parser : Parser (Route -> a) a
parser =
oneOf
[ map Top top
, map User string
, map Repo (string </> string)
]
but following error has occured.
-- TYPE MISMATCH -------------------------------------------------- src/Main.elm
This function cannot handle the argument sent through the (|>) pipe:
54| Model key TopPage
55| |> goTo (Route.parse url)
^^^^^^^^^^^^^^^^^^^^^
The argument is:
String -> List Tag -> String -> Model
But (|>) is piping it a function that expects:
Model
What did I make mistake here?....
Your Model type has five fields, but in the line
Model key TopPage
you are only providing values for the first two of the five. You are missing values for the name, tags and jwt fields. Provide values for these and the problem should go away.
When you declare a type alias such as Model, Elm creates a constructor function also named Model. Elm functions support partial application, in that if you pass in values for some but not all of the arguments, you end up with a function that takes in the rest of the values. You provided two arguments, so you end up with a function that takes three arguments and returns a Model.
There are two ways of building a value of a type. Given a simple example of a Person type alias:
type alias Person = { name : String, age : Int }
You can construct a value by specifying all fields (note that you don't have to specify Person in the constructor; Elm's compiler is smart enough to know it by its shape):
jane : Person
jane = { name = "Jane", age = 35 }
Or you can build a value by using the type name and specify each field's values in the order in which they were defined. In this style, you can think of Person acting like a function with two parameters that returns a Person value.
jane : Person
jane = Person "Jane" 35
In each case, you have to specify all fields of the type when you construct it in order to obtain a complete Person value. However, that is not the complete story. It is possible to leave off the age parameter when constructing a Person, but the result isn't a Person, it's a function that takes an age and returns a Person. In other words,
janeAged : Int -> Person
janeAged = Person "Jane"
You can strip off as many parameters from the end as you'd like to make more variations on that constructor, even stripping out all parameters:
somebody : String -> Int -> Person
somebody = Person
Back to your example. You are constructing a Model value by only specifying two parameters (Model key TopPage). The value of that expression does not result in a Model, but in a function that takes three more parameters to create a Model. And that's why the error message indicated you need three parameters to construct a model.
You need to specify all values of Model when creating it.

Perl 6 multi methods never match expected signature

I have a class with two multi methods (multi submit).
I call my multi like this:
$perspective.submit(:message($message.content));
Which gets shipped off to my class:
my $perspective-api = API::Perspective.new(:api-key(%*ENV<PERSPECTIVE_API_KEY>));
proto method submit (|) {*}
multi method submit(Str :$message!, MODEL :#models = TOXICITY) {
my $score = $perspective-api.analyze(:#models, :comment($message));
say #models Z=> $score<attributeScores>{#models}.map: *<summaryScore><value>;
multi method submit(Str :$name!, MODEL :#models = TOXICITY) {
my $score = $perspective-api.analyze(:#models, :comment($name));
say #models Z=> $score<attributeScores>{#models}.map: *<summaryScore><value>;
}
However I always get the following response:
Died because of the exception:
Cannot resolve caller AUTOGEN(Rose::ContentAnalysis::Perspective:D: :message(Str)); none of these signatures match:
(Rose::ContentAnalysis::Perspective: Str :$message!, MODEL :#models = MODEL::TOXICITY, *%_)
(Rose::ContentAnalysis::Perspective: Str :$name!, MODEL :#models = MODEL::TOXICITY, *%_)
Despite my named argument (:message) being a Str as required and #models having a default declared.
Multiple dispatch works in two phases:
Considering the number of positional parameters and their types
If there are any where clauses, named parameters, or sub-signatures, doing a test bind of the signature to see if it would match
The second phase will reject the candidate if it fails to bind for any reason. One such reason, and I believe the cause of the issue here, is that the default value is wrongly typed. For example, in:
multi m(:#x = "not-an-array") { }
m()
We get an error:
Cannot resolve caller m(...); none of these signatures match:
(:#x = "not-an-array")
in block <unit> at -e line 1
But changing it to:
multi m(:#x = ["an-array"]) { }
m()
Works fine. (Note that while a default value uses =, it's actually a binding, not an assignment.)
In the case in the question there's this:
MODEL :#models = TOXICITY
Looking at the module source the code is taken from, I see:
enum MODEL is export (
<TOXICITY SEVERE_TOXICITY TOXICITY_FAST IDENTITY_ATTACK
INSULT PROFANITY SEXUALLY_EXPLICIT THREAT FLIRTATION
ATTACK_ON_AUTHOR ATTACK_ON_COMMENTER INCOHERENT INFLAMMATORY
LIKELY_TO_REJECT OBSCENE SPAM UNSUBSTANTIAL>
);
Thus TOXICITY is just an Int, but what's expected is a typed array of MODEL values.
Thus, if you do this:
multi method submit(Str :$message!, MODEL :#models = Array[MODEL](TOXICITY)) {
It should work.
I see two issues.
One is that you have two methods that are identical except for the name of one named parameter.
Named parameters can have aliases:
# V--------------V
multi method submit(Str :name(:$message)!, MODEL :#models = TOXICITY) {
my $score = $perspective-api.analyze(:#models, :comment($message));
say #models Z=> $score<attributeScores>{#models}.map: *<summaryScore><value>;
}
Note that :$message is really short for :message($message)
Now on the problem which actually prevents your code from working.
#models is a Positional, but you are assigning it a singular value in the signature.
Assign it a Positional, and it works:
(In this case it has to be of type Array[MODEL] because of the MODEL type declaration.)
# V---------------------V
multi method submit(Str :name(:$message)!, MODEL :#models = Array[MODEL](TOXICITY,)) {
my $score = $perspective-api.analyze(:#models, :comment($message));
say #models Z=> $score<attributeScores>{#models}.map: *<summaryScore><value>;
}

Format initialization list different from function parameter lists

I have as a C++ class constuctor
Status::Status(QObject *parent) : QObject(parent)
, m_currentPage(Status::UndefinedPage)
, m_lastPage(Status::UndefinedPage) ,
m_currentSubPage(Status::UndefinedSubPage), m_lastSubPage(Status::UndefinedSubPage)
, m_ptr(0)
{
}
and would like to see it like this
Status::Status(QObject *parent)
: QObject(parent)
, m_currentPage(Status::UndefinedPage)
, m_lastPage(Status::UndefinedPage)
, m_currentSubPage(Status::UndefinedSubPage)
, m_lastSubPage(Status::UndefinedSubPage)
, m_ptr(0)
{
}
I have found the relevant options:
nl_class_colon = remove
nl_class_init_args = remove
pos_comma = lead_force
pos_class_comma = lead_force
pos_class_colon = lead_force
but this also affects normal function parameters which I do not want. As soon as I alter pos_comma all my member initialization list gets crowded.
How is it possible to define the look of constructor initialization list different from function parameter lists?
Thanks.
EDIT: I want the function parameter list to look like
int r = myFuntion("a", "b",
"c");
No, as of Uncrustify 0.60 this is impossible. You have to either stick with different style or look for another formatter. You could also submit a feature request.