纯脚本中 list/array 中的相似记录类型
Similar record types in a list/array in purescript
有什么办法可以做到
first = {x:0}
second = {x:1,y:1}
both = [first, second]
使得 both
被推断为 {x::Int | r}
或类似的东西?
我尝试了一些方法:
[{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
我能想到的一切似乎都在告诉我,不支持将这样的记录合并到一个集合中。有点像,函数可以是多态的,但列表不能。这是正确的解释吗?它让我想起了一些 F# "value restriction" 问题,虽然我认为那只是因为 CLR 限制,而 JS 不应该有那个问题。但也许它是无关的。
有什么方法可以声明 list/array 来支持这个吗?
您正在寻找的是“existential types”,而 PureScript 只是不像 Haskell 那样在语法级别上支持它们。但是你可以自己动手:-)
一种方法 是"data abstraction" - 即根据您要对其执行的操作对数据进行编码。例如,假设您希望在某个时候从它们中获取 x
的值。在这种情况下,制作一个数组:
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
如果您有多个这样的操作,您可以随时记录它们:
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
}
(注意每个函数中的 Unit
参数 - 它们是为了懒惰而存在的,假设每个操作都可能很昂贵)
但是如果你真的需要打字机,你可以按我说的做"poor man's existential type"。如果你仔细观察,存在类型只不过是 "deferred" 类型检查 - 推迟到你需要查看类型的地步。在 ML 语言中推迟某些事情的机制是什么?没错——一个函数! :-)
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
它的工作方式是 RecordRep
包装一个函数,该函数接受另一个函数,该函数在 r
中是多态的 - 也就是说,如果您正在查看 RecordRep
, 你必须准备好给它一个可以与任何 r
一起工作的函数。 toRecordRep
以这样一种方式包装记录,使其精确类型在外部不可见,但它将用于实例化您最终将提供的通用函数。在我的示例中,这样的函数是 _.x
.
但是请注意,这里存在问题:当您开始使用数组的元素时,行 r
实际上是未知的,因此您无法对其执行任何操作。就像,完全一样。您所能做的就是获取 x
字段,因为它的存在是硬编码在签名中的,但除了 x
- 您只是不知道。这是设计使然:如果您想将 anything 放入数组,您必须准备好从数组中获取 anything。
现在,如果您确实想对这些值做些什么,您必须通过限制 r
来解释,例如:
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
像这样传递 show
函数是可行的,因为我们以 Show {x::Int|r}
必须存在的方式限制了行 r
,因此,应用 show
到 {x::Int|r}
必须工作。根据需要重复您自己的类型 类。
这里是有趣的部分:由于类型 类 是作为函数字典实现的,所以上面描述的两个选项实际上是等价的 - 在这两种情况下你最终传递函数字典,仅在第一种情况下它是显式的,但在第二种情况下编译器会为您完成。
顺便说一下,Haskell 语言支持也是如此。
遵循基于 "existential types" 的@FyodorSoikin 回答以及我们在 purescript-exists
中可以找到的内容,我们可以提供另一种解决方案。
最后,我们将能够构建一个 Array
的记录,这些记录将是 "isomorphic" 到:
exists tail. Array { x :: Int | tail }
让我们从类型构造函数开始,它可用于对行类型(类型 #Type
)进行存在量化。我们无法在此处使用 purescript-exists
中的 Exists
,因为 PureScript 没有种类多态性,并且原始 Exists
在 Type
.
上进行了参数化
newtype Exists f = Exists (forall a. f (a :: #Type))
我们可以遵循并重新实现 Data.Exists
中的 (<Ctrl-c><Ctrl-v>
;-)) 定义,并构建一组工具来处理这些 Exists
值:
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
使用它们,我们能够构建 Records
的 Array
和 "any" 尾部,但我们必须在 newtype
之前将任何此类记录类型包装起来:
newtype R t = R { x :: Int | t }
derive instance newtypeRec :: Newtype (R t) _
现在我们可以使用 mkExists
构建一个 Array
:
arr :: Array (Exists R)
arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]
和过程值使用 runExists
:
x :: Array [ Int ]
x = map (runExists (unwrap >>> _.x)) arr
有什么办法可以做到
first = {x:0}
second = {x:1,y:1}
both = [first, second]
使得 both
被推断为 {x::Int | r}
或类似的东西?
我尝试了一些方法:
[{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
我能想到的一切似乎都在告诉我,不支持将这样的记录合并到一个集合中。有点像,函数可以是多态的,但列表不能。这是正确的解释吗?它让我想起了一些 F# "value restriction" 问题,虽然我认为那只是因为 CLR 限制,而 JS 不应该有那个问题。但也许它是无关的。
有什么方法可以声明 list/array 来支持这个吗?
您正在寻找的是“existential types”,而 PureScript 只是不像 Haskell 那样在语法级别上支持它们。但是你可以自己动手:-)
一种方法 是"data abstraction" - 即根据您要对其执行的操作对数据进行编码。例如,假设您希望在某个时候从它们中获取 x
的值。在这种情况下,制作一个数组:
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
如果您有多个这样的操作,您可以随时记录它们:
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
}
(注意每个函数中的 Unit
参数 - 它们是为了懒惰而存在的,假设每个操作都可能很昂贵)
但是如果你真的需要打字机,你可以按我说的做"poor man's existential type"。如果你仔细观察,存在类型只不过是 "deferred" 类型检查 - 推迟到你需要查看类型的地步。在 ML 语言中推迟某些事情的机制是什么?没错——一个函数! :-)
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
它的工作方式是 RecordRep
包装一个函数,该函数接受另一个函数,该函数在 r
中是多态的 - 也就是说,如果您正在查看 RecordRep
, 你必须准备好给它一个可以与任何 r
一起工作的函数。 toRecordRep
以这样一种方式包装记录,使其精确类型在外部不可见,但它将用于实例化您最终将提供的通用函数。在我的示例中,这样的函数是 _.x
.
但是请注意,这里存在问题:当您开始使用数组的元素时,行 r
实际上是未知的,因此您无法对其执行任何操作。就像,完全一样。您所能做的就是获取 x
字段,因为它的存在是硬编码在签名中的,但除了 x
- 您只是不知道。这是设计使然:如果您想将 anything 放入数组,您必须准备好从数组中获取 anything。
现在,如果您确实想对这些值做些什么,您必须通过限制 r
来解释,例如:
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
像这样传递 show
函数是可行的,因为我们以 Show {x::Int|r}
必须存在的方式限制了行 r
,因此,应用 show
到 {x::Int|r}
必须工作。根据需要重复您自己的类型 类。
这里是有趣的部分:由于类型 类 是作为函数字典实现的,所以上面描述的两个选项实际上是等价的 - 在这两种情况下你最终传递函数字典,仅在第一种情况下它是显式的,但在第二种情况下编译器会为您完成。
顺便说一下,Haskell 语言支持也是如此。
遵循基于 "existential types" 的@FyodorSoikin 回答以及我们在 purescript-exists
中可以找到的内容,我们可以提供另一种解决方案。
最后,我们将能够构建一个 Array
的记录,这些记录将是 "isomorphic" 到:
exists tail. Array { x :: Int | tail }
让我们从类型构造函数开始,它可用于对行类型(类型 #Type
)进行存在量化。我们无法在此处使用 purescript-exists
中的 Exists
,因为 PureScript 没有种类多态性,并且原始 Exists
在 Type
.
newtype Exists f = Exists (forall a. f (a :: #Type))
我们可以遵循并重新实现 Data.Exists
中的 (<Ctrl-c><Ctrl-v>
;-)) 定义,并构建一组工具来处理这些 Exists
值:
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
使用它们,我们能够构建 Records
的 Array
和 "any" 尾部,但我们必须在 newtype
之前将任何此类记录类型包装起来:
newtype R t = R { x :: Int | t }
derive instance newtypeRec :: Newtype (R t) _
现在我们可以使用 mkExists
构建一个 Array
:
arr :: Array (Exists R)
arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]
和过程值使用 runExists
:
x :: Array [ Int ]
x = map (runExists (unwrap >>> _.x)) arr