Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
386 views
in Technique[技术] by (71.8m points)

purescript - 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)?

question from:https://stackoverflow.com/questions/65847655/api-for-handling-polymothinc-records

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...