Mapping homogeneous record type - purescript

Suppose we have a record type that is homogeneous.
type RecI = { a :: Int, b :: Int, c :: Int, d :: Int, e :: Int }
We want to get from it type with the same keys but different value type:
type RecS = { a :: String, b :: String, c :: String, d :: String, e :: String }
Is it possible to get RecS type without explicitly defining all the keys from RecI?
And the second part of the question, what is the best way to implement mapping function from one type to another:
mapItoS :: (Int -> String) -> RecI -> RecS
?

To get a free-ish conversion from Int to String at type level, just give your record a parameter, then instantiate it with Int to get RecI and with String to get RecS:
type Rec a = { a :: a, b :: a, c :: a, d :: a, e :: a }
type RecI = Rec Int
type RecS = Rec String
To implement mapItoS, you can first convert to a Foreign.Object using fromHomogeneous, then map the function over it, then convert back to the record.
Unfortunately there is no toHomogeneous function, because in general you can't be sure that the Foreign.Object actually contains all required keys. But no matter: in this particular case you can be sure that it does, so you can get away with unsafeCoerce:
mapItoS :: forall a b. (a -> b) -> Rec a -> Rec b
mapItoS f = fromHomogeneous >>> map f >>> unsafeCoerce

A small self plug which is strictly relevant to the question :-P I've just published a library which provides many instances which allow a PureScripter to work with homogeneous Record and Variant:
https://pursuit.purescript.org/packages/purescript-homogeneous
I think it should have much better inference than solutions like heterogeneous. Please check it out and let me know what do you think.

Related

How do I get the name of the constructor for a product type in purescript?? Similar to haskell conNameOf

I want to extract the value of the constructor for my data which is a sum of product types data X = Xa A | Xb B | Xcd C D | Xefg E F G.... , where A B C... are of the type data A = A {a :: xyz , b :: abc..}
I want a function that gets takes in a value of type X and gives me "Xa", "Xb" .. based on the type. I know I can use case but is there a better way to do this?? Haskell has the toConstr function for this purpose,
I don't think there is a function in the standard library, but if you don't mind using the RTTI mechanisms (which is what conNameOf is using), you can relatively easily make your own using Generic: just convert the value to its generic representation using from, and then extract the constructor by matching on Sum and Constructor:
class ConstrName rep where
constrName' :: rep -> String
instance IsSymbol name => ConstrName (Constructor name a) where
constrName' (Constructor _) = reflectSymbol (Proxy :: Proxy name)
instance (ConstrName a, ConstrName b) => ConstrName (Sum a b) where
constrName' (Inl a) = constrName' a
constrName' (Inr b) = constrName' b
constrName :: forall a rep. Generic a rep => ConstrName rep => a -> String
constrName a = constrName' $ from a
Usage:
> constrName (Xa $ A { a: ..., b: ..., .... })
"Xa"

Learning Purescript, some help defining types

