Compiling Haskell School of Expression's source code

Hello! First post here. :slight_smile:

I’m trying to kickstart my journey on Haskell and since I’m a game developer, I’ve been looking for books that lean towards interactive applications. I stumbled upon Paul Hudak’s Haskell School of Expression and it sounded like the perfect book, but I’ve been trying to set up an environment that would allow me to write and compile the book’s code to no avail.

If I got it right, the book’s source code no longer works with current versions of GHC. I tried a few things, like getting an older version of GHC and installing the dependencies through Cabal, but I always get an error when it tries to compile OpenGL (Stack gives me a similar error). Not knowing the language’s enviroment puts a handicap on what I can currently do, so I was wondering if someone has hints on what I could do to compile and run the book’s source code?

Thanks in advance!

1 Like

Maybe these instructions by Ely De La Cruz will help: https://github.com/elycruz/haskell-by-multimedia

1 Like

Hello, can you post the error? Maybe it is something simple to fix (hopefully not needing a GHC downgrade).

1 Like

@JDGleeson Thanks for the link! I’ll check the instructions there and will post the results here. :slight_smile:

@f-a Whenever I try stack install GLFW or cabal install GLFW I get an error similar to this:

GLFW      > [1 of 2] Compiling Main             ( C:\\Users\Edgard\AppData\Local\Temp\stack-bd8d2a351602a734\GLFW-0.5.2.5\Setup.hs, C:\\Users\Edgard\AppData\Local\Temp\stack-bd8d2a351602a734\GLFW-0.5.2.5\.stack-work\dist\29cc6475\setup\Main.o )
GLFW      >
GLFW      > C:\\Users\Edgard\AppData\Local\Temp\stack-bd8d2a351602a734\GLFW-0.5.2.5\Setup.hs:261:5: error:
GLFW      >     `fail' is not a (visible) method of class `Monad'
GLFW      >     |
GLFW      > 261 |     fail str = StateT $ \_ -> fail str
GLFW      >     |     ^^^^

Ok, so I started following the instructions by adding these dependencies to my test project:

- GLFW >= 0.5.2.5
- OpenGL
- old-time
- stm

Then I got this message:

Error: While constructing the build plan, the following exceptions were encountered:

In the dependencies for test-0.1.0.0:
    GLFW must match >=0.5.2.5, but the stack configuration has no specified version  (latest matching version
         is 0.5.2.5)
needed since test is a build target.

Some different approaches to resolving this:

  * Recommended action: try adding the following to your extra-deps in C:\Haskell\test\stack.yaml:

- GLFW-0.5.2.5@sha256:fa54e997076a6b415c43100830e9b6ab6827c8766aef80e66d598de9b2400b93,3312

Plan construction failed.

But the GLFW version was added, right?

I tried the recommended solution by adding this:

extra-deps:
- GLFW-0.5.2.5@sha256:fa54e997076a6b415c43100830e9b6ab6827c8766aef80e66d598de9b2400b93,3312

But then I got this message:

C:\Haskell\test\package.yaml: Ignoring unrecognized field $.extra-deps

This is the full package.yaml file after adding the recommended change:

name:                test
version:             0.1.0.0
github:              "githubuser/test"
license:             BSD3
author:              "Author name here"
maintainer:          "example@example.com"
copyright:           "2020 Author name here"

extra-source-files:
- README.md
- ChangeLog.md

# Metadata used when publishing your package
# synopsis:            Short description of your package
# category:            Web

# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description:         Please see the README on GitHub at <https://github.com/githubuser/test#readme>

dependencies:
- base >= 4.7 && < 5
- OpenGL
- old-time
- stm

extra-deps:
- GLFW-0.5.2.5@sha256:fa54e997076a6b415c43100830e9b6ab6827c8766aef80e66d598de9b2400b93,3312

library:
  source-dirs: src

executables:
  test-exe:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - test

tests:
  test-test:
    main:                Spec.hs
    source-dirs:         test
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - test

