Haskell仅用于读访问的导出记录

时间:2017-08-12 23:33:05

标签: haskell record encapsulation accessor

我有一个使用记录语法的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_

但这似乎是解决问题的一种相当迂回的方式。有没有办法继续使用记录语法,只导出记录名以进行读访问,而不是写访问?

1 个答案:

答案 0 :(得分:5)

隐藏构造函数然后为每个字段定义新的访问器函数是一种解决方案,但对于具有大量字段的记录来说,它会变得乏味。

以下是HasField中新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的字段访问者应隐藏;它们必须在HasFieldMoat实例的范围内才能启动。

面向公众的api中的每个函数都应该返回并接收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完全公开并在库中定义Moat,隐藏客户端中的任何Moat构造函数。< / p>