I'm new to Functional Programming. I've used Ramda a bit (JavaScript library), but nothing like the type system in Purescript.
I have an idea that I feel should be expressible with Purescript's type system, but I'm not really sure where to start.
Lets say I'm trying to define some types for a Sudoku Board
newtype Index = Index Int
newtype Column = Column Int
newtype Row = Row Int
newtype Box = Box Int
I'd like to define what addition looks like for these types
In sudocode:
indexAddition :: (Index | Int) -> (Index | Int) -> Index
indexAddition a b = (a + b) % 81
RowAddition :: (Row | Int) -> (Row | Int) -> Row
RowAddition a b = (a + b) % 9
ColumnAddition and BoxAddition can probably me merged with RowAddition since they're gonna be basically the same.
-- I have to be able to say that a is a subset of Int, but Int isn't a type class
FooAddition :: forall a. Int a => a -> a -> a
FooAddition a b = (a + b) % 9
I somehow feel like I'm likely starting off on the wrong foot here.
Any help?
To answer your question directly, the way to have a function that works with different types, but a limited set of them (also known as "overloaded function") is type classes. More specifically, such function should be a method of a type class, and then you create an instance for each type (or combination of types) you'd like it to work with.
So the most straightforward approach would be this:
class IndexAddition a b where
indexAddition :: a -> b -> Index
instance addIntInt :: IndexAddition Int Int where
indexAddition a b = Index ((a+b) % 81)
instance addIntIndex :: IndexAddition Int Index where
indexAddition a (Index b) = Index ((a+b) % 81)
instance addIndexInt :: IndexAddition Index Int where
indexAddition (Index a) b = Index ((a+b) % 81)
instance addIndexIndex :: IndexAddition Index Index where
indexAddition (Index a) (Index b) = Index ((a+b) % 81)
As you can see, I made four instances, one for every combination of Index and Int. This works, but is admittedly a bit elaborate. Especially if you add a third parameter or a third possible type.
To make this a bit shorter and more manageable, you might observe that in order to add particular types, all you need from them is a way to convert to an Int. If you have that, you can convert both parameters to Int, then add, then wrap in Index:
class IsInt a where toInt :: a -> Int
instance ciIndex :: IsInt Index where toInt (Index a) = a
instance ciInt :: IsInt Int where toInt a = a
indexAddition :: forall a b. IsInt a => IsInt b => a -> b -> Index
indexAddition a b = Index ((toInt a + toInt b) % 81)
That said, I highly recommend that you reconsider your designs. Sure, ability to add numbers and indexes in any combination may look neat and nifty at first glance, but you probably will never need it in practice. And even if you do in some very specific circumstances, it's easy enough to just wrap/unwrap the values as needed. Trust me, I've been there. Many times.

No type class instance was found, the instance head contains unknown type variables

