u/Unable-Yellow-7323

Some questions about lib development

Hi everyone, I've been working on Haskell bindings for libgpiod. I've already uploaded it to Hackage, but it's currently in alpha.

Recently, I received some amazing feedback regarding memory management using bracket, ResourceT, etc. Now, I'm hoping to get some feedback and recommendations on a few other design doubts I have. Thanks in advance!

1. FilePath vs ByteString

In Haskell, FilePath is just an alias for String. I've been using it for functions like:

withChip :: FilePath -> (Chip -> IO a) -> IO a

However, libgpiod is often used on embedded devices with limited RAM. I'm wondering if I should use ByteString to minimize memory consumption. Or, since these strings are typically very short (e.g., "/dev/gpiochip0", "gpiochip0"), should I just stick with standard Strings?

2. Naming Functions and Qualified Imports

In the low-level layer, I used longer, more descriptive names like LineOffset and eventBufferCapacity. But for the high-level implementation, I was hoping to rely on qualified imports to keep names shorter:

  • LineOffset -> Line.Offset (import qualified Fuyu.GPIO.Line as Line)
  • eventBufferCapacity -> Event.bufferCapacity (import qualified Fuyu.GPIO.EdgeEvent as Event)

Is it considered good practice in Haskell to design an API expecting users to rely heavily on qualified imports for namespace management?

3. Theoretically Impossible States and Defensive Programming

In libgpiod, I can wait for specific edge events in a buffer using gpiod_line_request_wait_edge_events. This function guarantees that there is at least one edge event available when it returns successfully (represented in my code as EventReady). After getting an EventReady, I create a security token that wraps a line request guaranteed to have at least 1 event.

-- | Wait for edge events to occur on requested lines until the specified timeout.
-- Throws 'WaitEdgeEventsFailed' on error.
waitEvents :: Request -> Timeout -> IO (WaitResult ReadyRequest)
waitEvents req timeout = do
  res <- unwrapOrThrow WaitEdgeEventsFailed (D.lineRequestWaitEdgeEvents req timeout)
  pure $ case res of
    D.EventReady -> EventReady (ReadyRequest req)
    D.Timeout    -> TimeoutResult

-- | Get a specific edge event from the buffer by index.
bufferEvent :: Buffer -> Word -> IO Event
bufferEvent buf idx = unwrapOrThrow ReadEdgeEventsFailed (D.eventBufferGetEvent buf idx)

-- | Process raw edge events directly in the buffer using a callback without intermediate allocations,
-- returning a non-empty list of results.
withRawEvents :: ReadyRequest -> Buffer -> (Event -> IO a) -> IO (NonEmpty a)
withRawEvents readyReq buf action = do
  count <- readEventsRaw readyReq buf
  results <- forM [0 .. count - 1] $ \idx -> do
    ev <- bufferEvent buf (fromIntegral idx)
    action ev
  case NE.nonEmpty results of
    Just ne -> pure ne
    Nothing -> ioError (userError "readEvents: expected at least one event from ReadyRequest but got none")

My question is about withRawEvents: should I remove the NonEmpty case verification? Since it's theoretically impossible to have zero events when holding a ReadyRequest token, is it better to just assume it's non-empty or should I keep the defensive check?

4. Exceptions and Ctrl+C

Finally, simple scripts or tests are often terminated with Ctrl+C. To ensure a "clean shutdown", I created this helper:

-- | High-level managed application runner.
-- Automatically handles 'Ctrl+C' ('UserInterrupt'), interrupted system calls ('EINTR' / 'WaitEdgeEventsFailed'),
-- and prints formatted 'GpioException' messages cleanly without uncaught backtraces.
withGpioApp :: IO a -> IO ()
withGpioApp action = void action `catch` handleAppException
  where
    handleAppException :: SomeException -> IO ()
    handleAppException exc
      | isUserInterrupt exc = putStrLn "\nLoop terminated successfully!"
      | Just (WaitEdgeEventsFailed (Errno 4)) <- fromException exc = putStrLn "\nLoop terminated successfully!"
      | Just (gpioErr :: GpioException) <- fromException exc = putStrLn $ "\n[GPIO Exception]: " ++ show gpioErr
      | otherwise = throwIO exc

    isUserInterrupt :: SomeException -> Bool
    isUserInterrupt e = case fromException e of
      Just UserInterrupt -> True
      _                  -> False

