当前位置: 首页 > 知识库问答 >
问题:

有没有办法在Haskell中模拟线性类型?

淳于哲
2023-03-14

我正在为一个系统建模,该系统有一个创建资源的操作和其他消耗该资源的操作。然而,一个给定的资源只能被消耗一次——有没有一种方法可以保证在编译时这样做?

具体来说,假设第一个操作烘焙蛋糕,还有另外两个操作,一个用于“选择吃”蛋糕,另一个用于“选择吃蛋糕”,我只能做其中一个。

-- This is my current "weakly typed" interface:
bake :: IO Cake
eat  :: Cake -> IO ()
keep :: Cake -> IO ()

-- This is OK
do
  brownie <- bake
  muffin <- bake
  eat brownie
  keep muffin

-- Eating and having the same cake is not OK:
do
  brownie <- bake
  eat brownie
  keep brownie -- oops! already eaten!

通过在我们使用蛋糕后在蛋糕上设置一个标志,很容易在运行时强制执行不保留已经吃过的蛋糕(反之亦然)的限制。但是有没有办法在编译时强制执行呢?

顺便说一句,这个问题是为了证明概念,所以我可以接受任何能给我想要的静态安全的黑魔法。

共有3个答案

岑俊明
2023-03-14

部分解决方案。我们可以定义一个包装类型

data Caked a = Caked { getCacked :: IO a } -- ^ internal constructor

其中我们不导出构造函数/访问器。

它将有两个几乎但不太像绑定的函数:

beforeCake :: IO a -> (a -> Caked b) -> Caked b
beforeCake a f = Caked (a >>= getCaked . f)

afterCake :: Caked a -> (a -> IO b) -> Caked b
afterCake (Caked a) f = Caked (a >>= f)

客户创建结块的值的唯一方法是:

eat :: Cake -> Caked ()
eat = undefined

keep :: Cake -> Caked ()
keep = undefined

我们会在回调中分配Cake值:

withCake :: (Cake -> Caked b) -> IO b
withCake = undefined

我认为,这将确保eatkeep在回调中只被调用一次。

问题:不能处理多个Cake分配,Cake值仍然可以脱离回调的范围(幻象类型在这里有帮助吗?)

邢乐
2023-03-14

Polakow在他的Haskell研讨会论文中展示了如何在Haskell(pdf)中嵌入完整的线性lambda演算。

其主要思想是使用一个输入和一个输出上下文对每个构造函数进行索引,跟踪在各个子项中消耗的资源。

乐正浩博
2023-03-14

在Haskell中,这个的基本版本可以用一个由蛋糕库索引的GADT来表示(由Nat-s列表表示):

