2012-02-25 13 views
10

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 
+3

+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. –

+1

@ user990666 Czy mógłbyś zamieścić link do rozmowy? –

+2

@Matt Fenwick http://vimeo.com/36579366 –

Odpowiedz

2

Jestem pewien, że jest to niemożliwe w GHC. Kiedy Haskell jest skompilowany, język wyższego poziomu jest przerabiany na rdzeń, który również jest wpisywany. GHC nie rozpocznie transformacji w rdzeń dopóki program nie zostanie sprawdzony. Jest też ku temu powód: jak sprawdza typ programu, jednocześnie okazuje się. Jak zauważył jberryman, jedyną pracą wokół byłoby stworzenie elastycznego typu dla State, który pozwoliłby na polimorfizm, więc zmiana typu może nie zostać zarejestrowana jako jedna.

Powiązane problemy