Following the lead of the excellent answer in this post, I'm trying to get a working example of ArrowLoop that doesn't use arrow notation. I'm uncomfortable using arrow notation until I fully understand how arrows work under the hood. That being said, I've constructed a small program that based on my (limited) understanding of Arrows should work. However, it ends up terminating with the dreaded <<loop>> exception:
module Main where
import Control.Wire
import FRP.Netwire
farr :: SimpleWire (Int, Float) (String, Float)
farr = let
fn :: Int -> Float -> ((String, Float), SimpleWire (Int, Float) (String, Float))
fn i f = (("f+i: " ++ (show (fromIntegral i + f)), f + 0.1), loopFn)
loopFn :: SimpleWire (Int, Float) (String, Float)
loopFn = mkSFN $ \(i, f) -> fn i f
in
mkSFN $ \(i, _) -> fn i 0.0
main :: IO ()
main = do
let sess = clockSession_ :: Session IO (Timed NominalDiffTime ())
(ts, sess2) <- stepSession sess
let wire = loop farr
(Right s, wire2) = runIdentity $ stepWire wire ts (Right 0)
putStrLn ("s: " ++ s)
(ts2, _) <- stepSession sess2
let (Right s2, _) = runIdentity $ stepWire wire2 ts (Right 1)
putStrLn ("s2: " ++ s2)
My intuition tells me that the <<loop>> exception usually comes when you don't supply the initial value to the loop. Haven't I done that with the line containing fn i 0.0? The output disagrees:
$ ./test
s: f+i: 0.0
test.exe: <<loop>>
Does anyone know what I'm doing wrong?
The main point of confusion seemed to be the integral relationship between
ArrowLoopandmfix. For the uninitiated,fixis a function that finds the fixed point of a given function:mfixis the monadic extension of this function, whose type signature is, unsurprisingly:So what does this have to do with
ArrowLoop? Well, theArrowLoopinstance for Netwire runsmfixon the second argument of the passed wire. To put it another way, consider the type signature forloop:In Netwire, the instance of
ArrowLoopis:This means that the
loopfunction's type when used with wires is:Since
loopdoes not take an initial argument of typed, this means that there is no way to initialize any sort of conventional "looping" over the wire. The only way to get a value out of it is to keep applying the output as the input until it finds a termination condition, which is analogous to the wayfixworks. The wire that gets passed as an argument toloopnever actually steps more than once, sincestepWireis applied to the same wire over and over with different inputs. Only when the wire actually produces a fixed value, does the function step and produce another wire (which behaves the same way as the first).For completeness, here is code for my original intuition for how
loopwas supposed to work, which I have namedsemiLoop:Edit
After the wonderful answer provided by Petr, the
delaycombinator is essential to preventing theloopcombinator from diverging.delaysimply creates a single-value buffer between the laziness of using the next value in themfixportion of the loop described above. An identical definition ofsemiLoopabove is therefore: