API 用于处理 polymothinc 记录
API for handling polymothinc records
有点自定义问题,不是做作,而是尽可能简化。
-- 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 以某种方式以 sensible/ergonomic 的方式工作(这不需要此 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 领域的类型泄漏到 JS 代码中是不好的做法。首先,JS 代码可能会意外损坏它们(例如通过变异),其次,PureScript 编译器可能会在不同版本之间更改这些类型的内部表示,这将破坏您的绑定。
更好的方法是始终根据原语(或根据专门用于 FFI 交互的类型,例如 the FnX
family)指定 FFI 绑定,然后使用一层 PureScript 函数来转换 PureScript为这些原语输入类型参数并将它们传递给 FFI 函数。
有点自定义问题,不是做作,而是尽可能简化。
-- 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 以某种方式以 sensible/ergonomic 的方式工作(这不需要此 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 领域的类型泄漏到 JS 代码中是不好的做法。首先,JS 代码可能会意外损坏它们(例如通过变异),其次,PureScript 编译器可能会在不同版本之间更改这些类型的内部表示,这将破坏您的绑定。
更好的方法是始终根据原语(或根据专门用于 FFI 交互的类型,例如 the FnX
family)指定 FFI 绑定,然后使用一层 PureScript 函数来转换 PureScript为这些原语输入类型参数并将它们传递给 FFI 函数。