I guess I’m setting up the project incorrectly, but I have no idea what I’m doing wrong.

Silly me, I added extra-deps to the wrong file… :sweat_smile:

Now that I fixed it, I got exactly the same error when I was trying to install GLFW through stack or cabal:

GLFW      > configure
GLFW      > [1 of 2] Compiling Main             ( C:\\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\Setup.hs, C:\\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\.stack-work\dist\29cc6475\setup\Main.o )
GLFW      >
GLFW      > C:\\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\Setup.hs:261:5: error:
GLFW      >     `fail' is not a (visible) method of class `Monad'
GLFW      >     |
GLFW      > 261 |     fail str = StateT $ \_ -> fail str
GLFW      >     |     ^^^^

--  While building package GLFW-0.5.2.5 using:
      C:\Users\Edgard\AppData\Local\Programs\stack\x86_64-windows\ghc-8.8.4\bin\ghc-8.8.4.EXE --make -odir C:\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\.stack-work\dist\29cc6475\setup -hidir C:\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\.stack-work\dist\29cc6475\setup -i -i. -package=Cabal-3.0.1.0 -clear-package-db -global-package-db -package-db=C:\sr\snapshots\48ff3d61\pkgdb C:\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\Setup.hs C:\sr\setup-exe-src\setup-shim-Z6RU0evB.hs -main-is StackSetupShim.mainOverride -o C:\Users\Edgard\AppData\Local\Temp\stack-e7498e82bd6b0457\GLFW-0.5.2.5\.stack-work\dist\29cc6475\setup\setup -threaded
    Process exited with code: ExitFailure 1
Progress 3/4

You could try the HGL library that contains a Graphics.SOE module that should be compatible with the book. It compiles fine on GHC 8.8. That doesn’t require OpenGL.

1 Like

Hi @jaror, thank you for the suggestion. I tried installing the latest version of HGL, but I’m getting these errors:

HGL > Graphics\HGL\Win32\WND.hs:243:37: error:
HGL >     * Couldn't match type `Foreign.C.Types.CIntPtr'
HGL >                      with `GHC.Int.Int32'
HGL >       Expected type: LONG
HGL >         Actual type: LPARAM
HGL >     * In the expression: x
HGL >       In the first argument of `toPoint', namely `(x, y)'
HGL >       In the `pt' field of a record
HGL >     |
HGL > 243 |         send (Button {pt = toPoint (x,y), isLeft=isLeft, isDown=isDown})
HGL >     |                                     ^
HGL >
HGL > Graphics\HGL\Win32\WND.hs:243:39: error:
HGL >     * Couldn't match type `Foreign.C.Types.CIntPtr'
HGL >                      with `GHC.Int.Int32'
HGL >       Expected type: LONG
HGL >         Actual type: LPARAM
HGL >     * In the expression: y
HGL >       In the first argument of `toPoint', namely `(x, y)'
HGL >       In the `pt' field of a record
HGL >     |
HGL > 243 |         send (Button {pt = toPoint (x,y), isLeft=isLeft, isDown=isDown})
HGL >     |                                       ^
HGL >
HGL > Graphics\HGL\Win32\WND.hs:248:36: error:
HGL >     * Couldn't match type `Word' with `GHC.Word.Word32'
HGL >       Expected type: VKey
HGL >         Actual type: WPARAM
HGL >     * In the first argument of `MkKey', namely `wParam'
HGL >       In the `keysym' field of a record
HGL >       In the first argument of `send', namely
HGL >         `(Key {keysym = MkKey wParam, isDown = isDown})'
HGL >     |
HGL > 248 |         send (Key { keysym = MkKey wParam, isDown = isDown })
HGL >     |                                    ^^^^^^
HGL >
HGL > Graphics\HGL\Win32\WND.hs:260:41: error:
HGL >     * Couldn't match type `Foreign.C.Types.CIntPtr'
HGL >                      with `GHC.Int.Int32'
HGL >       Expected type: LONG
HGL >         Actual type: LPARAM
HGL >     * In the expression: x
HGL >       In the first argument of `toPoint', namely `(x, y)'
HGL >       In the `pt' field of a record
HGL >     |
HGL > 260 |         send (MouseMove { pt = toPoint (x,y) })
HGL >     |                                         ^
HGL >
HGL > Graphics\HGL\Win32\WND.hs:260:43: error:
HGL >     * Couldn't match type `Foreign.C.Types.CIntPtr'
HGL >                      with `GHC.Int.Int32'
HGL >       Expected type: LONG
HGL >         Actual type: LPARAM
HGL >     * In the expression: y
HGL >       In the first argument of `toPoint', namely `(x, y)'
HGL >       In the `pt' field of a record
HGL >     |
HGL > 260 |         send (MouseMove { pt = toPoint (x,y) })
HGL >     |                                           ^
HGL >

If I move back to version 3.2.2, I get this error:

HGL > Graphics\HGL\Internals\Events.hs:23:68: error:
HGL >     Module `Control.Concurrent.Chan' does not export `isEmptyChan'
HGL >    |
HGL > 23 | import Control.Concurrent.Chan(Chan, newChan, readChan, writeChan, isEmptyChan)
HGL >    |                                                                    ^^^^^^^^^^^
HGL >

Oh, that’s unfortunate. I guess the Linux parts are better maintained than the Windows part.

I have tried to update the source code to work with modern Haskell:

Can you try that?

I’ve included a cabal freeze file, so it should be reproducible. And it uses OpenGL and GLFW-b, so it should also work on Windows. But I haven’t tested that.

2 Likes

Hi @jaror, I’ve changed the project to use OpenGL and GLFW-b and it compiled, thanks! :slight_smile:

I got your version of SOE – so far, so good. I guess I can start getting my hands dirty on a bit of Haskell, now. :grin:

Thank you so far, I’ll shout again at the forum if anything else happens. (And I’m sure it will…)

Cheers!

2 Likes

Hi @jaror, I was following the code in chapter 3 using your version of SOE – the window is created correctly, but the window’s context is drawn black. No matter what I try to draw in it, the context stays completely black.

As a test, I was looking for a function that would redraw the buffer with a different color, but couldn’t find it (or maybe there is a way of doing it, and my noobie-ness in Haskell prevents me from seeing it :grin:).

I also tried disabling double buffering, but nothing changed.

Any ideas on what I could do to properly test it?

Thanks!

I found and fixed the bug: https://github.com/noughtmare/haskell-school-of-expression/commit/b8d91d04c8d8c41b98866ebbd4099cde6218e183

The problem was that GLFW 3 supports multiple windows, so I had to set the newly created window as the “current” window to use for OpenGL.

1 Like

Hi @jaror, thanks! I really appreciate your help. :slight_smile:

I got the fixed version, but it still didn’t work. This is the code that I’m using, following the book’s example:

main :: IO ()
main = runGraphics
  (do
    w <- openWindow "My First Graphics Program" (800, 800)
    drawInWindow w pic1
    spaceClose w
  )

pic1 = withColor Red (ellipse (150, 150) (300, 200))

spaceClose :: Window -> IO ()
spaceClose w = do
  k <- getKey w
  if k == ' ' then closeWindow w else spaceClose w

One thing that I don’t understand in this code is how the buffer is being swapped. I tried changing drawInWindow to drawInWindowNow, but nothing happened. I also tried clearWindow and other small tweaks to see if I could provoke a refresh. Do you have a small example that you know that works so I can copy it and see if it works on my side as well?

I’ve just tried your example and it works on my machine. Are you sure that you’re using the fixed version (maybe try inserting a putStrLn "test" to test it)?

What commands do you use to run it?

