为什么 GHC 不认为函数是线性的?

Why doesn't GHC recognize the function as linear?

我有一个非常简单的片段:

{-# LANGUAGE LinearTypes #-}

module Lib where

data Peer st = Peer { data :: String } deriving Show

data Idle
data Busy

sendToPeer :: Peer Idle %1-> Int -> IO (Peer Busy)
sendToPeer c n = case c of Peer d -> pure $ Peer d

我在 resolver: ghc-9.0.1.

来自documentation

A function f is linear if: when its result is consumed exactly once, then its argument is consumed exactly once. Intuitively, it means that in every branch of the definition of f, its argument x must be used exactly once. Which can be done by

  • Returning x unmodified
  • Passing x to a linear function
  • Pattern-matching on x and using each argument exactly once in the same fashion.
  • Calling it as a function and using the result exactly once in the same fashion.

而我的函数 sendToPeer 正是这样做的 - 在 c 上进行模式匹配并且它的参数 d 使用一次 - 在 Peer d 中是线性的:

By default, all fields in algebraic data types are linear (even if -XLinearTypes is not turned on).

但是我得到一个错误:

• Couldn't match type ‘'Many’ with ‘'One’
        arising from multiplicity of ‘c’
• In an equation for ‘sendToPeer’:
          sendToPeer c n = case c of { Peer d -> pure $ Peer d }
   |
11 | sendToPeer c n =
   |            ^

没有pure:

sendToPeer :: Peer Idle %1-> Int -> Peer Busy
sendToPeer c n = case c of Peer d -> Peer d

但错误是一样的

使用 Control.Functor.Linear instead, as well as the IO from System.IO.Linear 中的 pure,因为 Prelude 的内容根本没有声明为线性的。

请注意,这个更简单的示例也无法编译:

sendToPeer :: Peer Idle %1-> IO (Peer Idle)
sendToPeer c = pure c

“没有 pure”版本的问题对我来说很可疑,我认为这是一个错误,因为如果在参数级别完成模式匹配,它似乎可以工作:

sendToPeer :: Peer Idle %1-> Int -> Peer Busy
sendToPeer (Peer d) n = Peer d

您遇到了多个问题:

  • Prelude.pure 不是线性的。您需要在 linear-base.
  • 中使用来自 Control.Functor.Linear 的线性 Applicative class 和相关方法函数 pure
  • Prelude.IO 不是线性的(即没有线性 Applicative class 的实例)。您需要在 linear-base.
  • 中使用 System.IO.Linear 中的线性 IO
  • case 语句无法与当前的 LinearTypes 扩展一起正常工作。

关于最后一点,GHC 手册指出:

A case expression may consume its scrutinee One time, or Many times. But the inference is still experimental, and may over-eagerly guess that it ought to consume the scrutinee Many times.

linear-base 的用户指南比较直白。它包括一个标题为 Case statements are not linear 的部分,建议您不能将它们用于线性函数。

目前,如果您想保留 Oneness,则必须避免仔细检查带有 case 的表达式。

以下出现 type-check。我 认为 我已经按照推荐的方式设置了导入。请注意 Data.Functor.LinearControl.Functor.Linear 中都有 pure 的版本,并且它们的工作方式不同。请参阅 documentation 顶部关于 Data.Functor.Linear 模块的注释。

{-# LANGUAGE LinearTypes #-}

module Lib where

import Prelude.Linear
import Control.Functor.Linear as Control
import qualified Prelude as NonLinear
import qualified System.IO.Linear as Linear

data Peer st = Peer String deriving Show

data Idle
data Busy

sendToPeer :: Peer Idle %1-> Int -> Linear.IO (Peer Busy)
sendToPeer (Peer d) n = Control.pure (Peer d)