I am quite new to Haskell, and I am currently doing a Clash project. I have struggled for few days to understand what these code means in Haskell:
One example is from the retro-clash book https://github.com/gergoerdi/retroclash-book-code/blob/master/src/serial/echo/src/Serial.hs
topEntity
:: "CLK" ::: Clock System
-> "RX" ::: Signal System Bit
-> "TX" ::: Signal System Bit
topEntity = withResetEnableGen board
where
board rx = tx
where
input = serialRx @8 (SNat @9600) rx
buf = fifo input txReady
(tx, txReady) = serialTx @8 (SNat @9600) buf
makeTopEntity 'topEntity
The first thing seems confusing to me is, why we can have board rx = tx?
like if there is board = some-expression it is more understandable to me, because the identification 'board' has just been mentioned in the previous context. But how is rx also appear in the left of the equal sign? Is it related to pattern matching? (if is, how can rx be matched? like in the definition of topEnity there is no rx mentioned?)
Also, I can not understand these three line of the inner 'where', the relation between different function names seems really complicated.
It might help to consider a simpler example. Assuming you've done enough Haskell programming to run into the
mapfunction, you probably understand the following program to double the elements of a list:You may also know that
doubleListcan be defined without explicitly referencing the argument listxs, and it will still behave the same as the original:If
doubleis only referenced bydoubleList, it's possible to move its definition into awhereclause:On the other hand, if we thought that the definition of
doublewas too complicated, we might rewrite it in terms of some subexpressions defined in its ownwhereclause:or even:
Combining these changes would give:
which is similar, in structure, to the Clash example:
So, in the Clash example,
topEntityis defined by applyingwithResetEnableGento a one-argument functionboard:which is, itself, defined in terms of some subexpressions given in a
whereclause.The only additional wrinkle is that the subexpressions include some mutual recursion, so the result of
board rxis defined astx, which is part of the output ofserialTxapplied tobuf, butbufis defined in terms of bothinput(which depends on the actual argumentrxpassed toboard) andtxReady, which is another part of the output ofserialTx.So,
bufis calculated based ontxReady, andtxReadyis calculated based onbuf.This is not something you typically see in conventional languages, where this kind of mutual dependence would most likely result in an infinite loop, but it's pretty common in Haskell.
For a non-Clash example, consider this function, which determines which elements of a list are even:
using the following helper function:
Note that
edepends onoandodepends one, just likebufandtxReadyin the Clash example, but the program still works fine:That's because
eandoare functions, andcheckensures that they're called in a way that doesn't result in an infinite loop (i.e., becauseeonly callsounder certain conditions and vice versa).Something similar is going on with the Clash example.