I've chosen to center this question around JSON objects and wl-pprint-annotated (here is the paper behind that library) because they make it easy to have an MVCE, but my problem is not actually around pretty-printing just JSON objects and I am flexible for which pretty-printing library I use.
Consider the following simplified JavaScript object data type:
data Object = Object [(String, Object)]
| String String
How can I defined a pretty-printing function that wraps its output to multiple lines in the usual way? What I mean by that is: the pretty-printed output should, whenever possible fit on one line. When that is not possible, I expect the outermost objects to start adding newlines before the inner ones.
Here is one attempt using wl-pprint-annotated:
{-# LANGUAGE OverloadedString #-}
import Text.PrettyPrint.Annotated.WL
prettyObject :: Object -> Doc a
prettyObject (String str) = "\"" <> text str <> "\""
prettyObject (Object fields) = Union ("{" <+> hsep fields' <+> "}")
("{" <#> indent 2 (vsep fields') <#> "}")
where
fields' :: [Doc a]
fields' = punctuate "," [ text key <> ":" <+> prettyObject val
| (key,val) <- fields ]
Now, some test cases.
ghci> o1 = Object [("key1", String "val1")]
ghci> o2 = Object [("key2", String "val2"), ("looooooooooong key3", String "loooooooooooong val3"),("key4", String "val4")]
ghci> o3 = Object [("key5", String "val5"), ("key6", o2), ("key7", String "val7")]
ghci> prettyObject o1
{ key1: "val1" }
ghci> prettyObject o2
{
key2: "val2",
looooooooooong key3: "loooooooooooong val3",
key4: "val4"
}
ghci> prettyObject o3
{ key5: { key1: "val1" }, key6: {
key2: "val2",
looooooooooong key3: "loooooooooooong val3",
key4: "val4"
}, key7: "val7" }
I would like the last output to instead be
{
key5: { key1: "val1" },
key6: {
key2: "val2",
looooooooooong key3: "loooooooooooong val3",
key4: "val4"
},
key7: "val7"
}
I am looking for a solution which somehow fits with one of the existing pretty-printing libraries in Haskell (in reality, I'm pretty-printing much more than just a subset of JSON).
I am not looking for a solution which defines a prettyObject :: Object -> String - the whole point of this approach is that the rendering of the Doc depends on where it is in the big picture of what is being pretty-printed.
The pretty print library you are using can already do this; (you have just told it to do a different thing!) generally this family (WL) of pretty printers handles this case pretty well.
Note the positioning of your Union:
prettyObject (Object fields) = Union <one line> <many line>
At the point in your text where you are logically making the choice to break, which is at the beginning of a key-value pair, you don't have a Union in your Doc structure. The choice is made at the point where a {..} enclosed block begins; and if you scrutinize the output, that is exactly what it gives you:
{ key5: { key1: "val1" }, key6: { ----- line break here
key2: "val2",
You need a function to implement your desired logic for key-value pairs:
indent' k x = flatAlt (indent k x) (flatten x)
prettyKVPair (k,v) = indent' 2 $ text k <> ":" <+> pretty v
indent' is like indent, but provides an explicit alternative which is not indented. flatAlt provides an alternative which is used when the text is flattened, and your text will be flattened by (you may have guessed) flatten. You also need to re-structure prettyObject accordingly:
prettyObject :: Object -> Doc a
prettyObject (Object fields) = sep $ "{" : fields' ++ [ "}" ] where
fields' = punctuate "," $ map prettyKVPair fields
...
Note there is no explicit Union, but sep = group . vsep and group = \x -> Union (flatten x) x. You now have a union corresponding to logical choices about where you flatten your text.
The result:
>pretty o1
{ key1: "val1" }
>pretty o2
{
key2: "val2",
looooooooooong key3: "loooooooooooong val3",
key4: "val4"
}
>pretty o3
{
key5: "val5",
key6: {
key2: "val2",
looooooooooong key3: "loooooooooooong val3",
key4: "val4"
},
key7: "val7"
}
In response to the question in the comment, the way to provide a flat alternative is to use flatAlt, of course! The only issue here is you want to do this for a single element (the last one) of a list - but this is an issue with lists, not Doc. Feel free to use Data.Sequence or any other Traversable, with which most of the 'list-like' functions like punctuate work, if this is an operation you need a lot.
flattenedOf a b = flatAlt a (flatten b) # useful combinator
trailingSep _ [] = []
trailingSep s xs = as ++ [ (a <> s) `flattenedOf` a ]
where as = init xs; a = last xs
...
prettyObject (Object fields) = <unchanged> where
fields' = trailingSep "," $ <unchanged>
Related
Lets say that I have a string template which contains N number of placeholders:
"{placeholder1}/{placeholder2}-{placeholder3}/{placeholder4}.{placeholder5}"
And let's say that I have a map:
"placeholder1" -> "aaa",
"placeholder2" -> "xxx",
"placeholder3" -> "yyy",
"placeholder4" -> "zzz",
"placeholder5" -> "bbb"
Given this map and placeholder string template, is it possible to replace the placeholder keys with the placeholder values? Or would this require using regex?
you can iterate over the data and apply to the template using String.replace, and keep iterating on new state.
Given,
scala> val template = "{placeholder1}/{placeholder2}-{placeholder3}/{placeholder4}.{placeholder5}"
template: String = {placeholder1}/{placeholder2}-{placeholder3}/{placeholder4}.{placeholder5}
scala> val data = Map("placeholder1" -> "aaa",
"placeholder2" -> "xxx",
"placeholder3" -> "yyy",
"placeholder4" -> "zzz",
"placeholder5" -> "bbb")
data: scala.collection.immutable.Map[String,String] = HashMap(placeholder5 -> bbb, placeholder1 -> aaa, placeholder3 -> yyy, placeholder2 -> xxx, placeholder4 -> zzz)
apply the template on data using foldLeft. I'm assuming {} indicates the template placeholder.
scala> data.foldLeft(template){ case (newState, kv) => newState.replace(s"{${kv._1}}", kv._2)}
res6: String = aaa/xxx-yyy/zzz.bbb
NOTE: kv above is each entry in Map, alternatively, you can deconstruct kv as (k, v).
scala> data.foldLeft(template){ case (newState, (k, v)) => newState.replace(s"{$k}", v)}
res7: String = aaa/xxx-yyy/zzz.bbb
Alternative solution:
Though .foldLeft is enough, you can write your own vanilla recursive that applies one entry at a time and keeps iterating until data is empty.
def format(template: String, data: Map[String, String]): String = {
if(data.isEmpty) template
else format(template.replace(s"{${data.head._1}}", data.head._2), data.tail)
}
val formatted = format(template, data) // aaa/xxx-yyy/zzz.bbb
why not use string interpolation
def replace(m: Map[String,String]) = s"${m("placeholder1")}/${m("placeholder2")}-...."
I have a function that given an Int returns a list of lists of Strings.
fetchParts :: Int -> [[String]]
This is what the output looks like
[["title", "some title"], ["rate", "2.4"], ["dist", "some string"], ["tr", "1"], ["td, "2"] ..]]
The length of the output can be variable. Only the first 3 lists can be present 100% of the time.
The later part of the list can be
["a", "1"], ["b", "2"] ..
or
["some", "1"], ["part", "2"], ["of", "3"] ..]
or
["ex1", "a"], ["ex2", "b"], ..]
or some other combination of strings.
And I want to add this output to a sqlite3 database file. I'm using HDBC and HDBC.Sqlite3 for this.
To add something to a database file I'm running functions like these
initialConnection <- connectSqlite3 "src/parts.db"
run initialConnection partsEntry []
commit initialConnection
disconnect initialConnection
where partsEntry is a simple SQL String like this
partsEntry = "INSERT INTO PARTSDATA ( title, rate, dist, ...) VALUES ( "some title", "2.4", "some string", ...)
where
( title, rate, dist, ...) are from head <$> fetchParts 1
and
("some title", "2.4", "some string" ...) are from last <$> fetchParts 1
The problem is say if "some" column doesn't exists, code will throw errors.
What I want to do is something like this
if column "abc" doesn't exists, add column "abc" and insert
"this" value at the current row
if column "abc" exists, just insert "this" value at the current row
But I'm not sure how to go about doing that.
I was able to solve the problem.
First use describeTable function from HDBC package. The function will return column names and type. If you just need the names like I did, this is what you can do
getColumnsInTable :: conn -> String -> IO [String]
getColumnsInTable conn tableName = do
d <- describeTable conn tableName
return $ fst <$> d
The return will have all the columns' names.
Scan through the list to see if it contains all the columns you wish. If it doesn't use a function like the following to alter the table, i.e. add a new column with INT type.
createNewColumn conn columnName = do
let stmt = "ALTER TABLE FantasyBooks ADD COLUMN " ++ columnName ++ " INT;"
run conn stmt []
I am trying to understand if the following Groovy syntax is correct:
String f() { return "Hello"}
String g() { return "World"}
Map myMap = [
a : f(),
b : g(),
]
String x = myMap['a']
String y = myMap['b']
assert x == "Hello"
assert y == "World"
The Groovy language reference documentation on maps is not clear in this regard:
http://docs.groovy-lang.org/latest/html/documentation/index.html#_maps
Yes it's correct. Your case is covered in the first example in the documentation.
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
Think of the syntax as something like this: [value: expression]
The keys a and b become strings (value) and the methods are called to evaluate the expressions.
I would expect the result for plusses to be some kind of array
case class Plus()
val plus: P[Plus] = P ("+") map {_ => Plus()}
val plusses: P[List[Plus]] = P ( plus.rep.! ) // type mismatch; found: Parser[String] required: Parser[List[Plus]]
but compiler says
type mismatch; found : Parser[String] required: Parser[List[Plus]]
Answer
First, you don't need to capture something with .! as you already have a result in the plus parser. The .rep then "creates" a parser that repeats the plus parser 0 to n times and concatenates the result into a Seq[Plus].
case class Plus()
val plus: P[Plus] = P ("+") map {_ ⇒ Plus()}
val plusses: P[Seq[Plus]] = P ( plus.rep )
Second, if using the repeat operation rep, Parsers return Seq and not List If you really need a list, use map to convert Seq to List.
val plussesAsList: P[List[Plus]] = P( plus.rep ).map( seq ⇒ seq.toList)
.! is "to capture the section of the input string the parser parsed" (see http://lihaoyi.github.io/fastparse/#Capture). So the return type of .! is String.
I think what you want is:
val plusses: P[List[Plus]] = P ( plus.rep ) map (_.toList)
Here's what you will get:
# plusses.parse("+++")
res6: core.Result[List[Plus]] = Success(List(Plus(), Plus(), Plus()),3)
I have a collection of maps that looks something like this:
def list = [
[key1: 'ABC', key2: 3, value: 1.01],
[key1: 'ABC', key2: 4, value: 1.02],
[key1: 'ABC', key2: 4, value: 1.03],
[key1: 'DEF', key2: 3, value: 1.04]]
I'm trying to get a result that looks like this that groups and sums up the values for the unique key1 and key2 values and results in a hierarchy.
['ABC':[[key2: 2, value: 1.01]
[key2: 4, value: 2.05]], //note values are added
'DEF':[[key2: 3, value: 1.04]]
]
There are many examples of mapping routines that have one key, but what is the best way to fold these when using more than one key?
One solution I thought of was to use groupby to get the list grouped by the first key. The problem there is the combine or reduce must then be run on the sub list of each element:
list.parallel
.map{it}
.groupBy{it.key1}
at this point I want to reduce on the .value() of the grouped maps which I can't really do within the chain
I also tried to use combine, which works a bit like the examples here. However it looks like if combine gets a map back, it wants to combine it further.
def result = list.parallel
.map{[it.key1, it]}
.combine({-> [:]}) { map, v -> println "$map - $v = ${v.getClass()}"
map[v.key2] = map[v.key2]?:0 + v.value
map
}
Then there is the option to just reduce on the maps, but the reduce routine then becomes a pretty complicated beast of combining nested maps. So I'm wondering if there is something simpler, or should I just run a reduce routine to combine the complex maps.
list.parallel
.map{[(it.key1):it]}
.reduce([:]) { a, b ->
complexMapCombiner(a, b)
}
So here's a solution that works, but is less elegant than I'd like. If anyone has something better please post an answer.
#Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')
import static groovyx.gpars.GParsPool.*
def list = [
[key1: 'ABC', key2: 3, value: 1.01],
[key1: 'ABC', key2: 4, value: 1.02],
[key1: 'ABC', key2: 4, value: 1.03],
[key1: 'DEF', key2: 3, value: 1.04]]
withPool {
def mapInner = { entrylist ->
withPool{
entrylist.getParallel()
.map{[it.key2, it.value]}
.combine(0) {acc, v -> acc + v}.getParallel()
.map{[key2: it.key, value: it.value]}.collection
}
}
//for dealing with bug when only 1 list item
def collectSingle = { entrylist ->
def first = entrylist[0]
return [[key2:(first.key2), value:first.value]]
}
def result = list.parallel
.groupBy{it.key1}.getParallel()
.map{ [(it.key) : (it.value?.size())>1?mapInner.call(it.value):collectSingle.call(it.value) ] }
.reduce([:]) {a, b -> a + b}
println "result = $result"
}