With the following "toy model" of Clash:
{-# LANGUAGE RankNTypes, KindSignatures, DataKinds, FlexibleContexts #-}
-- Simplified model of platform definitions
data Domain = DomSys | Dom25
data Signal (dom :: Domain)
data Clock (dom :: Domain)
class HiddenClock (dom :: Domain)
withClock :: Clock dom -> (HiddenClock dom => r) -> r
withClock _ _ = undefined
I would like to use withClock to close over the HiddenClock constraint inside a local where block. Suppose I have the following two toplevel definitions:
-- Simplified model of external standalone definitions
mainBoard :: (HiddenClock dom) => Signal dom -> Signal dom -> Signal dom -> (Signal dom, Signal dom)
mainBoard = undefined
peripherals :: (HiddenClock dom) => Signal dom -> Signal dom
peripherals = undefined
video
:: Clock domVid
-> Clock domSys
-> Signal domSys
-> Signal domSys
-> (Signal domVid, Signal domSys, Signal domSys)
video = undefined
then, I would like to write something like the following:
topEntity :: Clock Dom25 -> Clock DomSys -> Signal DomSys -> Signal Dom25
topEntity clkVid clkSys input = vga
where
(vga, vidRead, line) = video clkVid clkSys vidAddr vidWrite
(vidAddr, vidWrite) = withClock clkSys board
board = mainBoard vidRead line p
where
p = peripherals input
Unfortunately, GHC (at least as of 8.10.7) is unable to infer the correct type for board, which causes withClock clkSys board to not really close over the HiddenClock DomSys constriant:
• No instance for (HiddenClock 'DomSys)
arising from a use of ‘mainBoard’
• In the expression: mainBoard vidRead line p
In an equation for ‘board’:
board
= mainBoard vidRead line p
where
p = peripherals input
In an equation for ‘topEntity’:
topEntity clkVid clkSys input
= vga
where
(vga, vidRead, line) = video clkVid clkSys vidAddr vidWrite
(vidAddr, vidWrite) = withClock clkSys board
board
= mainBoard vidRead line p
where
p = peripherals input
|
38 | board = mainBoard vidRead line p
| ^^^^^^^^^^^^^^^^^^^^^^^^
• No instance for (HiddenClock 'DomSys)
arising from a use of ‘peripherals’
• In the expression: peripherals input
In an equation for ‘p’: p = peripherals input
In an equation for ‘board’:
board
= mainBoard vidRead line p
where
p = peripherals input
|
40 | p = peripherals input
| ^^^^^^^^^^^^^^^^^
This can be worked around by adding a type signature to board:
board :: (HiddenClock DomSys) => (Signal DomSys, Signal DomSys)
My question is: is it possible to change this code slightly, or fiddle with the exact type of withClock etc., to make this definition of topEntity typecheck without a type signature on the binding of board?
I don't think you can really infer this and I'm not entirely sure why you need to. In Clash
HiddenClockusesImplicitParamsunder the hood. Currently, yourboardhas no way of knowing where the clock is coming from.You need to either pass the clock by value
clkSysor explicitly write that the clock is needed at the type level with aHiddenClockconstraint.ImplicitParamsdon't really work like normal type class constraints. ThisHiddenClockisn't a constraint on thedom. And you can see that by the fact thatHiddenClock 'DomSysis still needed as a constraint, even though it has no free variables.Here is an example using plain Haskell (with
ImplicitParams) of your issue:And GHC tells me:
In order to make this work, you either need to have
withXin the definition ofbaz(explicitly passing x/the clock there) or be explicit about theImplicitParamsdependency. You don't need a full type signature if you don't want to, you just need theImplicitParamsconstraint (usingPartialTypeSignatures):This now compiles just fine (with a warning that can be disabled with
{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}if you really want):