{-# LANGUAGE
  TypeFamilies, GADTs, TypeOperators, PartialTypeSignatures,
  DataKinds, PolyKinds #-}

import GHC.TypeLits
import Data.Proxy
import GHC.Exts

-- Allocate a new cake
type family New cs where
  New '[]       = 0
  New (c ': cs) = c + 1

-- Constraint satisfiable if "c" is in "cs"
type family Elem c cs :: Constraint where
  Elem c (c ': cs)  = ()
  Elem c (c' ': cs) = Elem c cs

type family Remove c cs where
  Remove c '[]        = '[]  
  Remove c (c ': cs)  = cs
  Remove c (c' ': cs) = c' ': Remove c cs

data Bake :: [Nat] -> [Nat] -> * -> * where
  Pure :: a -> Bake cs cs a
  Bake :: (Proxy (New cs) -> Bake (New cs ': cs) cs' a) -> Bake cs cs' a
  Eat  :: Elem c cs => Proxy c -> Bake (Remove c cs) cs' a -> Bake cs cs' a
  Keep :: Elem c cs => Proxy c -> Bake cs cs' a -> Bake cs cs' a

ok :: Bake '[] _ _
ok =
  Bake $ \cake1 ->
  Bake $ \cake2 ->
  Eat cake1 $
  Keep cake2 $
  Eat cake2 $
  Pure ()

not_ok :: Bake '[] _ _
not_ok =
  Bake $ \cake1 ->
  Bake $ \cake2 ->
  Eat cake1 $
  Keep cake1 $ -- we already ate that
  Eat cake2 $
  Pure ()  

不幸的是,我们无法从Bake操作中删除类型注释,并将类型留待推断:

foo =
  Bake $ \cake1 ->
  Bake $ \cake2 ->
  Eat cake1 $
  Pure ()

-- Error: Could not deduce (Elem (New cs0) (New cs0 + 1 : New cs0 : cs0))

显然,(Elem(New cs0)(New cs0 1:New cs0:cs0))对所有cs0都是可以满足的,但GHC看不到这一点,因为它无法决定New cs0是否与New cs0 1不相等,因为GHC不能对灵活的cs0变量做任何假设。

如果我们添加NoMonomfigismRestrationfoo将进行typecheck,但这将使不正确的程序通过将所有Elem约束推到顶部进行typecheck。这仍然可以防止用不正确的术语做任何有用的事情,但这是一个相当丑陋的解决方案。

更一般地说,我们可以将Bake表示为一个索引的自由单子,这使我们可以用RebindableSyntax表示do,并允许对BakeF进行定义,这比我们之前看到的更加清晰。它还可以像普通的老式Freemonad一样简化样板文件,尽管我发现人们在实际代码中不太可能在两种不同的情况下使用索引的Free monad。

{-# LANGUAGE
  TypeFamilies, GADTs, TypeOperators, PartialTypeSignatures, StandaloneDeriving,
  DataKinds, PolyKinds, NoImplicitPrelude, RebindableSyntax, DeriveFunctor #-}

import Prelude hiding (Monad(..))
import GHC.TypeLits
import Data.Proxy
import GHC.Exts

class IxFunctor f where
  imap :: (a -> b) -> f i j a -> f i j b

class IxFunctor m => IxMonad m where
  return :: a -> m i i a
  (>>=)  :: m i j a -> (a -> m j k b) -> m i k b
  fail   :: String -> m i j a

infixl 1 >>
infixl 1 >>=

(>>) :: IxMonad m => m i j a -> m j k b -> m i k b
ma >> mb = ma >>= const mb

data IxFree f i j a where
  Pure :: a -> IxFree f i i a
  Free :: f i j (IxFree f j k a) -> IxFree f i k a

liftf :: IxFunctor f => f i j a -> IxFree f i j a
liftf = Free . imap Pure

instance IxFunctor f => IxFunctor (IxFree f) where
  imap f (Pure a)  = Pure (f a)
  imap f (Free fa) = Free (imap (imap f) fa)

instance IxFunctor f => IxMonad (IxFree f) where
  return = Pure
  Pure a  >>= f = f a
  Free fa >>= f = Free (imap (>>= f) fa)
  fail = error

-- Old stuff for Bake

type family New cs where
  New '[]       = 0
  New (c ': cs) = c + 1

type family Elem c cs :: Constraint where
  Elem c (c ': cs)  = ()
  Elem c (c' ': cs) = Elem c cs

type family Remove c cs where
  Remove c '[]        = '[]  
  Remove c (c ': cs)  = cs
  Remove c (c' ': cs) = c' ': Remove c cs

-- Now the return type indices of BakeF directly express the change
-- from the old store to the new store.
data BakeF cs cs' k where
  BakeF :: (Proxy (New cs) -> k) -> BakeF cs (New cs ': cs) k
  EatF  :: Elem c cs => Proxy c -> k -> BakeF cs (Remove c cs) k
  KeepF :: Elem c cs => Proxy c -> k -> BakeF cs cs k

deriving instance Functor (BakeF cs cs')
instance IxFunctor BakeF where imap = fmap

type Bake = IxFree BakeF

bake   = liftf (BakeF id)
eat  c = liftf (EatF c ())
keep c = liftf (KeepF c ())

ok :: Bake '[] _ _
ok = do
  cake1 <- bake
  cake2 <- bake
  eat cake1
  keep cake2
  eat cake2

-- not_ok :: Bake '[] _ _
-- not_ok = do
--   cake1 <- bake
--   cake2 <- bake
--   eat cake1
--   keep cake1 -- already ate it
--   eat cake2
 类似资料:
  • 由于没有快速的lambda计算器,我使用上面的策略将非类型化lambda演算的术语编译为Haskell,以便快速计算它们。我对它的性能印象深刻:该程序创建了一个从到的数字列表,并在我的计算机上在不到一秒钟的时间内将它们相加。这比我预期的要快得多--只比Haskell直接等价物慢4倍--并且足以对我的目标有用。但是,请注意,为了满足类型系统的需要,我必须将函数和术语包装在fun/num构造函数下面。

  • 我正在尝试用Mockito模拟我的房间数据库,以便我可以在我的存储库中测试复杂的算法。无论我朝哪个方向走,我都会遇到很多不同的错误。 首先,我试图模仿整个数据库对象,这创建了一个空接口异常。 为了解决这个问题,我使用了房间的静态对象生成器。(这是一个仪器化的单元测试,所以我可以访问底层的Android依赖项) 有了这个,我收到了一个被滥用的匹配器异常… org.mockito.exceptions

  • 我有一个关于嘲笑的问题。在kotlin中,当您使用Mockito时,您可以: 或 有没有办法在dart/flutter中做这样的事情?我真的只需要将mock传递给某个函数并忘记,为什么我必须创建对象并使用Mock类扩展它? 有没有其他的图书馆可以帮助解决这个问题?

  • 问题内容: 我需要知道是否存在从子模块访问父模块的方法。如果我导入子模块: 我有-是否有一些Python魔术可以从中访问模块?上课与此类似。 问题答案: 如果您已经访问了模块,则通常可以从词典中访问它。Python不会使用名称保留“父指针”,特别是因为这种关系不是一对一的。例如,使用您的示例: 如果你会注意到存在的在模块只是一个假象在里面的语句。只要您需要该模块即可。 实际上,将来的版本可能不再导

  • 假设有一个类库X的输入文件,其中包含一些接口。 为了使用这个库,我需要传递一个与。当然,我可以在源文件中创建相同的接口: 但这样一来,我就有了让它和库中的一个保持最新的负担,而且它可能非常大,并导致大量代码重复。 因此,有没有办法“提取”接口的这个特定属性的类型?类似于(这不起作用,导致“找不到名称I2”错误)。 编辑:在TS操场上玩了一会儿后,我注意到以下代码完全实现了我想要的: 但是,它需要声

  • 有没有办法只在满足条件时才在Javafx中“渲染”组件?我有兴趣做一个具有不同角色的用户界面,如果角色允许,只需添加一个组件,我也想继续使用FXML。我没有读过类似的东西。