Home freenode/#haskell: Logs Calendar

Logs: freenode/#haskell

←Prev  Next→ 502,152 events total
2021-05-06 07:23:46 <olle> jacks2: depends on where you put the dependencies of functions draw and save.
2021-05-06 07:25:03 <olle> tdammers: cool
2021-05-06 07:25:54 <tdammers> anyway, OOP languages don't explicitly distinguish effectful code from pure code, but if you take "modern best practices" and run with them, you usually end up with 3 types of classes: those that represent data structures (and have no behavior beyond getters / setters / constructor / destructor), those that are just collections of utility functions (and have no state of their own, except maybe caches /
2021-05-06 07:25:56 <tdammers> memoization), and those that implement effectful interactions with the outside world
2021-05-06 07:26:12 × nineonin_ quits (~nineonine@2604:3d08:7783:f200:98ea:cd24:b2e0:9b6f) (Remote host closed the connection)
2021-05-06 07:26:17 <tdammers> well, that's basically what you'd have in Haskell too. data structures, pure logic, and effects, neatly separated
2021-05-06 07:26:44 <olle> Yeah
2021-05-06 07:26:46 <jacks2> data Shape = Shape { shapeName :: Text, shapeData :: [Point], shapeDraw :: Shape -> IO () }. then mkCircle points = Shape "Circle" points drawCircle
2021-05-06 07:27:44 waleee-cl joins (uid373333@gateway/web/irccloud.com/x-udyzskrqxqzkzbjc)
2021-05-06 07:27:48 <tdammers> jacks2: I'd ditch the shapeDraw member, just use the shapeName (or use an enum type)
2021-05-06 07:28:03 <tdammers> defunctionalize
2021-05-06 07:28:39 <jacks2> tdammers, and use what instead? with this you can have a list of shapes and then draw them all
2021-05-06 07:29:03 <jacks2> this is assuming they can't be drawn just using shapeData
2021-05-06 07:29:06 <tdammers> shapeDraw :: Shape -> IO (); shapeDraw s = case shapeName s of { ... }
2021-05-06 07:29:06 <jacks2> in a generic way
2021-05-06 07:29:27 <jacks2> tdammers, that doesn't meet his requirement.. you can't add different shapes, as a library user
2021-05-06 07:29:31 <tdammers> only reason why you'd inject the drawing function into the data structure is because you need the consumer to push their own drawing routines
2021-05-06 07:30:06 <tdammers> but if you do that, why not just `type Shape = IO ()`
2021-05-06 07:31:16 <jacks2> that's assuming that there's only one "method" each shape can have, drawShape, and that other fields, like name, aren't useful
2021-05-06 07:31:31 <olle> saveShape too
2021-05-06 07:31:46 <tdammers> sure. but you basically just invented half of OOP
2021-05-06 07:31:51 <olle> haha
2021-05-06 07:32:17 Gurkenglas_ joins (~Gurkengla@unaffiliated/gurkenglas)
2021-05-06 07:32:33 <jacks2> you can get pretty far with a records of functions. and it meets his requirement, your suggestion doesn't, you are hardcoding a limited number of shapes in your shapeDraw above
2021-05-06 07:32:56 <tdammers> I'm questioning the requirement
2021-05-06 07:33:22 <olle> Sure, sure :)
2021-05-06 07:33:28 <tdammers> IME, this kind of flexibility (allowing truly arbitrary implementations of the draw method) is hardly ever needed
2021-05-06 07:33:37 <tdammers> when it is, sure, break out the OOP
2021-05-06 07:34:36 <olle> It's also related to encapsulation - if the data for each shape should or should not be public to the outside.
2021-05-06 07:35:04 <tdammers> encapsulation is something you achieve by limiting module exports
2021-05-06 07:35:05 coot joins (~coot@37.30.58.122.nat.umts.dynamic.t-mobile.pl)
2021-05-06 07:35:30 <olle> tdammers: so you get package private stuff?
2021-05-06 07:35:55 <tdammers> you can, if you don't expose private modules, but that's recommended against
2021-05-06 07:36:03 <tdammers> you do get module-private stuff though
2021-05-06 07:36:26 <olle> But one module = one file...? Or that's only OCaml?
2021-05-06 07:37:08 <tdammers> according to the language standard, you can have multiple modules in one Haskell source file, but in practice, GHC doesn't support that and requires one module per file
2021-05-06 07:37:15 <tdammers> it also requires the filename to match the module name
2021-05-06 07:37:27 <tdammers> (except for Main, that is)
2021-05-06 07:38:31 <olle> Imagine we have one file for Point, Rectangle, and another file for shapeSave. Then we want only shapeSave to have access to internal representation of the shapes, but nothing else. Possible?
2021-05-06 07:38:40 <olle> Might not matter so much when immutability is the default, tho.
2021-05-06 07:39:25 <tdammers> forget "internal representation"
2021-05-06 07:40:00 <tdammers> that's not important; what is "internal" is not a property of the data, the data just is, it's a property of the operations acting on the data
2021-05-06 07:40:14 <tdammers> "does this function need to know about the points in this shape?"
2021-05-06 07:40:28 <tdammers> if it doesn't, then *for the purpose of that function*, the points are "internal"
2021-05-06 07:40:45 <tdammers> but you don't need to hide them from the module that defines them, if other functions may need to look at them
2021-05-06 07:40:59 <tdammers> you can also "hide" things behind a typeclass, for example
2021-05-06 07:41:10 <olle> hmmm
2021-05-06 07:41:16 <tdammers> drawShape :: Drawable s => s -> IO ()
2021-05-06 07:41:31 <olle> Same with Savable?
2021-05-06 07:41:32 <tdammers> now all drawShape knows about the shape in question is whatever methods the Drawable typeclass exposes
2021-05-06 07:41:32 × cole-h quits (~cole-h@c-73-48-197-220.hsd1.ca.comcast.net) (Ping timeout: 268 seconds)
2021-05-06 07:41:35 <tdammers> sure
2021-05-06 07:42:25 × hypercube quits (~hypercube@2603-6011-f901-9e5b-0000-0000-0000-08cf.res6.spectrum.com) (Ping timeout: 250 seconds)
2021-05-06 07:42:33 <tdammers> and now we get back to "defunctionalization": instead of making the drawing and saving code a member of the Shape type directly, you defunctionalize it. design a little EDSL that captures the primitives you need to express drawing an arbitrary shape, or saving an arbitrary shape
2021-05-06 07:43:01 <dibblego> https://en.wikipedia.org/wiki/Expression_problem
2021-05-06 07:43:02 <olle> EDSL? Domain-specific?
2021-05-06 07:43:09 <jacks2> that works.. but if you wanted to have a list of different shapes, then you also have to resort to use existential types. and you can't have more than one drawing method per shape. records of functions make the code simpler and more flexible
2021-05-06 07:43:17 <jacks2> to using existential types*
2021-05-06 07:44:10 <tdammers> e.g., data DrawPrim = MoveTo Point | LineTo Point | BezierTo Point Point Point | FillPath. Now you can say data Shape = Shape { ..., shapeDrawPrims :: [DrawPrim] }
2021-05-06 07:44:28 <tdammers> and separately: drawPrims :: [DrawPrim] -> IO ()
2021-05-06 07:45:17 <tdammers> jacks2: I like to think of those records-of-functions as the Haskell equivalent of an OOP interface
2021-05-06 07:45:30 <olle> jacks2: right, that's the next use-case: forall shapes as shape, do draw shape
2021-05-06 07:46:02 <tdammers> also, relevant: https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/
2021-05-06 07:46:23 × pavonia quits (~user@unaffiliated/siracusa) (Quit: Bye!)
2021-05-06 07:46:47 × vicfred quits (vicfred@gateway/vpn/mullvad/vicfred) (Quit: Leaving)
2021-05-06 07:47:48 <jacks2> tdammers, yes.. I see him replacing typeclass with records of functions :)
2021-05-06 07:48:25 <tdammers> the gist of it: record-of-functions works and gives you maximum flexibility, but you lose a fair degree of type-level expressivity. however, existentials don't buy you that back, because you're essentially still doing the same thing, only with more boilerplate.
2021-05-06 07:48:50 <tdammers> and because you lose that bit of type safety, record-of-functions is generally my last resort
2021-05-06 07:49:13 <tdammers> it is sometimes needed, but more often than not, it's actually more prudent to challenge the flexibility requirements
2021-05-06 07:49:18 <olle> Which type-safety do you lose with records of functions?
2021-05-06 07:49:30 <jacks2> you have just one type, Shape
2021-05-06 07:49:56 <olle> jacks2: But shape can have an ADT do say which type of shape it is?
2021-05-06 07:50:02 <olle> shapeType = Point | Rectangle
2021-05-06 07:50:27 <tdammers> if you do that, you lose the flexibility of adding arbitrary shapes after the fact
2021-05-06 07:50:29 <jacks2> you can stick drawRectangle into a circle shape
2021-05-06 07:50:48 <olle> Right
2021-05-06 07:51:06 <olle> Wonder how the Rust people would approach this...
2021-05-06 07:51:29 <tdammers> the problem is the same regardless of programming language, really
2021-05-06 07:51:45 <olle> Yes, but each language has its own "idiomatic" solution :)
2021-05-06 07:51:53 <tdammers> because the economics are different
2021-05-06 07:52:39 <davve> the best languages are the ones who force a specifc code style imo
2021-05-06 07:52:42 davve flees
2021-05-06 07:53:58 <olle> davve: design patterns and architectural habits change more often than language features.
2021-05-06 07:54:01 <tdammers> Elm does that, and it's absolutely terrible for it
2021-05-06 07:55:06 <tdammers> PHP also does it, but not on purpose, and it's also terrible for it
2021-05-06 07:55:12 <davve> my point is that its good to leave as little room for discussing stuff like indentation
2021-05-06 07:55:15 <DigitalKiwi> ghc error if it doesn't pass hindent
2021-05-06 07:55:38 <DigitalKiwi> -Werror -Wall -Whindent
2021-05-06 07:55:56 <tdammers> if people want to shave yaks, they will. not the language's job to play police.
2021-05-06 07:55:57 <DigitalKiwi> -Werror -Wall -Whindent -Whlint
2021-05-06 07:56:44 <DigitalKiwi> https://github.com/cachix/pre-commit-hooks.nix
2021-05-06 07:57:00 <DigitalKiwi> domenkozar[m] the yak shaver
2021-05-06 07:57:16 m0rphism joins (~m0rphism@HSI-KBW-085-216-104-059.hsi.kabelbw.de)
2021-05-06 07:57:25 merijn joins (~merijn@83-160-49-249.ip.xs4all.nl)
2021-05-06 07:58:49 hypercube joins (~hypercube@2603-6011-f901-9e5b-0000-0000-0000-08cf.res6.spectrum.com)
2021-05-06 08:00:35 <xsperry> olle, when it comes to extensibility, functional and OOP modeling work in the opposite direction. in functional languages it is easier to add new functions and harder to add new "types/classes/cases", and the opposite is true for OOP languages. google for "expression problem"
2021-05-06 08:01:32 <DigitalKiwi> or click the link dibblego sent lol
2021-05-06 08:01:50 <davve> imo functional makes the most sense unless you are modelling stuff from real life, then encapsulating data, mutations and stuff are more natural
2021-05-06 08:02:11 <jacks2> types ala carte supposedly address the expression problem? I never explored them
2021-05-06 08:02:26 <tdammers> we are always modelling stuff from real life. or never, depending how you look at it.

All times are in UTC.