1

I'm trying to implement bits and pieces of vulkan-tutorial in haskell.

For now im stuck trying to translate this code from c:

for (const char* layerName : validationLayers) {
    bool layerFound = false;

    for (const auto& layerProperties : availableLayers) {
        if (strcmp(layerName, layerProperties.layerName) == 0) {
            layerFound = true;
            break;
        }
    }

    if (!layerFound) {
        return false;
    }
}

return true; 

So far i got to this point:

-- This has type (Int -> Text) -> Bool
let partOne = all (`elem` requiredValidationLayers) . flip map [0 .. realCount-1]
-- This has type Int -> IO Text
let partTwo i = do
        let layerProperty = advancePtr layerProperties i
        myField_ <- readStringField @"layerName" layerProperty
        pure $ toS myField_ :: IO Text

I'm feeling that i have all the pieces here, but also that i might be going in a completely wrong direction.

How do i put this stuff together?

thanks

PS: ok, i just noticed that the set inclusion check is likely reversed - doesn't matter, lets for the sake of the question pretend that it's actually fine

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Pavel Beliy
  • 494
  • 1
  • 3
  • 14
  • Where exactly is `partTwo`? What is the original code C-code supposed to do? – DevNebulae Jan 19 '20 at 12:51
  • partTwo takes an integer i and returns **layerProperties[i].layerName** in c equivalent due to how vulkan-api is dealing with these constructs, layerProperties is actually an opaque Ptr to an array of structures. Hence all this boilerplate – Pavel Beliy Jan 19 '20 at 13:01
  • the original c code is a set inclusion check : if every element of validationLayers is in availableLayers – Pavel Beliy Jan 19 '20 at 13:03
  • Write a *pure* function `f` the implements the C code. Then lift it with `Control.Appicative.liftA2` to work with values in an `IO` action. That is, if `f :: [a] -> [b] -> Bool`, then `liftA2 f :: IO [a] -> IO [b] -> IO Bool` (specialized to `IO`, that is). – chepner Jan 19 '20 at 14:53
  • except that one of the Lists can only be read in IO Monad, so it is actually [Layer] -> IO [Layer] -> IO Bool. Still lifting it should produce something like IO [Layer] -> IO [Layer] -> IO Bool, right? due to joinability of monads... i will look later again at this code with a fresh mind – Pavel Beliy Jan 19 '20 at 14:55

2 Answers2

1

thanks all commenters, i think i got it now :)

this is how it looks like (inside a do block of IO () ):

supportedLayers <- for [0 .. realCount-1] $ \i -> do
    let layerProperty = advancePtr layerProperties i
    myField_ <- readStringField @"layerName" layerProperty
    pure $ toS myField_ :: IO Text
return $ requiredValidationLayers `includes` supportedLayers

where

includes :: Eq a => [a] -> [a] -> Bool
includes a b = all (`elem` b) a
Will Ness
  • 70,110
  • 9
  • 98
  • 181
Pavel Beliy
  • 494
  • 1
  • 3
  • 14
0

I'm going to start with some assumptions:

  1. You have a type data Layer = ...
  2. You have a type alias type Name = String
  3. You have some function layerName :: Layer -> Name

I'm going to walk through the definition of the pure function, not because it's terribly important, but because it uses a technique for moving from pure to impure (i.e., IO-dependent) code.


Does a given layer have a given name?

First, you have the base operation

strcmp(layerName, layerProperties.layerName) == 0

This is a simple Haskell function that takes a Name and a Layer and returns True if the layer has the given name.

hasName :: Name -> Layer -> Bool
hasName name layer = name == layerName layer

Does one of several layers have any one of several names?

But we don't have a single name or layer; we have lists of each. We can easily handle this by using the Applicative instance for lists to model nondeterminism.

import Control.Applicative

foo :: [Name] -> [Layer] -> [Bool]
foo = liftA2 hasName

I won't bother given this function a good name, because we won't need one.

Now, we can get a list of the result of comparing every name to every layer, but we don't care about the individual results: we just want one value that says if we found a match or not. For this, we'll use or :: [Bool] -> Bool.

findMatch :: [Name] -> [Layer] -> Bool
findMatch names layers = or (foo names layers)

foo is simple enough that we'll just inline its definition, so that we don't need to come up with a better name:

findMatch :: [Name] -> [Layer] -> Bool
findMatch names layers = or (liftA2 hasName names layers)

Handling IO

Now, we've been assuming that we already have our lists of names and layers as pure values. But what if we don't? What if we only have lists read in from a file or somewhere else, so that we have IO [Name] and/or IO [Layer]? The solution is to use liftA2 again, but instead of using the list instance of Applicative, we'll use the IO instance.

ionames :: IO [Name]
ionames = ...

iolayers :: IO [Layer]
iolayers = ...

result :: IO Bool
result = liftA2 findMatch ionames iolayers
chepner
  • 497,756
  • 71
  • 530
  • 681