Modelling a Javascript object in Purescript - google-cloud-firestore

I'm trying to model in Purescript the SetOptions data type from Firestore.
Up to now I have the following
foreign import data FieldPath :: Type
foreign import buildFieldPath :: Array String -> FieldPath
foreign import fieldNames :: FieldPath -> Array String
type MergeFields = Array (String \/ FieldPath)
data SetOptions
= MergeOption Merge
| MergeFieldsOption MergeFields
Note that SetOptions is a sum type since the merge and mergeFields are mutually exclusive (even if this is not documented).
Now I need to convert SetOptions into a Javascript object, so that I can pass it to some function from the Javascript firebase library.
It should be something of the form
{
"mergeFields": [
"foo",
new FieldPath("bar", "baz")
]
}
My issue is the type of this.
I can't use Object since the contained data are not homogeneous (merge refers to booleans, mergeFields refers to arrays).
I can't use Json because I need to have FieldPath objects in the result.
The only solution I found up to now is returning some Json and then on the javascript side parse it and add the FieldPath objects where needed, but it looks dirty and brittle.

I would probably skip coproduct SetOption representation on the PS side and just provide these two "dirty" constructors:
foreign import data SetOption :: Type
merge :: Boolean -> SetOption
merge m = unsafeCoerce { merge: m }
mergeFields :: MergeFields -> SetOption
mergeFields mf = unsafeCoerce { mergeFields: mf }
I would probably do the same for MergeFields coproduct.
We are doing something similar in our community project - material ui bindings: purescript-react-basic-mui. Additionally we are grouping these related constructors into records to achieve "cheap namespacing" because we are generating all these bindings from typescript declarations, but this is not really important in this context.
Please take a look at some definitions in this example module:
https://github.com/purescript-react-basic-mui/purescript-react-basic-mui/blob/codegen-read-dts/src/MUI/Core/Badge.purs#L20
EDIT: I think that this latest discourse thread can be a good additional inspiration for you #macrosh.

Related

Parse JSON in PureScript with Argonaut

I use argonaut library in PureScript for decode and encode JSON.
I cannot write an implementation to decode and encode such json field:
"field": [3, "text"]
Here's an array with different data types.
How can I instance it in argonaut library?
If you have a fixed number of values of different types, this is generally (in computer science and mathematics) called a "tuple", with a special name for when there are just two of them - a "pair".
JavaScript doesn't have a concept of a tuple, and admittedly it would make little sense in the absence of static types. So traditionally tuples in JavaScript are encoded as arrays.
But PureScript does have such concept! In the standard library it's called - surprise! - Tuple (and then there are variants for different number of elements - Tuple3, Tuple4, and so on)
And Argonaut follows the JavaScript convention: it encodes tuples as arrays. So if you just type your field as a Tuple Int String, it will work:
type MyObj = { field :: Tuple Int String }
x :: Either JsonDecodeError MyObj
x = parseJson "{ \"field\": [3, \"text\"] }" >>= decodeJson
main :: Effect Unit
main =
logShow x -- prints Right { field: Tuple 3 "text" }

Unwrap Maybe values from nested records