I'm not sure if there's a better or more idiomatic way to handle Ctrl+C when using custom exception types like these:

data GpioException
  = ChipOpenFailed FilePath Errno
  | ChipInfoFailed Errno
  | LineInfoFailed Errno
  | LineSettingsNewFailed Errno
  -- ...

Any feedback or recommendations would be greatly appreciated. I'd love to ensure this library follows Haskell best practices. Thanks!

reddit.com
u/Unable-Yellow-7323 — 2 days ago
▲ 23 r/haskell

fuyu-gpio: High-level, type-safe interface for Linux GPIO (libgpiod v2).

Hello, after a few days and having received some amazing advice here, I’m delighted to present my two libraries of bindings for libgpiod.

fuyu-gpio-direct 0.1.0.0: A lib of ‘direct’, almost 1:1, low-level and mid-level bindings to the libgpiod core API. This library was created, taking inspiration from direct-sqlite, with the aim of having two smaller libraries, and serves as a basis for the development of other libraries.

fuyu-gpio 0.0.9.0: The high-level version, featuring better modularity, safer resource management using deterministic `with*/bracket` constructs, and enhanced type safety (security tokens).

Both are now available on Hackage and GitHub. fuyu-gpio repository currently includes five examples. And the last two show how to use managed and transformers to avoid the Pyramid of Doom.

Furthermore, in the repositories for both packages, there is an Dockerfile containing a version of Debian 13 alongside Haskell, for the purpose of cross-compilation.

I’d be delighted to receive suggestions on how to improve both packages, thanks!

reddit.com
u/Unable-Yellow-7323 — 3 days ago
▲ 21 r/haskell

Idiomatic FFI architecture (libgpiod): bracket for FDs vs ForeignPtr for memory?

Hi everyone, ​I'm writing Haskell bindings for libgpiod (v2), primarily targeting SBCs and embedded systems and I want to validate my approach to resource management before committing to the final API design.

The C library exposes two different types of opaque pointers.

​1. OS/Hardware Resources (Chip, LineRequest)

These hold underlying Linux file descriptors and physical hardware locks. My plan is to strictly use raw Ptr internally and expose a bracket-based API (e.g., withChip and withLineRequest) to guarantee immediate and deterministic release, as GHC's lazy garbage collector could easily exhaust the FD limit on a small SBC if I used ForeignPtr.

  1. Pure Memory Configs (LineSettings, LineConfig)

These are just structs in RAM used to prepare data before a hardware request. To avoid the deeply nested with* blocks for every single config object.

I plan to wrap these in ForeignPtr with their respective C finalizers. I think this allows the user and me, to pass them around purely and ergonomically, letting the GC handle the cleanup since they don't hold FDs.

My questions are:

  1. ​Is this hybrid approach (strict scoping for FDs + GC for pure RAM structs) the best practice for this type of hardware FFI?

  2. ​For the withChip pattern, how do users typically architect long-running daemons around it? Do they just wrap the main application loop inside a top-level withSomething block?

Any insights or edge cases I should watch out for would be greatly appreciated. Thanks!

reddit.com
u/Unable-Yellow-7323 — 27 days ago
▲ 5 r/RISCV

Question about Muse Pi Pro / BPI-F3 restock

​Hi everyone, ​I was planning to buy a Muse Pi Pro (16GB ver) from the Chip Board House Store on AliExpress, but the price just suddenly increased by around $100.

So... ​Does anyone know if the official store (which I believe is the Banana Pi store) usually restocks this board, or if the BPI-F3 (16GB ver) will be back in stock soon?

​​If that's not the case, should I go for the Orange Pi RV2 instead? Is it a good alternative? I was thinking about that option mainly because I don't want to miss out on getting a Spacemit K1/M1 based SBC due to the current stock shortages and the excessive markup by resellers on AliExpress.

P.S. Another option I’ve been looking into is the BPI-CM6, mostly because of its documentation (and it's still available in the official AliExpress store). Has anyone here tried it out yet?

reddit.com
u/Unable-Yellow-7323 — 2 months ago