使用makeLenses,类约束和类型同义词

时间:2015-06-01 20:22:11

标签: haskell

我对Haskell很新,想要使用makeLenses中的Control.Lens和类约束以及类型同义词来使我的函数类型更紧凑(可读?)。

我试图想出一个最小的虚拟示例来演示我想要实现的目标,这个例子没有其他用途。

在这篇文章的最后,如果您对上下文感兴趣,我已经添加了一个更接近原始问题的示例。

最小例子

例如,假设我定义了以下数据类型:

data State a = State { _a :: a
                     } deriving Show     

,我也制作镜片:

makeLenses ''State

为了对类型构造函数a使用的类型参数State强制实施类约束,我使用了一个智能构造函数:

mkState :: (Num a) =>  a -> State a
mkState n = State {_a = n}

接下来,假设我有许多类型与此类似的函数:

doStuff :: Num a => State a -> State a
doStuff s = s & a %~ (*2)

这一切都按预期工作,例如:

test = doStuff . mkState $ 5.5 -- results in State {_a = 11.0}

问题

我尝试使用以下类型的同义词:

type S = (Num n) => State n -- Requires the RankNTypes extensions

,以及:

{-# LANGUAGE RankNTypes #-}

,试图简化doStuff的类型签名:

doStuff :: S -> S

,但这会产生以下错误:

Couldn't match type `State a0' with `forall n. Num n => State n'
    Expected type: a0 -> S
    Actual type: a0 -> State a0
In the second argument of `(.)', namely `mkState'
    In the expression: doStuff . mkState
    In the expression: doStuff . mkState $ 5.5
Failed, modules loaded: none.

问题

我目前对Haskell的了解不足以理解导致上述错误的原因。我希望有人可以解释导致错误的原因和/或建议构建类型同义词的其他方法,或者为什么不能使用这种类型的同义词。

背景

我原来的问题看起来更接近这个:

data State r = State { _time :: Int
                     , _ready :: r
                     } deriving Show

makeLenses ''State

data Task = Task { ... }

在这里,我想使用以下智能构造函数强制_ready的类型作为Queue类的实例:

mkState :: (Queue q) => Int -> q Task -> State (q Task)
mkState t q  = State { _time = t
                     , _ready = q
                     }

我还有许多类型与此类似的函数:

updateState :: Queue q => State (q Task) -> Task -> State (q Task)
updateState s x = s & ready %~ (enqueue x) & time %~ (+1)

我想使用类型同义词S来重写这些函数的类型:

updateState :: S -> Task -> S

,但与第一个最小的例子一样,我不知道如何定义类型同义词S或者它是否可能。

尝试简化类型签名可能没有什么好处?

相关阅读

我已经在SO上阅读了以下相关问题:

这也可能是相关的,但考虑到我目前对Haskell的理解,我无法理解所有这些:

后续

我已经有一段时间了,因为我有机会做一些Haskell。感谢@bheklilr我现在设法引入了一个类型同义词,只是为了找到我仍然无法理解的下一个类型错误。我已就新类型错误发布了以下后续问题Type synonym causes type error

1 个答案:

答案 0 :(得分:2)

由于.运算符与RankNTypes的使用相结合,您会发现该错误。如果你从

更改它
test = doStuff . mkState $ 5.5

test = doStuff $ mkState 5.5

甚至

test = doStuff (mkState 5.5)

它会编译。为什么是这样?看看类型:

doStuff :: forall n. Num n => State n -> State n
mkState :: Num n => n -> State n

(doStuff) . (mkState) <===> (forall n. Num n => State n -> State n) . (Num n => n -> State n)

希望括号有助于在此明确说明,n forall n. Num n ... doStuff Num n => ...mkState的{​​{1}}不同,因为forall的范围仅延伸到括号的末尾。所以这些函数实际上不能编写,因为编译器将它们视为单独的类型!由于这个原因,$运算符实际上特别适用于使用ST monad,因此您可以执行runST $ do ...

您可以使用GADT轻松完成您想要的任务,但我不相信lens&#39; TemplateHaskell将适用于GADT类型。但是,在这种情况下,您可以很容易地编写自己的内容,因此这不是一件大事。

进一步解释:

doStuff . mkState $ 5.5

非常不同
doStuff $ mkState 5.5

在第一个中,doStuff表示所有 Num类型n,其类型为State n -> State n,而mkState对某些 Num类型m,其类型为m -> State m。这两种类型并不相同,因为&#34;对于所有&#34;和#34;对某些人来说&#34;量化(因此ExistentialQuantification),因为组合它们意味着对于某些Num m,您可以生成所有Num n

doStuff $ mkState 5.5中,您拥有相应的

(forall n. Num n => State n -> State n) $ (Num m => State m)

请注意$之后的类型不是函数,因为mkState 5.5已完全应用。所以这是有效的,因为适用于所有 Num n,您可以执行State n -> State n,并且您可以为其提供一些Num m => State m。这直观地起作用。同样,这里的区别在于组合与应用。你可以使用适用于所有类型的函数组合一个适用于某些类型的函数,但是你可以将值传递给适用于所有类型的函数(&#34;所有类型&#34;这里的含义{ {1}})。