问题描述
-- 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
}
我想要的是一个可以处理 R0 和 R1 类型值的通用 API(函数)。
所以我做了一个和类型
data T a x y r
= T0 (R0 a x y r)
| T1 (R1 a x r)
并且我声明了这个函数,有一个约束,即 x
和 y
必须是函子。
some :: ∀ a x y r.
Functor x =>
Functor y =>
T a x y r -> a
some = unsafeCoerce -- just stub
然后尝试使用它。
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
尝试申请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.
所以问题是:是否有可能以一种合理/符合人体工程学的方式使此类 API 工作(不需要此 API 的用户提供一些附加类型注释)?
我显然可以实现不同的函数 some0
和 some1
来处理这两种情况,但我想知道使用单个函数(这使 API 表面更简单)的方式是否可行。
还有什么其他建议可以实现这些要求(当其中一个记录具有过多参数时,良好的 API 处理这种以上述方式不同的多态记录类型)?
解决方法
您应该使 T1
和 T0
分开类型,然后使函数 some
本身重载以同时使用它们:
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
另一种虽然不太优雅的解决方案是让 some
的调用者使用类型签名显式指定 y
类型。这是编译器无法推断类型的情况下的默认方法:
someRes1 :: forall a. a
someRes1 = some (T1 { fn: fn1 } :: T a X Y Unit)
请注意,我必须为 someRes1
添加类型签名,以便在范围内包含类型变量 a
。否则我无法在类型签名 T a X Y Unit
中使用它。
另一种指定 y
的替代方法是引入 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 }
这样您就不必拼出 T
的所有参数。
我提供了后两个解决方案只是为了上下文,但我相信第一个是您正在寻找的,基于您对提到“多态方法”的问题的描述。这就是类型类的用途:它们引入了临时多态性。
说到“方法”:根据这个词,我猜那些 fn
函数来自某个 JavaScript 库,对吧?如果是这样的话,我相信你做错了。将 PureScript-land 类型泄漏到 JS 代码中是不好的做法。首先,JS 代码可能会意外损坏它们(例如通过变异),其次,PureScript 编译器可能会在不同版本之间更改这些类型的内部表示,这会破坏您的绑定。
更好的方法是始终根据原语(或专门用于 FFI 交互的类型,例如 the FnX
family)来指定 FFI 绑定,然后使用一层 PureScript 函数来转换 PureScript 类型这些原语的参数并将它们传递给 FFI 函数。