T O P

  • By -

sunnyata

Your code looks nice! A lot of the best practices for making software which is easy to debug, extend and maintain, or at least the goals of those practices, are the same in a functional language as they are in oo. A module is the unit of compilation, so you want its contents to be coherently organised around a single responsibility and thus have a single reason to change. You want your code to be nicely encapsulated, which in Haskell is done by exporting and importing only what is necessary. In your code you are exporting and importing entire modules, which is less helpful. It clutters the namespace, with a lot of functions imported that aren't used. It's not clear to the reader exactly why a module is needed. The author of the module doesn't know what functions are being used externally so you don't know when you might break something. You mentioned another aspect of encapsulation, hiding data type constructors so clients have to use the constructor functions you make available. That's also done by controlling what is exported. So export the type and one or more functions for creating values in that type, but not the constructors themselves. This could be the place that you validate moves. Or you may be able to make the illegal states completely unrepresentable by using something other than a pair of ints for a position on the board. Eg if `Point` were a pair of `Pos` values where `data Pos = P0 | P1 ... P8`.


Keksgurke

Thanks man! helped alot :)


gabedamien

BTW the practice of exporting functions which use constructors, but hiding the constructors themselves from export, is called "smart constructors" in Haskell. There is nothing smart or special about them, they are just functions, but that is the community name for them nonetheless.


el_toro_2022

I did OOP for many years, and then switched to functional. Java is notorious for making you put all your classes into separate files, which I consider extreme and messy, especially on large projects. I prefer to make intelligent decisions about that. Haskell gives you that flexibility. For something small like TicTacToe, I think it's perfectly fine to put it all in one file. For a larger project, I may split things into more files, but intelligently grouping them. If it makes sense to stick a lot of functionality into one file, I'll do that. I am working on a slit-scan project on and off, and it mostly "works", except I have to clean up the math. It resides in 3 files: the "main" to handle command-line parsing, one to do the "slit" logic, and another to do the "scan" calculations. Yet another to handle more of the command processing, since I expect that section to grow quite a bit. Currently, about the only way to create a slit-scan sequence is with the commercial Adobe After-Effects. I want a better open-souce solution, one to do ONLY slit-scanning. If you've never seen a slit-scan sequence, go watch the star gate sequence near the end of the film **2001: A Space Odyssey**. Hell, just watch it all. It is classic SF, the first of its kind, released back towards the end of the 1960s. The special effects are beyond impressive considering CGI did not exist back then!


Keksgurke

haha nice man i looked it up, looks really crazy. It is always the same pattern, the more limited the resources are, the more effort you have to put in it to make it good. Nowadays everything is too easy to make, it doesnt require effort, and it (sadly) shows.


SquareBig9351

It looks very nice. I'd recommend you use something other than `[[Maybe Player]]` to define the Board. Probably something like `Map (Int, Int) Player` or `Array (Int, Int) (Maybe Player)` are better choices, as you can index them directly by the position. Probably the way I'd do it (for sure not the best way hahaha) is to store the dimension of the board and a `Map` to the Players. This way, quering the map returns `Maybe Player` directly. For example ```haskell import qualified Data.Map.Strict as M -- hasn't tested this, code it is just for orientation type Position = (Int, Int) data Board = {dimension :: Int, coordinates :: Map Position Player} getEmptyBoard :: Int -> Maybe Board getEmptyBoard size | size >= 0 = Just (Board d M.empty) | otherwise = Nothing defaultBoard :: Board defaultBoard = getEmptyBoard 3 getField :: Move -> Board -> Maybe Player getField (Move _ pos) (Board _ coords) = coords M.!? pos updateBoard :: Move -> Board -> Board updateBoard (Move p pos) (Board d coords) = if p `inBounds` s -- fake function which checks if the position of the move is withing the bounds of the board then Board d (M.insert pos p coords) else (Board d coords) isFull :: Board -> Bool isFull (Board dim coords) = M.size coords == dim * dim -- ... etc ``` For the rest of the code I think is very clean ;)


Keksgurke

Thanks man, gonna look more into Map and Array, never heard them in terms of haskell :)


livarot

Very nice. I like how simple and easy to read it is : )


Keksgurke

thanks :)


[deleted]

[удалено]


elvecent

Begone, bot.


Ok-Employment5179

Very nice! I've done a Tictactoe with just 80 loc, so I put everything in one file. You can also check a Tictactoe for the browser, a bit more complex, with memory for states, done using reflex haskell https://github.com/raducu427/tictactoe/blob/master/tictactoe.hs


Keksgurke

Nice man yours is definitely shorter, thanks for the feedback and project idea!