Haskell 导出记录只读
Haskell Export Record for Read Access Only
我有一个使用记录语法的 Haskell 类型。
data Foo a = Foo { getDims :: (Int, Int), getData :: [a] }
我不想导出Foo
值构造函数,这样用户就无法构造无效的对象。但是,我想导出getDims
,以便用户可以获取数据结构的维度。如果我这样做
module Data.ModuleName(Foo(getDims)) where
然后用户可以使用getDims
来获取维度,但问题是他们也可以使用记录更新语法来更新字段。
getDims foo -- This is allowed (as intended)
foo { getDims = (999, 999) } -- But this is also allowed (not intended)
我想避免后者,因为它会使数据处于无效状态。我意识到我不能简单地使用记录。
data Foo a = Foo { getDims_ :: (Int, Int), getData :: [a] }
getDims :: Foo a -> (Int, Int)
getDims = getDims_
但这似乎是解决问题的一种相当迂回的方法。有没有办法继续使用记录语法,同时只导出记录名称以供读取访问,而不是写入访问?
隐藏构造函数然后为每个字段定义新的访问器函数是一种解决方案,但对于具有大量字段的记录来说可能会变得乏味。
这是一个新的 HasField
typeclass in GHC 8.2.1 解决方案,它避免了为每个字段定义函数。
想法是像这样定义一个辅助新类型:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PolyKinds #-} -- Important, obscure errors happen without this.
import GHC.Records (HasField(..))
-- Do NOT export the actual constructor!
newtype Moat r = Moat r
-- Export this instead.
moat :: r -> Moat r
moat = Moat
-- If r has a field, Moat r also has that field
instance HasField s r v => HasField s (Moat r) v where
getField (Moat r) = getField @s r
记录 r
中的每个字段都可以从 Moat r
访问,语法如下:
λ :set -XDataKinds
λ :set -XTypeApplications
λ getField @"getDims" $ moat (Foo (5,5) ['s'])
(5,5)
Foo
构造函数应该对客户端隐藏。但是, Foo
的字段访问器应该 而不是 被隐藏;它们必须在 Moat
的 HasField
个实例的作用域内。
面向 public 的 api 中的每个 函数应该 return 并接收 Moat Foo
而不是 Foo
s.
为了使访问器语法稍微不那么冗长,我们可以转向 OverloadedLabels
:
import GHC.OverloadedLabels
newtype Label r v = Label { field :: r -> v }
instance HasField l r v => IsLabel l (Label r v) where
fromLabel = Label (getField @l)
在 ghci 中:
λ :set -XOverloadedLabels
λ field #getDims $ moat (Foo (5,5) ['s'])
(5,5)
不是隐藏 Foo
构造函数,另一种选择是使 Foo
完全 public 并在您的库中定义 Moat
,隐藏任何 Moat
来自客户的构造函数。
我有一个使用记录语法的 Haskell 类型。
data Foo a = Foo { getDims :: (Int, Int), getData :: [a] }
我不想导出Foo
值构造函数,这样用户就无法构造无效的对象。但是,我想导出getDims
,以便用户可以获取数据结构的维度。如果我这样做
module Data.ModuleName(Foo(getDims)) where
然后用户可以使用getDims
来获取维度,但问题是他们也可以使用记录更新语法来更新字段。
getDims foo -- This is allowed (as intended)
foo { getDims = (999, 999) } -- But this is also allowed (not intended)
我想避免后者,因为它会使数据处于无效状态。我意识到我不能简单地使用记录。
data Foo a = Foo { getDims_ :: (Int, Int), getData :: [a] }
getDims :: Foo a -> (Int, Int)
getDims = getDims_
但这似乎是解决问题的一种相当迂回的方法。有没有办法继续使用记录语法,同时只导出记录名称以供读取访问,而不是写入访问?
隐藏构造函数然后为每个字段定义新的访问器函数是一种解决方案,但对于具有大量字段的记录来说可能会变得乏味。
这是一个新的 HasField
typeclass in GHC 8.2.1 解决方案,它避免了为每个字段定义函数。
想法是像这样定义一个辅助新类型:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PolyKinds #-} -- Important, obscure errors happen without this.
import GHC.Records (HasField(..))
-- Do NOT export the actual constructor!
newtype Moat r = Moat r
-- Export this instead.
moat :: r -> Moat r
moat = Moat
-- If r has a field, Moat r also has that field
instance HasField s r v => HasField s (Moat r) v where
getField (Moat r) = getField @s r
记录 r
中的每个字段都可以从 Moat r
访问,语法如下:
λ :set -XDataKinds
λ :set -XTypeApplications
λ getField @"getDims" $ moat (Foo (5,5) ['s'])
(5,5)
Foo
构造函数应该对客户端隐藏。但是, Foo
的字段访问器应该 而不是 被隐藏;它们必须在 Moat
的 HasField
个实例的作用域内。
面向 public 的 api 中的每个 函数应该 return 并接收 Moat Foo
而不是 Foo
s.
为了使访问器语法稍微不那么冗长,我们可以转向 OverloadedLabels
:
import GHC.OverloadedLabels
newtype Label r v = Label { field :: r -> v }
instance HasField l r v => IsLabel l (Label r v) where
fromLabel = Label (getField @l)
在 ghci 中:
λ :set -XOverloadedLabels
λ field #getDims $ moat (Foo (5,5) ['s'])
(5,5)
不是隐藏 Foo
构造函数,另一种选择是使 Foo
完全 public 并在您的库中定义 Moat
,隐藏任何 Moat
来自客户的构造函数。