Po obejrzeniu filmu z talk by Bret Victor, zostałem zainspirowany do napisania szybkiego hacka, który był nieco podobny do środowiska programistycznego, które zademonstrował w rozmowie.Jak mogę zmienić swoje typy danych bez powodowania ponownej kompilacji w Haskell?
Zasadniczo chodzi o to, że aplikacja działa w jednym oknie, a po zapisaniu zmiany w pliku źródłowym program się zmienia.
Działa to doskonale dla małych zmian, z tym że nie mogę zmienić typu stanu w moim kodzie bez wyłączania aplikacji i rekompilacji.
Jak mogę rozwiązać problem z wyrażaniem i czy typ danych mojego stanu może być w stanie zmienić bez powodowania ponownej kompilacji?
P.S. Oto kod. Początkowo nie chciałem publikować postów, ponieważ było naprawdę chaotycznie i szybko zhakowano je razem, ale ludzie chcieli, żeby mogli je zdobyć.
Najpierw wyświetlacz i bezczynny moduł (to był szybki hack, więc nie wymyśliłem, jak zrobić je jako prawdziwe moduły).
Idle.hs
\state -> do
counter <- readIORef state
writeIORef state ((counter + 1)`mod`3)
postRedisplay Nothing
Display.hs
\state -> let
cube w = do
renderPrimitive Quads $ do
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 w w w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) w (-w)
points :: Integer -> [(GLfloat,GLfloat,GLfloat)]
points n' = let n = fromIntegral n' in map (\k -> let t = 2*pi*k/n in (sin(t),cos(t),0.0)) [1..n]
in do
clear [ ColorBuffer ]
counter <- readIORef state
mapM_ (\(x,y,z) -> preservingMatrix $ do
color $ Color3 ((x+1.0)/2.0) ((y+1.0)/2.0) ((z+1.0)/2.0)
translate $ Vector3 x y z
cube (0.3::GLfloat)
) $ points (9 + counter)
flush
głównego modułu
module Main where
import Control.Monad
import Data.Typeable as Typeable
import System.IO
import Data.IORef
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Language.Haskell.Interpreter
main :: IO()
main = do
(_, _) <- getArgsAndInitialize
createWindow "Hello World"
action <- newIORef $ do
clear [ ColorBuffer ]
flush
let imports = ["Prelude", "Data.IORef", "Graphics.Rendering.OpenGL", "Graphics.UI.GLUT"]
let modules = ["State"]
runFile (undefined :: IORef Integer -> IO()) "Display.hs" imports $ \displayCode ->
runFile (undefined :: IORef Integer -> IO()) "Idle.hs" imports $ \idleCode -> do
state <- newIORef 12
displayCallback $= display displayCode state
idleCallback $= Just (idle displayCode idleCode state)
mainLoop
display displayCode state = do
f <- execute displayCode
f state
idle displayCode idleCode state = do
update displayCode
update idleCode
f <- execute idleCode
f state
instance Eq GhcError where
GhcError s == GhcError t = s == t
instance Eq InterpreterError where
UnknownError s == UnknownError t = s == t
WontCompile s == WontCompile t = s == t
NotAllowed s == NotAllowed t = s == t
GhcException s == GhcException t = s == t
data V a = V {
update :: IO(),
execute :: IO a
}
runFile :: Typeable a => a -> String -> [String] -> (V a -> IO()) -> IO()
runFile theType file imports f = do
currentError <- newIORef Nothing
currentAction <- newIORef Nothing
let v = V {
update = do
fileContents <- readFile file
result <- runInterpreter $ do
setImports imports
interpret fileContents theType
oldError <- readIORef currentError
case result of
Right newAction -> do
when (oldError /= Nothing) $ do
writeIORef currentError Nothing
putStrLn (file ++ " Ok!")
writeIORef currentAction (Just newAction)
Left newError -> do
when ((Just newError) /= oldError) $ do
writeIORef currentError (Just newError)
print newError
, execute = do
action <- readIORef currentAction
case action of
Nothing -> do
err <- readIORef currentError
return (error (show err))
Just act -> return act
}
update v
f v
+1 za udział w wykładzie Bret Victor. Byłoby wspaniale, gdybyś mógł zamieścić gdzieś swój kod. Myślę, że statycznie sprawdzony język nie jest zbyt odpowiedni dla takiego środowiska. Jeśli nalegasz na typy statyczne, wówczas środowisko wykonawcze (przynajmniej w środowisku uruchomieniowym debugowania) powinno wyrzucać statyczne typy i pracować z typami dynamicznymi. Nie jestem pewien, czy taki czas działania istnieje dla Haskella. –
@ user990666 Czy mógłbyś zamieścić link do rozmowy? –
@Matt Fenwick http://vimeo.com/36579366 –