Combining lists of variants of different types in purescript - purescript

I have been playing around with Purescript as a means of experimenting with programming in a Haskell-like language with row and column polymorphism.
In particular, using Purescript's variant package, I was trying to write an operation to combine two lists list of "notes" sequentially, where the two lists have possibly disjoint sets of variants. The idea here is to allow users to be able to combine scores declared on some sub-set of notes to be automatically combined with scores containing a super-set, without preforming any explicit conversions.
For example, given the following boilerplate to make working with the variants easier:
import Data.Variant
import Prim.Row
data A = A
data B = B
data C = C
data D = D
data E = E
data F = F
data G = G
data H = H
_A = SProxy :: SProxy "_A"
_B = SProxy :: SProxy "_B"
_C = SProxy :: SProxy "_C"
_D = SProxy :: SProxy "_D"
_E = SProxy :: SProxy "_E"
_F = SProxy :: SProxy "_F"
_G = SProxy :: SProxy "_G"
I can define the following types:
type CNatural = Variant (
_A :: A,
_B :: B,
_C :: C,
_D :: D,
_E :: E,
_F :: F,
_G :: G)
type CPentatonic = Variant (
_A :: A,
_C :: C,
_D :: D,
_E :: E,
_G :: G)
And given those, I can define two scores:
score1 :: Array CPentatonic
score1 = [inj _A A,inj _C C]
score2 :: Array CNatural
score2 = [inj _C C, inj _B B]
So, to combine them, I want a function of signature
combine :: forall v w u. Union w v u =>
Array (Variant v)
-> Array (Variant w)
-> Array (Variant u)
However, my attempt at this:
combine x y = (map expand x) <> (map expand y)
yields
No type class instance was found for
Prim.Row.Union v2
t3
u4
The instance head contains unknown type variables. Consider adding a type annotation.
If I try changing the Union v w u constraint to a Union w v u constraint, the error goes back and forth between the first map expand, and the second map expand, but nothing I do seems to resolve both constraints, even if I have both a Union v w u and a Union w v u constraint, which I thought would have been redundant.
Is there something I'm missing? Is it possible to do something like this in Purescript using the variant library?

If you want to produce Variant u by using expand you have to require that both v and w are subrows of u:
module Main where
import Prelude
import Data.Variant (expand, Variant)
import Prim.Row (class Union)
combine :: forall v v_ w w_ u. Union w w_ u => Union v v_ u =>
Array (Variant v)
-> Array (Variant w)
-> Array (Variant u)
combine arr1 arr2 = map expand arr1 <> map expand arr2
Here is a working gist loaded into try.purescript.

Related

Mapping homogeneous record type

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.

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.

API for handling polymothinc records

