Haskell: How do I model a nested JSON with fixed outer keys and an enumerated inner key? - class

Consider an external API that takes as input either usd or eur, and accordingly returns a json, something like this:
api currency = case currency of
"usd" -> "{\"bitcoin\": {\"usd\": 20403}, \"ethereum\": {\"usd\": 1138.75}}"
"eur" -> "{\"bitcoin\": {\"eur\": 20245}, \"ethereum\": {\"eur\": 1129.34}}"
If I just needed api "usd", I would use Aeson's (?) generic decoding feature:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data Usd = Usd
{ usd :: Double
} deriving (Show, Generic)
instance FromJSON Usd
data Coin = Coin
{ bitcoin :: Usd
, ethereum :: Usd
} deriving (Show,Generic)
instance FromJSON Coin
processUsd = decode (api "usd") :: Maybe CoinUsd
But if both api "usd" and api "eur" are to be used, what is the best way to abstract currency out?
(In case you ask what I really want to do with it, well, the answer is nothing! This example is admittedly contrived. I want to understand ways to use data and class in modeling a json format whose keys are constrained in some ways. I would also like to maximally use Aeson's automatic decoding feature, avoiding custom fromJSON code to the extent possible.)
One option is to use nested Data.Map:
processAny :: String -> Maybe (M.Map String (M.Map String Double))
processAny currency = decode (api currency)
But this is too general. I still want the outer keys ("bitcoin" etc) hardcoded/fixed. What are the options at this degree of pickiness? My immediate thought is to have a generalized Currency type and use it as a parameter for Coin. But I can't figure how to work it out?! Below are some vague statements that I hope convey my intent:
data (Currency a) => Coin a
{ bitcoin :: a
, ethereum :: a
} deriving (Show,Generic)
instance FromJSON (Coin a) where
-- parseJSON x = codeIfNeeded
class (FromJSON a) => Currency a where
-- somehow abstract out {currencyName :: Double} ?!
I am not even sure if it makes any sense at all, but if it does, how do I formalize it? Also, what is the best way to model it otherwise (while, as mentioned before, not resorting to the extremes of Data.Map and fully hand written parseJSON)?

Let's begin by modeling elements like {"usd": 20403} in isolation. We can define a type like
{-# LANGUAGE DerivingStrategies #-}
newtype CurrencyAmount currency = CurrencyAmount {getCurrencyAmount :: Double}
deriving stock (Show)
parameterized with "phantom types" like:
data Euro -- no constructors required, used only as type-level info
data USD
This approach lets us (and forces us) to reuse the same "implementation" and operations for different currencies.
One operation we want to do is to parse "tagged" currency amounts. But the key in the JSON varies for each currency, that is, it depends on the phantom type. How to tackle that?
Typeclasses in Haskell let us obtain values from types. So let's write a typeclass that gives us the JSON Key to use for each currency:
import Data.Aeson
import Data.Aeson.Key
import Data.Proxy
class Currency currency where
currencyKey :: Proxy currency -> Key -- Proxy optional with AllowAmbiguousTypes
With instances
{-# LANGUAGE OverloadedStrings #-}
instance Currency Euro where
currencyKey _ = "eur"
instance Currency USD where
currencyKey _ = "usd"
Now we can write an explicit FromJSON instance for CurrencyAmount:
instance Currency currency => FromJSON (CurrencyAmount currency) where
parseJSON = withObject "amount" $ \o ->
CurrencyAmount <$> o .: currencyKey (Proxy #currency)
And we can define Coin like this:
{-# LANGUAGE DeriveAnyClass #-}
data Coin currency = Coin
{ bitcoin :: CurrencyAmount currency,
ethereum :: CurrencyAmount currency
}
deriving stock (Show, Generic)
deriving anyclass (FromJSON)

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" }

Modelling a Javascript object in Purescript

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.

Coproduct with multiple values

I´m playing with Coproduct of shapeless but I dont know if I´m using wrong but I dont know how can I create a coProduct with mutlple values
Having this code
case class Name(value: String)
case class Age(value: Int)
case class Sex(value: String)
type Person = Name :+: Age :+: Sex :+: CNil
#Test
def main(): Unit = {
val person = Coproduct[Person](Name("Paul"))
println(person.select[Name])
println(person.select[Age])
println(person.select[Sex])
}
How can I creare Person with Name, Age and Sex using Coproduct?.
Regards.
Coproduct and product are two dual constructs rooted in category theory, but in programming terms can be simplified to:
product includes all of the given types
coproduct materializes one of the given types
For example, a Person that consists of name, age and sex should be modeleld using a product type. This is because a person consists of name, age and sex; all those types are needed in order to construct a person.
Example of a coproduct could be anything that has a subtyping relationship, for example a Fruit can be either an Apple, a Banana or an Orange. We also use coproducts often for result types that might fail, e.g. a Response can either be a Success or a Failure. Either and Future are two examples of coproducts.
You mixed the things up a bit, and you modelled a coproduct (perfectly correct, from the language syntax point of view) when you really needed a product. If you run your code, you will see
Some(Name(Paul))
None
None
This is because your Person, the way you coded it, is either a name, age or sex. Only one of those. And you then created an instance of Person by creating a name, which is fine, so if you print those three selections you will see that the name selector gives you Some(Name(Paul)), while other two are empty.
If you code the Person as a product (in shapeless implemented via heterogenous lists), you will get:
case class Name(value: String)
case class Age(value: Int)
case class Sex(value: String)
type Person = HNil
val person = Name("Paul") :: Age(32) :: Sex("Yes please") :: HNil
println(person) // Name(Paul) :: Age(32) :: Sex(Yes please) :: HNil
In plain Scala, we can model coproducts using constructs such as Either or via subtyping hierarchies (e.g. trait Fruit is extended by case classes Apple, Banana etc.), while products are usually modelled simply via case classes (in your case it would be case class Person(name: String, age: Int, sex: String).
In shapeless, coproducts can basically be viewed as an "Either with any number of possibilities rather than just two", while product is implemented as a heterogenous list, meaning "a collection of any number of values which can also be of varying types".
Read more about products (aka "heterogenous lists") here and about coproducts (aka "discriminated unions") here.

Why does Scala have a case object?

It cannot be initialized with params and it will only be compared with itself (singleton). So no advantage for its equals and hash code function. Has anyone come across a case where they find it useful?
You can use case objects as an alternative for enumerations.
Details can be found here: Case objects vs Enumerations in Scala
A simplified example from that question:
sealed trait Currency {
def name: String
def symbol: String
}
case object EUR extends Currency {
val name = "EUR"
val symbol = "€"
}
case object USD extends Currency {
val name = "USD"
val symbol = "$"
}
Advantages
This way there can be more fields (compared to ID and name in an Enumeration)
The compiler warns (in case of a sealed type hierarchy), if a match is not exhaustive.
So this code
val ccy: Currency = EUR
ccy match {
case EUR =>
println("Euro")
}
will result in
Warning:(27, 7) match may not be exhaustive.
It would fail on the following inputs: USD
ccy match {
Disadvantages
There is no "get by name" method (Enumeration provides withName())
You cannot iterate over "all" elements
I think the most important difference is that case objects can be serialized while simple objects cannot.
This makes them very useful as messages with Akka-Remote.
EDIT:
As Rüdiger Klaehn pointed out, this is not the only benefit we get from the case keyword. There is also:
hashCode implementation
a useful toString implementation
For classes additionally:
pattern matching optimization
a companion object with useful apply and unapply implementations
(This list may not be exhaustive!)

Pattern matching on List[T] and Set[T] in Scala vs. Haskell: effects of type erasure

Would the Haskell equivalent of the code below produce correct answers?
Can this Scala code be fixed to produce correct answers ? If yes, how ?
object TypeErasurePatternMatchQuestion extends App {
val li=List(1,2,3)
val ls=List("1","2","3")
val si=Set(1,2,3)
val ss=Set("1","2","3")
def whatIsIt(o:Any)=o match{
case o:List[Int] => "List[Int]"
case o:List[String] => "List[String]"
case o:Set[Int] => "Set[Int]"
case o:Set[String] => "Set[String]"
}
println(whatIsIt(li))
println(whatIsIt(ls))
println(whatIsIt(si))
println(whatIsIt(ss))
}
prints:
List[Int]
List[Int]
Set[Int]
Set[Int]
but I would expect it to print:
List[Int]
List[String]
Set[Int]
Set[String]
You must understand that by saying o:Any you erase all the specific information about the type and further on the type Any is all that the compiler knows about value o. That's why from that point on you can only rely on the runtime information about the type.
The case-expressions like case o:List[Int] are resolved using the JVM's special instanceof runtime mechanism. However the buggy behaviour you experience is caused by this mechanism only taking the first-rank type into account (the List in List[Int]) and ignoring the parameters (the Int in List[Int]). That's why it treats List[Int] as equal to List[String]. This issue is known as "Generics Erasure".
Haskell on the other hand performs a complete type erasure, which is well explained in the answer by Ben.
So the problem in both languages is the same: we need to provide a runtime information about the type and its parameters.
In Scala you can achieve that using the "reflection" library, which resolves that information implicitly:
import reflect.runtime.{universe => ru}
def whatIsIt[T](o : T)(implicit t : ru.TypeTag[T]) =
if( t.tpe <:< ru.typeOf[List[Int]] )
"List[Int]"
else if ( t.tpe <:< ru.typeOf[List[String]] )
"List[String]"
else if ( t.tpe <:< ru.typeOf[Set[Int]] )
"Set[Int]"
else if ( t.tpe <:< ru.typeOf[Set[String]] )
"Set[String]"
else sys.error("Unexpected type")
println(whatIsIt(List("1","2","3")))
println(whatIsIt(Set("1","2","3")))
Output:
List[String]
Set[String]
Haskell has a very different approach to polymorphism. Above all, it does not have subtype polymorphism (it's not a weakness though), that's why the type-switching pattern matches as in your example are simply irrelevant. However it is possible to translate the Scala solution from above into Haskell quite closely:
{-# LANGUAGE MultiWayIf, ScopedTypeVariables #-}
import Data.Dynamic
import Data.Set
whatIsIt :: Dynamic -> String
whatIsIt a =
if | Just (_ :: [Int]) <- fromDynamic a -> "[Int]"
| Just (_ :: [String]) <- fromDynamic a -> "[String]"
| Just (_ :: Set Int) <- fromDynamic a -> "Set Int"
| Just (_ :: Set String) <- fromDynamic a -> "Set String"
| otherwise -> error "Unexpected type"
main = do
putStrLn $ whatIsIt $ toDyn ([1, 2, 3] :: [Int])
putStrLn $ whatIsIt $ toDyn (["1", "2", "3"] :: [String])
putStrLn $ whatIsIt $ toDyn (Data.Set.fromList ["1", "2", "3"] :: Set String)
Output:
[Int]
[String]
Set String
However I must outline boldly that this is far from a typical scenario of Haskell programming. The language's type-system is powerful enough to solve extremely intricate problems while maintaining all the type-level information (and safety). Dynamic is only used in very special cases in low-level libraries.
GHC does even more type erasure than the JVM; at runtime the types are completely gone (not just the type parameters).
Haskell's approach to types is to use them at compile time to guarantee that no ill-typed operation can ever be carried out, and since Haskell doesn't have OO-style subtyping and dynamic dispatch, there's no purpose at all to keeping the types around. So data is compiled to a memory structure that simply contains the right values, and functions are compiled with baked-in knowledge of the structure of the types on which they operate1, and just blindly expect their arguments to have that structure. That's why you get fun things like segmentation faults if you mess with unsafeCoerce incorrectly, not just a runtime exception saying the value was not of the expected type; at runtime Haskell has no idea whether a value is of any given type.
So rather than Haskell giving "the right answer" to the equivalent program, Haskell disallows your program as unsafe! There is no Any type in Haskell to which you can cast whatever you want.
That's not 100% true; in both Haskell and Scala there are ways of keeping type information alive at runtime. Essentially it's done by creating ordinary data structures that represent types, and passing them around together values that are of those types, so at runtime you can refer to the type representation object for information about the type of the other object. There are library and language facilities in both languages to let you use this mechanism at a higher (and more principled) level, so that it's easier to use safely. Because it requires the type tokens to be passed around, you have to "opt-in" to such features, and your callers have to be aware of it to pass you the required type tokens (whether the actual generation and passing of the token is done implicitly or explicitly).
Without using such features, Haskell provides no way to pattern match on a value that could be of type List Int or Set String to find out which one it is. Either you're using a monomorphic type, in which case it can only be one type and the others will be rejected, or you're using a polymorphic type, in which case you can only apply code to it that will do the same thing2 regardless of which concrete type instantiates the polymorphic type.
1 Except for polymorphic functions, which assume nothing about their polymorphic arguments, and so can basically do nothing with them except pass them to other polymorphic functions (with matching type class constraints, if any).
2 Type class constrained polymorphic types are the only exception to this. Even then, if you've got a value a type that's a member of some type class, all you can do with it is pass it to other functions that accept values in any type that is a member of that type class. And if those functions are general functions defined outside of the type class in question, they'll be under the same restriction. It's only the type class methods themselves that can actually "do something different" for different types in the class, and that's because they are the union of a whole bunch of monomorphic definitions that operate on one particular type in the class. You can't write code that gets to take a polymorphic value, inspect it to see what it was instantiated with, and then decide what to do.
Of course Haskell prints the right answer:
import Data.Set
import Data.Typeable
main = do
let li=[1,2,3]
let ls=["1","2","3"]
let si=Data.Set.fromList[1,2,3]
let ss=Data.Set.fromList["1","2","3"]
print $ typeOf li
print $ typeOf ls
print $ typeOf si
print $ typeOf ss
prints
[Integer]
[[Char]]
Set Integer
Set [Char]