> Whether a function is pure doesn't mean it can't have internal state.
This seems to be in direct contradiction to what is written on Wikipedia[1], specifically that "the function return values are identical for identical arguments".
If it has non-trivial internal state, how can it return identical values for identical inputs?
Sadly your example eludes me as I don't know any Haskell so it reads like line noise.
It does have identical return values for identical arguments. That doesn't mean the function can't have internal state. Maybe this is more clear in pseudo code:
That function example is not allowed in Haskell. It's pure functional throughout, not just at "function boundaries".
I agree that conceptually a language can be made where only function boundaries are required to be side-effect free, and internally "anything goes" as long as it doesn't pollute the outside world. This might be a good model for circuit design, especially if using something like an IO monad to store persistent state across clocks, such as flip-flops or registers.
However, this is not what Haskell does, at all. There are no mutation functions like "fill(x)". You have to create the buffer filled with x right from the beginning.
More importantly, in Haskell that buffer is defined with a recursion, so it looks something like: (x,(x,(x)))
Effectively it is an immutable linked list. Any modification involves either rebuilding the whole thing from scratch (recursively!) or making some highly restricted changes such as dropping the prefix and attaching a new one, such as (y,(x,(x))).
Haskell is not LISP, F#, or Clojure. It's pure and lazy, which is very rare in functional programming. It's an entirely different beast, and I don't see how any variant of it is a good fit for circuit design, which is inherently highly stateful and in-place-mutable.
Such a function is perfectly allowed and even encouraged, see here where I allocate a mutable array with undefined values and write and read from it in a pure function. Basically replicating the pseudo imperative code I have written before:
Monadic computations are very much in the spirit of Haskell. I don't think the Haskell community view the ST monad as a hack. It's one of many tools in the box of any Haskell programmer.
What I was thinking of was more like a linear shift feedback register. It has zero inputs and outputs random bits, thanks to its non-trivial internal state.
Or as we were saying, a register. Could for example be a function which takes a read/write flag and a value, writes the value to the internal state if write flag is set, and returns the internal value.
edit: In my softcore I have a function to read/write registers, it modifies global state rather than internal state so would not be pure either.
edit2: I guess my point is, for me "state" is something that is non-trivial, and retained between invocations. Your example is trivial and not retained (it's completely overwritten always).
You are seeing a single invocation as running your circuit for a single clock cycle after an arbitrary amount of clock cycles. That's the wrong approach. A single invocation of a circuit takes a stream of values and produces a stream of values. Every invocation start from clock cycle 0.
So yes if your circuit depends on external signals driven by registers living in a different circuit you need to pass those in as inputs. Essentially circuits are composeable just like functions.
A linear feedback shift registers always produces the same stream of values no matter how many times you run it. It's completely pure.
Still, I would be very interested to see how one would implement a shift register, lets say something like the 74HC165, so I can see how the pure functions interact with the register state.
Runnable using replit. To make it as simple as possible I didn't use Clash and used types available in prelude Haskell (Bool instead of bit, 64 bit integer as register state etc. Every single element in the list represent a value coming out of the register on the rising edge of a clock cycle. You can easily extend it if you want latches, enables etc. But that doesn't really change the core point of the code.
Much appreciated. I think I get the gist of it, even though it looks very complex compared to the Verilog counterpart. To be fair, my lack of Haskell knowledge doesn't help.
But it's really helpful to get a feel for the different approach.
I mean Haskell is definitely and acquired taste if you haven't used other languages in the same style before. Also note that writing this in actual Clash would be a one liner. Because stuff like registers and shifts are available as part of the standard library.
I've tried to get into Haskell, and while I don't have a huge problem with the overall concepts most of the time, although a bit alien at times, my brain just can't seem to handle the syntax.
I've found myself thinking differently about code and using a lot more functional-ish concepts when writing my "normal" code though, so I do like the exposure.
So your point is you can have temporary variables in pure functions. That's fine.
However how do you implement registers? Do you have to pass around the "register file"? And if so, how does that work?
Like, how would a parallel-to-serial shift register look like? Ie an asynchronous latch updates the internal shift register and an independent clock shifts out the values.
This seems to be in direct contradiction to what is written on Wikipedia[1], specifically that "the function return values are identical for identical arguments".
If it has non-trivial internal state, how can it return identical values for identical inputs?
Sadly your example eludes me as I don't know any Haskell so it reads like line noise.
[1]: https://en.wikipedia.org/wiki/Pure_function