It is a little bit custom issue, is not contrived, but just simplified as possible.
-- this record that has fn that handles both x and y,
-- x and y supposed to be Functors, a arbitrary param for x/y, r is arbitrary result param
type R0 a x y r =
{ fn :: x a -> y a -> r
}
-- this record that has fn that handles only x
type R1 a x r =
{ fn :: x a -> r
}
What I want is a common API (function) that could handle values of R0 and R1 types.
So I do a sum type
data T a x y r
= T0 (R0 a x y r)
| T1 (R1 a x r)
And I declare this function, there is a constraint that x and y have to be Functors.
some :: ∀ a x y r.
Functor x =>
Functor y =>
T a x y r -> a
some = unsafeCoerce -- just stub
Then try to use it.
data X a = X { x :: a}
data Y a = Y { y :: a }
-- make X type functor
instance functorX :: Functor X where
map fn (X val) = X { x: fn val.x }
-- make Y type functor
instance functorY :: Functor Y where
map fn (Y val) = Y { y: fn val.y }
-- declare functions
fn0 :: ∀ a. X a -> Y a -> Unit
fn0 = unsafeCoerce
fn1 :: ∀ a. X a -> Unit
fn1 = unsafeCoerce
Trying to apply some:
someRes0 = some $ T0 { fn: fn0 } -- works
someRes1 = some $ T1 { fn: fn1 } -- error becase it can not infer Y which should be functor but is not present in f1.
So the question is: Is it possible to make such API work somehow in a sensible/ergonomic way (that would not require some addition type annotations from a user of this API)?
I could apparently implement different functions some0 and some1 for handling both cases, but I wonder if the way with a single function (which makes API surface simpler) is possilbe.
And what would be other suggestions for implementing such requirements(good API handling such polymorphic record types that differ in a way described above, when one of the records has exessive params)?
You should make T1 and T0 separate types and then make function some itself overloaded to work with them both:
data T0 x y r a = T0 (R0 a x y r)
data T1 x r a = T1 (R1 a x r)
class Some t where
some :: forall a. t a -> a
instance someT0 :: (Functor x, Functor y) => Some (T0 x y r) where
some = unsafeCoerce
instance someT1 :: Functor x => Some (T1 x r) where
some = unsafeCoerce
An alternative, though much less elegant, solution would be to have the caller of some explicitly specify the y type with a type signature. This is the default approach in situations when a type can't be inferred by the compiler:
someRes1 :: forall a. a
someRes1 = some (T1 { fn: fn1 } :: T a X Y Unit)
Note that I had to add a type signature for someRes1 in order to have the type variable a in scope. Otherwise I couldn't use it in the type signature T a X Y Unit.
An even more alternative way to specify y would be to introduce a dummy parameter of type FProxy:
some :: ∀ a x y r.
Functor x =>
Functor y =>
FProxy y -> T a x y r -> a
some _ = unsafeCoerce
someRes0 = some FProxy $ T0 { fn: fn0 }
someRes1 = some (FProxy :: FProxy Maybe) $ T1 { fn: fn1 }
This way you don't have to spell out all parameters of T.
I provided the latter two solutions just for context, but I believe the first one is what you're looking for, based on your description of the problem mentioning "polymorphic methods". This is what type classes are for: they introduce ad-hoc polymorphism.
And speaking of "methods": based on this word, I'm guessing those fn functions are coming from some JavaScript library, right? If that's the case, I believe you're doing it wrong. It's bad practice to leak PureScript-land types into JS code. First of all JS code might accidentally corrupt them (e.g. by mutating), and second, PureScript compiler might change internal representations of those types from version to version, which will break your bindings.
A better way is to always specify FFI bindings in terms of primitives (or in terms of types specifically intended for FFI interactions, such as the FnX family), and then have a layer of PureScript functions that transform PureScript-typed parameters to those primitives and pass them to the FFI functions.

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

generalize purescript function using MonadAff

I have a question about generalizing. Starting with this function:
test0 :: String -> String
test0 s = s
we can generalize it in its argument:
test1 :: forall a. Show a => a -> String
test1 s = show s
or in its functional result:
test12 :: forall a. Show a => String -> a
test12 s = s
Now consider the following function:
test2 :: forall e. Aff e Int
test2 s = pure 0
I would like to generalize it in its functional result:
test3 :: forall e m. MonadAff e m => m e Int
test3 s = pure 0
However, I now get an error:
Could not match kind type with kind # Control.Monad.Eff.Effect while checking the kind of MonadAff e m => m e Int in value declaration test3.
I cannot understand why. Moreover, I've found an example of similar such generalizing in Hyper.Node.Server, for example in this type:
write :: forall m e. MonadAff e m => Buffer -> NodeResponse m e
The constraint MonadAff e m asserts that the monad m somehow wraps Aff e somewhere inside. But it doesn't assert that monad m itself must have a type argument e. That would be awfully restrictive, wouldn't it?
Therefore, when constructing your return type, don't apply m to e:
test3 :: forall e m. MonadAff e m => m Int
test3 = pure 0
The example you found is quite different. Here, the function is not returning a value in m, like in your test3, but rather a value NodeResponse, which is a wrapper around a function that returns m Unit.