I have been using cabal repl in the directory that contains the haskell-school-of-expression.cabal file, then you can load in your own file with :load Myfile.hs and then you can call the main function by just typing in main or you can run one of the included programs by typing Picture.main without needing to load your own file. Picture.main should open a window with a few different shapes that you can click to bring them to the foreground. There are also Animation.main1 up to Animation.main8 which should show various animations.

1 Like

Just checked the code here, I can see the line that you added.

I’m using stack since I couldn’t create a working environment using cabal. Basically, I have a package.yaml file at the root of my project and I use stack build and/or stack run to build/run the project.

I tried using cabal repl in your project folder and I got these errors:

Resolving dependencies...
cabal.exe: Could not resolve dependencies:
[__0] trying: SOE-0.1.0.0 (user goal)
[__1] next goal: base (dependency of SOE)
[__1] rejecting: base-4.14.0.0/installed-4.14.0.0, base-4.14.0.0 (constraint
from project config TODO requires ==4.13.0.0)
[__1] rejecting: base-4.13.0.0 (constraint from non-upgradeable package
requires installed instance)
[__1] rejecting: base-4.12.0.0, base-4.11.1.0, base-4.11.0.0, base-4.10.1.0,
base-4.10.0.0, base-4.9.1.0, base-4.9.0.0, base-4.8.2.0, base-4.8.1.0,
base-4.8.0.0, base-4.7.0.2, base-4.7.0.1, base-4.7.0.0, base-4.6.0.1,
base-4.6.0.0, base-4.5.1.0, base-4.5.0.0, base-4.4.1.0, base-4.4.0.0,
base-4.3.1.0, base-4.3.0.0, base-4.2.0.2, base-4.2.0.1, base-4.2.0.0,
base-4.1.0.0, base-4.0.0.0, base-3.0.3.2, base-3.0.3.1 (constraint from
project config TODO requires ==4.13.0.0)
[__1] fail (backjumping, conflict set: SOE, base)
After searching the rest of the dependency tree exhaustively, these were the
goals I've had most trouble fulfilling: base, SOE

Ah, you’re probably using GHC version 8.10 and I’m still using 8.8. I have loosened the bounds and removed the cabal.project.freeze file: https://github.com/noughtmare/haskell-school-of-expression/commit/83f691acf69565b725835e66800fa59acf0699cd

That should make it possible for you to run cabal repl.

EDIT: I have now also added a stack.yaml file so you should be able to run stack repl directly: https://github.com/noughtmare/haskell-school-of-expression/commit/1da248910eca92e32a561dee92b34e884e2ba4d3

1 Like

Thanks again! I managed to run the repl, but this is what I got from Picture.main:

So it seems that something odd is really going on…

Can you try running this file with stack runghc Main.hs:

{-# LANGUAGE LambdaCase #-}
module Main where

import           System.Exit
import qualified Graphics.Rendering.OpenGL     as GL
import           Graphics.Rendering.OpenGL      ( ($=) )
import qualified Graphics.UI.GLFW              as GLFW

main :: IO ()
main = do
  GLFW.init >>= \case
    False -> exitWith (ExitFailure (-1))
    True  -> do
      GLFW.createWindow 640 480 "Hello World" Nothing Nothing >>= \case
        Nothing     -> GLFW.terminate *> exitWith (ExitFailure (-1))
        Just window -> do
          GLFW.makeContextCurrent (Just window)

          GL.clearColor $= GL.Color4 1 0 0 1

          let loop = GLFW.windowShouldClose window >>= \case
                True  -> return ()
                False -> do
                  GL.clear [GL.ColorBuffer]
                  GLFW.swapBuffers window
                  GLFW.pollEvents
                  loop

          loop

          GLFW.terminate

That should open a red window. If that doesn’t work then there is something wrong with OpenGL or GLFW-b.

1 Like

Yes! :slight_smile:

Maybe it was the loop that made a difference in this case?

@jaror help is terrific, but it is evident the book has bitrot (unfortunate but unexpected — GHC does not care that much about retro compatibility and you can say similar words for the ecosystem and Windows).

Maybe there are similar publications which are readily usable?