I have a series of nested records of config information in PureScript that I want to pass to a JavaScript function. These records predominantly consist of Maybe-typed values. Is there a way I can serialize this to JavaScript objects which omit the Nothing-valued methods and unwrap the Just-valued methods?
I've taken a shot at writing this in JavaScript using semi-hacky instanceof checks, but it's been very painful, e.g. because there is no easy way to stop the recursion (I can't differentiate my records from random other JavaScript objects). Is there a better way?
One option is to use the purescript-nullable package. You can turn Maybe values into Nullable values using toNullable :: forall a. Maybe a -> Nullable a. The resulting runtime representation is appropriate for passing to JavaScript functions, since toNullable (Just value) becomes value during runtime and toNullable Nothing becomes null during runtime.
Another option is to use the purescript-simple-json package. You can use the write :: forall a. WriteForeign a => a -> Foreign function to turn a record with Maybe values into a record where Just value is replaced with value and Nothing is replaced with undefined. This approach should more straightforward for your use case of nested records with Maybe values.
You can use genericEncodeJson from Data.Argonaut and set the omitNothingFields flag to true. This flag does exactly what you expect.
module Main where
import Prelude
import Control.Monad.Eff.Console (logShow)
import Data.Argonaut.Generic.Aeson (options)
import Data.Argonaut.Generic.Encode (Options(..), genericEncodeJson)
import Data.Generic (class Generic)
import Data.Maybe (Maybe(..))
data TheRecord = TheRecord { a :: Maybe Int, b :: Maybe String, c :: String }
derive instance gRecord :: Generic TheRecord
main =
-- Prints {"c":"Always here","a":42}
logShow $ genericEncodeJson (Options o { omitNothingFields = true }) rec
where
rec = TheRecord { a: Just 42, b: Nothing, c: "Always here" }
Options o = options

How do you set the document title using Purescript?

After searching for some time I found in Pursuit the module DOM.HTML.History which has the data type DocumentTitle. This type could probably be used together with the function
replaceState ::
∀ e. Foreign -> DocumentTitle -> URL -> History -> Eff (history :: HISTORY | e) Unit
To change the document.title property of the page, however, I can't find examples showing how to call this function (e.g., where do I get the external Foreign data type?). Also, I'm not even sure if this function would do what I expect it to do...
In the unfortunate case that the Purescript team didn't include in their core API a way to change the document title, it's still possible to do so by making use of purescript's handy FFI mechanism.
Add these two files into your project:
Document.js
exports.setDocumentTitle =
function (title)
{
return function ()
{
window.document.title = title;
};
};
Document.purs
module Document
where
import Control.Monad.Eff (kind Effect, Eff)
import Data.Unit (Unit)
foreign import data DOCUMENT :: Effect
foreign import setDocumentTitle ::
∀ fx . String -> Eff (document :: DOCUMENT | fx) Unit
Now you can call setDocumentTitle as you would call Console's log function, except the effect would be DOCUMENT instead of CONSOLE, of course.
kazouas answer would look like this (in PS 0.12)
import Effect (Effect)
import Data.Unit (Unit)
foreign import setDocumentTitle :: String -> Effect Unit
Javascript remains the same.

How do you present any data type to the user with PureScript?

I want to make a very human-friendly development environment, and I'm considering using PureScript to provide the language part. I see that out of the box, Show doesn't work on records of things which are instances of Show:
log (show {a:5})
The 'Try PureScript!' (http://try.purescript.org/) compiler says:
No type class instance was found for
Prelude.Show { a :: Int
}
Is there a tool for generically printing any data structure, especially one containing records? Is there some type trickery that would support generically walking over the record to support my own class like present :: Present a => a -> Presentation? The problem is that I don't know what the types will be ahead of time. The user enters a record and I want to be able to present it. It seems that I'll have to patch the compiler to support this.
Records are disallowed in instance heads. For discussion and reasons, see this thread.They must be wrapped in data or newtype if we want to write instances for them.
However, there is a generics library and a deriving mechanism that lets us generate Show instances.
import Data.Generic
data Foo = Foo {a :: Int} | Bar {b :: String}
derive instance genericFoo :: Generic Foo
instance showFoo :: Show Foo where
show = gShow
Working with untyped data in PureScript is done using the purescript-foreign or the purescript-argonaut libraries. I'd suggest argonaut.
The representation of a record with unknown fields and unknown types for these fields would be: StrMap Json from the purescript-maps package. I'd suggest you take a look at the (not yet merged) documentation over here: https://github.com/hdgarrood/purescript-argonaut-core/blob/565c7e650c51c45570663cf1838ec9cfa307a9c7/README.md. I've also put together a little example, showing how to match on a heterogeneous array from JavaScript:
-- src/Main.purs
module Main where
import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Data.Argonaut (foldJson, Json)
import Data.Foldable (traverse_)
newtype Presentation = Presentation String
unPresentation :: Presentation -> String
unPresentation (Presentation p) = p
instance showPresentation :: Show Presentation where
show = unPresentation
class Present a where
present :: a -> Presentation
instance presentInt :: Present Int where
present = Presentation <<< show
instance presentNumber :: Present Number where
present = Presentation <<< show
instance presentBoolean :: Present Boolean where
present = Presentation <<< show
instance presentString :: Present String where
present = Presentation
presentJson :: Json -> Presentation
presentJson =
foldJson
(const (Presentation "null"))
present
present
present
(const (Presentation "array"))
(const (Presentation "record"))
foreign import vals :: Array Json
main :: forall e. Eff ( console :: CONSOLE | e) Unit
main = traverse_ (log <<< show <<< presentJson) vals
And the corresponding js file:
// src/Main.js
// module Main
exports.vals = [1, 1.2, "hello", true, [1,2,3], {a: 3, b: "hi"}];
Running this program gives you:
> pulp run
* Building project in/home/creek/Documents/so-christopher-done
* Build successful.
1.0
1.2
hello
true
array
record
Yes, traceAny and related functions from purescript-debug. Here are a few examples: test/Main.purs#L22. I'd post the links to Pursuit, but it doesn't seem to have purescript-debug at the moment.

Passing records to ffi

When I pass a record to javascript, it works:
data Record = Record {
elem :: String
}
doSomethingForeign :: Record -> Fay ()
doSomethingForeign = ffi " callJsFun(%1) "
But when the function is not monomorphical, the record is not evaluated, one needs to do it manually:
class Passable a
instance Passable Record
instance Passable Text
doSomethingForeign' :: (Passable a) => a -> Fay ()
doSomethingForeign' = ffi " callJsFun(Fay$$_(%1)) "
This is the simple case, when the extra typing of Fay$$_ isn't that annoying, but if I pass more complex structures with type parameters to js, then adding just Fay$$_ won't solve it. I'd like to know the rule, when the evaluation to native js types is applied and where not.
The thunks will remain and type conversions won't happen if you have a type variable or Ptr X in the FFI, in contrast to a concrete type or Automatic a where the opposite applies.
I think what you want here is :: Passable a => Automatic a -> Fay () to force any thunks. It should be equivalent to separating this into two functions with a monomorphic argument. Using Automatic with a foreign type such as Text will only force the thunk and not do any type conversions.