我对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。
答案 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}})。