Why does this code fail
type SectionedItems s i = SectionedItems{
section : s,
items : List i,
subsections: List (SectionedItems s i)
}
si1 : SectionedItems String String
si1 = SectionedItems{
section = "",
items = [
"1",
"2"
],
subsections = [
]
}
and this code succeeds
type SectionedItems s i = SectionedItems{
section : s,
items : List i,
subsections: List (SectionedItems s i)
}
si1 : SectionedItems String String
si1 = SectionedItems{
section = "",
items = [
"1",
"2"
],
subsections = [
]
}
Why does elm fail for the first code? I know it is failing due to whitespace but why? Why do the { and } have to be aligned when creating an instance but not when declaring the type?
It's not that those brackets have to be lined up, it's that you can't put the closing bracket at the start of the line.
For example, this compiles:
si1 : SectionedItems String String
si1 = SectionedItems{
section = "",
items = [
"1",
"2"
],
subsections = [
]
}
Just putting in one extra space before the closing bracket is enough.
Why? Because the "children" of si1 must have greater indentation than si1 itself. If they don't, Elm thinks you're trying to start a new definition, and } isn't a valid way to start a definition.
Related
I have written my custom annotation processor for my bot command system.
It looks like this:
#Command(
name = "User profile",
description = "Fetches user profile",
identifiers = ["user", "u", "гыук", "г"]
)
class UserProfileCommand (
#Argument(
name = "Player",
description = "Something written here",
identifiers = ["nick", "nickname", "тшсл", "тшслтфьу"],
required = true,
implicit = true
)
val actor: String,
#Argument(
name = "Server",
description = "Something written here",
identifiers = ["server", "s", "ыукмук", "ы"],
)
#Default("bancho")
val server: String,
#Argument(
name = "Mode",
description = "Something written here",
identifiers = ["mode", "m", "ьщву", "ь"],
)
#Default("default")
val mode: String,
#Argument(
name = "Render mode",
description = "Enables render mode",
identifiers = ["render", "r", "кутвук", "к"]
)
#Default("false")
val isRenderMode: Boolean
The problem is that I want to reuse some definitions of the arguments in other commands, so I want to move annotations to constants and use them.
object Arguments {
val Actor = Argument(
name = "Actor",
description = "",
identifiers = arrayOf("nick", "nickname", "тшсл", "тшслтфьу"),
required = true,
implicit = true
)
}
I can create instance of Argument like that and move it to constant but I cant find any way to use it. The example below unsurprisingly gives me error:
#Argument = Arguments.Actor
val actor: String,
Is there any way to achieve what I want? Is there any way I can to it without using code generation libraries? If I need to use these libraries, then which one can you recommend? How can I generate annotations with it?
I have a list of rules which i want to generate at runtime as it depends on availability_domains where availability_domains is a list
availability_domains = [XX,YY,ZZ]
locals {
rules = [{
ad = XX
name = "service-XX",
hostclass = "hostClassName",
instance_shape = "VM.Standard2.1"
...
},{
ad = YY
name = "service-YY",
hostclass = "hostClassName",
instance_shape = "VM.Standard2.1"
...
}, ...]
}
Here, all the values apart from ad and name are constant. And I need rule for each availability_domains.
I read about null_resource where triggers can be used to generate this but i don't want to use a hack here.
Is there any other way to generate this list of map?
Thanks for help.
First, you need to fix the availability_domains list to be a list of strings.
availability_domains = ["XX","YY","ZZ"]
Assuming availability_domains is a local you just run a forloop on it.
locals {
availability_domains = ["XX","YY","ZZ"]
all_rules = {"rules" = [for val in local.availability_domains : { "ad" : val, "name" : "service-${val}" , "hostclass" : "hostClassName", "instance_shape" : "VM.Standard2.1"}] }
}
or if you dont want the top level name to the array then this should work as well
locals {
availability_domains = ["XX","YY","ZZ"]
rules = [for val in local.availability_domains : { "ad" : val, "name" : "service-${val}" , "hostclass" : "hostClassName", "instance_shape" : "VM.Standard2.1"}]
}
I am doing a http request with elm and my response is an Array of Objects. Each object is as follows
obj = {
title: "Some Title",
words: [ "word1", "word2", "word3", "word4" ]
}
Here is my decoder so far:
type alias ThisRes = List ResObj
type alias ResObj =
title: String
words: List String
decoded : Decoder ThisRes
decoded =
decode ThisRes
I can't seem to get the decoder right and any help that can be provided would be appreciated.
obj =
"""
{
"title": "Some Title",
"words": [ "word1", "word2", "word3", "word4" ]
}
"""
type alias ResObj =
{ title : String, words : List String }
objDecoder =
map2 ResObj
(at [ "title" ] string)
(at [ "words" ] (list string))
headingFrom : Result String ResObj -> String
headingFrom result =
case result of
Ok resobj ->
resobj.title
Err reason ->
toString reason
main =
h1 [] [ text <| headingFrom <| decodeString objDecoder obj ]
Breaking this down:
obj is just a string representing some JSON, to illustrate the example.
You define the ResObj type alias, and get a type constructor for it. Now you can create ResObj values by calling ResObj "MyTitle" ["word1", "wordb", "wordFightingMongooses"].
objDecoder is a value that helps decode JSON into ResObj. It calls map2, which takes a type constructor, and then two decoded JSON fields. (at ["title"] string) says "turn the value at title in the JSON into a string" and you can guess what the next line does. So this evaluates to, ultimately, ResObj "SomeTitle" ["word1", "word2", "word3", "word4"] and creates your value.
Down in our main, we have the expression decodeString objDecoder obj. decodeString takes a decoder value, and a JSON string, and sees if it can decode it. But it doesn't return the ResObj itself -- because the decoding can fail, it returns a Result.
So we have to process that Result with our function headingFrom. If the decoding succeeded our result will be Ok resobj, and we can work with the resobj finally. If it failed it'll be Err reason.
This question is kind of silly but i didn't find a straight forward solution.
Assuming I have a model that resembles this: - at least this big.
initModel =
{ selectedCategory = "Vacantion"
, context = "root/Work"
, abstractSyntaxTree =
[ { categoryName = "Work"
, categoryContent =
[]
}
, { categoryName = "Vacation"
, categoryContent =
[ { folderName = "Hawaii"
, folderContent =
FolderContent
( [ { folderName = "Booking"
, folderContent = FolderContent ( [], [] )
}
]
, [ "flightTicket.jpg" ]
)
}
]
}
]
}
Question: How can i display it in the browser so that it looks good? - nothing fancy - just to see what's happening like a quick debugger..
What I've tried so far:
view =
div []
[ pre [style [("width", "300") ] ] [ text (toString model)]
]
Works great on smaller models but on this case, I get this long -single line - formatted json like structure:
I think this is the Problem: The prettify extension i installed in google chrome doesn't know how to deal with strings that do not contain \n in them. To check for this, i manually added a \n - and that string was split on the second row, as expected.
The output form text (toSting model) - it's a string that has no \n in it - so that's why everything is displayed on a single line in the browser - regardless of the 300 px limit on width.
Splitting up the string - by adding \n by myself - works except i don't know where exactly to add the \n. To make it dynamic, this requires a full-flagged parser for the model. A way to know where the expression starts, where is the next matching bracket .. etc. I'm not good enough to build this parser. I feel i'm over complicating this stuff. Must be a better solution..
How do you folks do it?
Elm does not allow you to enumerate over the items in a record. For type-safety no doubt. So there is no "clean" way to do display a record nicely.
UPDATE: The solution below will not work anymore as of Elm 0.19.
It relies on a (deprecated) function toString that would convert any type into a string.
Since Elm 0.17 (when I made this), Elm had already released a great debugger in 0.18, which already has a feature to follow model and messages in a separate window.
For debugging, you can do some trickery with toString to print it more neatly.
Below is example code, which you can copy/ paste to elm-lang.org/try.
Simply pass any record to the viewModel function, and it displays it in the browser.
It is quite crude, and probably not very performant with large records, but it will do the job..
import Html exposing (Html, text, div, p, pre)
import Html.Attributes exposing (style)
import String
quote = "\""
indentChars = "[{("
outdentChars = "}])"
newLineChars = ","
uniqueHead = "##FORMAT##"
incr = 20
model =
{ name = "Abe"
, age = 49
, someTuple = (18,49)
, relatives = [ "Claire", "Bill" ]
, comments = "any special characters like []{}, will not be parsed"
, cars = [ { brand = "BMW", model = "535i" } ]
}
viewModel : a -> Html msg
viewModel model =
let
lines =
model
|> toString
|> formatString False 0
|> String.split uniqueHead
in
pre [] <| List.map viewLine lines
viewLine : String -> Html msg
viewLine lineStr =
let
(indent, lineTxt) = splitLine lineStr
in
p [ style
[ ("paddingLeft", px (indent))
, ("marginTop", "0px")
, ("marginBottom", "0px")
]
]
[ text lineTxt ]
px : Int -> String
px int =
toString int
++ "px"
formatString : Bool -> Int -> String -> String
formatString isInQuotes indent str =
case String.left 1 str of
"" -> ""
firstChar ->
if isInQuotes then
if firstChar == quote then
firstChar
++ formatString (not isInQuotes) indent (String.dropLeft 1 str)
else
firstChar
++ formatString isInQuotes indent (String.dropLeft 1 str)
else
if String.contains firstChar newLineChars then
uniqueHead ++ pad indent ++ firstChar
++ formatString isInQuotes indent (String.dropLeft 1 str)
else if String.contains firstChar indentChars then
uniqueHead ++ pad (indent + incr) ++ firstChar
++ formatString isInQuotes (indent + incr) (String.dropLeft 1 str)
else if String.contains firstChar outdentChars then
firstChar ++ uniqueHead ++ pad (indent - incr)
++ formatString isInQuotes (indent - incr) (String.dropLeft 1 str)
else if firstChar == quote then
firstChar
++ formatString (not isInQuotes) indent (String.dropLeft 1 str)
else
firstChar
++ formatString isInQuotes indent (String.dropLeft 1 str)
pad : Int -> String
pad indent =
String.padLeft 5 '0' <| toString indent
splitLine : String -> (Int, String)
splitLine line =
let
indent =
String.left 5 line
|> String.toInt
|> Result.withDefault 0
newLine =
String.dropLeft 5 line
in
(indent, newLine)
main =
viewModel model
By now we have a nice package for 0.19.
elm install ThinkAlexandria/elm-pretty-print-json
And run
json = """{"name": "Arnold", "age": 70, "isStrong": true,"knownWeakness": null,"nicknames": ["Terminator", "The Governator"],"extra": {"foo": "bar","zap": {"cat": 1,"dog": 2},"transport": [[ "ford", "chevy" ],[ "TGV", "bullet train", "steam" ]]}}"""
{-| Formating configuration.
`indent` is the number of spaces in an indent.
`columns` is the desired column width of the formatted string. The formatter
will try to fit it as best as possible to the column width, but can still
exceed this limit. The maximum column width of the formatted string is
unbounded.
-}
config = {indent = 4, columns = 80}
-- run prettifier
Result.withDefault "" (Json.Print.prettyString config json)
-- result
{-
{
"extra": {
"transport": [
[
"ford",
"chevy"
],
[
"TGV",
"bullet train",
"steam"
]
],
"zap": {
"dog": 2,
"cat": 1
},
"foo": "bar"
},
"nicknames": [
"Terminator",
"The Governator"
],
"knownWeakness": null,
"isStrong": true,
"age": 70,
"name": "Arnold"
}
-}
I found this in slack : elm-debug-transformer
Not rely what i asked for, because for example im gonna need to make it work with node.js also, but still seems like good solution for now.
The canonical example for getting the value from an input is:
view : Address String -> String -> Html
view address string =
div []
[ input
[ placeholder "Text to reverse"
, value string
, on "input" targetValue (Signal.message address)
, myStyle
]
[]
, div [ myStyle ] [ text (String.reverse string) ]
]
I get this. But I want my address to be of type Address String Action (where Action is some other type I define). To my understanding, this would mean the address expects a String followed by a Action type as it's "arguments" (I think of Address as a function, but that might not be correct).
Is it possible to use an address type of Address String Action, and then use it with an input in a similar way? Or am I allowed to do Address String Action in the first place?
The example you link to is probably a bit too simplistic in that both the Action and Model are a string. You will seldom run into that.
I've tweaked the example with something that is more canonical to elm in its current form:
main =
StartApp.start { model = { text = "" }, view = view, update = update }
type Action
= SetText String
type alias Model =
{ text : String }
update : Action -> Model -> Model
update action model =
case action of
SetText text ->
{ model | text = text }
view : Address Action -> Model -> Html
view address model =
div []
[ input
[ placeholder "Text to reverse"
, value model.text
, on "input" targetValue (Signal.message address << SetText)
, myStyle
]
[]
, div [ myStyle ] [ text (String.reverse model.text) ]
]
Notice how the Action type is a union type listing all the different ways you can interact with the page. In this example, the only thing you can do is to set the text.
The signature of view is now more explicit. The first argument is the address of a mailbox that deals in type Action, and the second argument contains the current state of the model.
view : Address Action -> Model -> Html
There is no need to go down a path of trying something like Address String Action since now Action encapsulates the setting of the text.