【问题标题】:Lowering of higher order function with linear types使用线性类型降低高阶函数
【发布时间】:2021-09-06 09:50:03
【问题描述】:

我最近一直在尝试线性类型,并且一直想知道是否可以进行以下转换。没有线性类型肯定是无效的。

目的是降低高阶函数参数。这应该没问题,因为线性类型确保 HOF 只被调用一次,所以恰好存在 1 个 a 值。问题是如何转义 lambda 并观察 a

   lower :: ((a %1-> b) %1-> r) %1-> b %1-> (r,a)

【问题讨论】:

  • lower 的第一个参数是线性id 时会发生什么?
  • 这是一个很好的问题,我的第一反应是它使上述功能变得不可能。想多了,我不确定是不是这样。至少,如果随后为 r 提供了 a,那么 a 和 b 上的线性注释仍然成立
  • 跟进:lower :: (forall r. (a %1-> r) %1-> r) %1-> a 呢?这几乎是但不完全是双重否定消除......
  • @DanielWagner,这看起来很合理,您也许可以走得更远:blah :: ((a %1-> ()) %1-> ()) %1-> a 似乎很可能。

标签: haskell higher-order-functions linear-types


【解决方案1】:

编辑:您不能安全地实现lower。正如 dfeuer 和 danidiaz 在 cmets 中所说:如果 lower 的第一个参数是恒等函数怎么办?通过我在下面的旧答案中展示的实现,您可以编写:

main :: IO ()
main = putStrLn (snd (lower (\x -> x) False))

这会在运行时产生错误:

Lin: Prelude.undefined
CallStack (from HasCallStack):
  error, called at libraries/base/GHC/Err.hs:74:14 in base:GHC.Err
  undefined, called at Lin.hs:25:19 in main:Main

所以线性类型并没有提供足够的保证来保证实现这个功能是安全的。

Edit2:您可以通过将签名稍微更改为:lower :: ((a %1 -> b) %1 -> Ur r) %1 -> b %1 -> (Ur r, a) 来保存这种情况。这样就不可能将线性参数a %1 -> b 存储在r 中。

import Control.Exception (evaluate)

-- from Data.Unrestricted.Linear from linear-base
data Ur a where
  Ur :: a -> Ur a

lower :: ((a %1 -> b) %1 -> Ur r) %1 -> b %1 -> (Ur r, a)
lower = toLinear2 lower'

lower' :: ((a %1 -> b) %1 -> Ur r) -> b -> (Ur r, a)
lower' f b = unsafePerformIO $ do
  ref <- newIORef undefined
  r <- evaluate $ f (toLinear (\a -> unsafePerformIO $ b <$ writeIORef ref a))
  a <- readIORef ref
  pure (r, a)

旧答案

这可能不是您要寻找的答案,但如果您确定它是安全的,那么您可以稍微改变一下规则:

{-# LANGUAGE LinearTypes, GADTs #-}
import System.IO.Unsafe
import Data.IORef
import Unsafe.Coerce
import Control.Exception (evaluate)

-- 'coerce', 'toLinear' and 'toLinear2' are also found in Unsafe.Linear from linear-base

coerce :: forall a b. a %1-> b
coerce a =
  case unsafeEqualityProof @a @b of
    UnsafeRefl -> a
{-# INLINE coerce #-}

toLinear :: (a %p-> b) %1-> (a %1-> b)
toLinear = coerce

toLinear2 :: (a %p-> b %q-> c) %1-> (a %1-> b %1-> c)
toLinear2 = coerce

lower :: ((a %1 -> b) %1 -> r) %1 -> b %1 -> (r, a)
lower = toLinear2 lower'

lower' :: ((a %1 -> b) %1 -> r) -> b -> (r, a)
lower' f b = unsafePerformIO $ do
  ref <- newIORef undefined
  r <- evaluate $ f (toLinear (\a -> unsafePerformIO $ b <$ writeIORef ref a))
  a <- readIORef ref
  pure (r, a)

我认为线性类型的主要目的只是为了获取额外的类型级信息,所以我认为没有任何安全的原语可以让你更干净地做到这一点(虽然我不知道所有的细节和出局)。线性类型确实允许你实现一个安全的接口,下面有不安全的操作。例如,查看toLinearthis file from linear-base 中使用了多少次。

也许上面的不安全位可以外包给较低级别​​的库。也许像 promises 这样的东西,但随后是线性类型。

【讨论】:

  • 是的,我认为它可能会涉及到 performunsafeio。我已经接近它,到目前为止不成功,对类型有额外的限制,使用状态单子。我确实认为它会屈服于这一点,但有适当的限制
  • 我对线性类型了解不多,但是如果lower 的第一个参数存储它的参数在它的结果中而不是实际调用我> 吗?然后,如果首先使用结果的a 组件,您将得到未定义。
  • @dfeuer 你是对的。我刚刚测试过。事实证明这是不安全的。
  • 我不认为使用 and 的反例是有效的。据我了解,线性类型仅在线性消耗输出时才保证多重性。
  • @OllieB 我添加了第二个编辑,以显示您可以进行轻微更改以使其(希望)再次安全。
猜你喜欢
  • 2012-10-25
  • 1970-01-01
  • 1970-01-01
  • 2020-02-16
  • 2020-05-17
  • 2013-01-16
  • 1970-01-01
相关资源
最近更新 更多