Well, just simplified as possible:
There is a function that takes functor and does whatever
sToInt :: ∀ a s. Functor s => s a -> Int
sToInt val = unsafeCoerce val
Usage of this function with functor S which param (v) is functor too.
-- declare date type S that is functor
data S (v :: Type -> Type) a = S (v a)
instance functorS :: Functor v => Functor (S v) where
map f (S a) = S (map f a)
sV :: ∀ v a. S v a
sV = unsafeCoerce 1
sss :: Int
sss = sToInt sV -- get the error here
No type class instance was found for
Data.Functor.Functor t2
The instance head contains unknown type variables. Consider adding a type annotation.
while applying a function sToInt
of type Functor t0 => t0 t1 -> Int
to argument sV
while checking that expression sToInt sV
has type Int
in value declaration sss
where t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
So it doesn't like S Functor instance has v param Functor constraint, I wonder why getting this error and how to fix it for this case.
This doesn't have to do with v or with the specific shape of S. Try this instead:
sV :: forall f a. Functor f => f a
sV = unsafeCoerce 1
sss :: Int
sss = sToInt sV
You get a similar error.
Or here's an even more simplified version:
sV :: forall a. a
sV = unsafeCoerce 1
sss :: Int
sss = sToInt sV
Again, same error.
The problem is that sToInt must get a Functor instance as a parameter (that's what the Functor s => bit in its type signature says), and in order to pick which Functor instance to pass, the compiler needs to know the type of the value. Like, if it's Maybe a, it will pass the Functor Maybe instance, and if it's Array a, it will pass the Functor Array instance, and so on.
Usually the type can be inferred from the context. For example when you say map show [1,2,3], the compiler knows that map should come from Functor Array, because [1,2,3] :: Array Int.
But in your case there is nowhere to get that information: sV can return S v for any v, and sToInt can also take any functor type. There is nothing to tell the compiler what the type should be.
And the way to fix this is obvious: if there is no context information for the compiler to get the type from, you have to tell it what the type is yourself:
sss :: Int
sss = sToInt (sV :: S Maybe _)
This will be enough for the compiler to know that v ~ Maybe, and it will be able to construct a Functor (S Maybe) instance and pass it to sToInt.
Alternatively, if you want the consumer of sss to decide what v is, you can add an extra dummy parameter to capture the type, and require that the consumer pass in a Functor v instance:
sss :: forall v. Functor v => FProxy v -> Int
sss _ = sToInt (sV :: S v _)
ddd :: Int
ddd = sss (FProxy :: FProxy Maybe)
In Haskell you can do this with visible type applications instead of FProxy, but PureScript, sadly, doesn't support that yet.
Even more alternatively, if sToInt doesn't actually care for a Functor instance, you can remove that constraint from it, and everything will work as-is:
sToInt :: forall s a. s a -> Int
sToInt a = unsafeCoerce a
sV :: forall v a. S v a
sV = unsafeCoerce 1
sss :: Int
sss = sToInt sV
This works because PureScript allows for ambiguous (aka "unknown") types to exist as long as they're not used for selecting instances.

Similar record types in a list/array in purescript

Is there any way to do something like
first = {x:0}
second = {x:1,y:1}
both = [first, second]
such that both is inferred as {x::Int | r} or something like that?
I've tried a few things:
[{x:3}] :: Array(forall r. {x::Int|r}) -- nope
test = Nil :: List(forall r. {x::Int|r})
{x:1} : test -- nope
type X r = {x::Int | r}
test = Nil :: List(X) -- nope
test = Nil :: List(X())
{x:1} : test
{x:1, y:1} : test -- nope
Everything I can think of seems to tell me that combining records like this into a collection is not supported. Kind of like, a function can be polymorphic but a list cannot. Is that the correct interpretation? It reminds me a bit of the F# "value restriction" problem, though I thought that was just because of CLR restrictions whereas JS should not have that issue. But maybe it's unrelated.
Is there any way to declare the list/array to support this?
What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)
One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:
type RecordRep = Unit -> Int
toRecordRep :: forall r. { x :: Int | r } -> RecordRep
toRecordRep {x} _ = x
-- Construct the array using `toRecordRep`
test :: Array RecordRep
test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]
-- Later use the operation
allTheXs :: Array Int
allTheXs = test <#> \r -> r unit
If you have multiple such operations, you can always make a record of them:
type RecordRep =
{ getX :: Unit -> Int
, show :: Unit -> String
, toJavaScript :: Unit -> Foreign.Object
}
toRecordRep r =
{ getX: const r.x
, show: const $ show r.x
, toJavaScript: const $ unsafeCoerce r
}
(note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)
But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)
newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)
toRecordRep :: forall r. {x::Int|r} -> RecordRep
toRecordRep r = RecordRep \f -> f r
test :: Array RecordRep
test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]
allTheXs = test <#> \(RecordRep r) -> r _.x
The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.
Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.
Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:
newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)
toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
toRecordRep r = RecordRep \f -> f r
test :: Array RecordRep
test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]
showAll = test <#> \(RecordRep r) -> r show
Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.
And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.
Incidentally, this is how Haskell language support for this works as well.
Folloing #FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
Finally we will be able to build an Array of records which will be "isomorphic" to:
exists tail. Array { x :: Int | tail }
Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.
newtype Exists f = Exists (forall a. f (a :: #Type))
We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:
module Main where
import Prelude
import Unsafe.Coerce (unsafeCoerce)
import Data.Newtype (class Newtype, unwrap)
newtype Exists f = Exists (forall a. f (a :: #Type))
mkExists :: forall f a. f a -> Exists f
mkExists r = Exists (unsafeCoerce r :: forall a. f a)
runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
runExists g (Exists f) = g f
Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:
newtype R t = R { x :: Int | t }
derive instance newtypeRec :: Newtype (R t) _
Now we can build an Array using mkExists:
arr :: Array (Exists R)
arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]
and process values using runExists:
x :: Array [ Int ]
x = map (runExists (unwrap >>> _.x)) arr

Syntax for polymorphic record type alias

Is there a way to define a type/alias representing a row polymorphic record?
So given this example
tester :: forall r. {val :: Int | r} -> Int
tester a =
a.val
callTester = tester {val: 1, b: 2}
I want to define the record type as an alias. Something like
type Val = forall r. {val :: Int | r}
tester :: Val -> Int
tester a =
a.val
callTester = tester {val: 1, b: 2}
But that will not compile.
For larger records and more complex functions defining the types multiple times leads to quite a lot of noise. It would be nice to factor this out.
e.g. fn :: a -> b -> a I have to define a twice
For non-polymorphic records its simple but I explicitly want to allow records with additional fields that are not know upfront.
Thanks
Here is how I got it working for the example above.
type Val r = {val :: Int | r}
tester :: forall a. Val a -> Int
tester v =
v.a
callTester = tester {val: 1, b: 2}
So define a type, and have the forall on the functions using the type