tag:blogger.com,1999:blog-1777990983847811806.post6093749414184193382..comments2024-03-16T16:29:29.582-07:00Comments on Haskell for all: First-class modules without defaultsGabriella Gonzalezhttp://www.blogger.com/profile/01917800488530923694noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-1777990983847811806.post-86719346521969321642016-08-21T11:47:14.648-07:002016-08-21T11:47:14.648-07:00You can add another type parameter for the individ...You can add another type parameter for the individual element type:<br /><br />data StringModule s c = String<br />__{ map :: (c -> c) -> s -> s<br />__, concatMap :: (c -> s) -> s -> s<br />__, filter :: (c -> Bool) -> s -> s<br />__, length :: s -> Int<br />__...<br /><br />.. then you'd have a module of type `StringModule ByteString Word8` for bytestrings and a module of type `StringModule Text Char` for text.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-69367765490338808302016-08-15T03:13:21.051-07:002016-08-15T03:13:21.051-07:00String and Text work with Char, not Word8. ByteStr...String and Text work with Char, not Word8. ByteString works with Word8, not Char. How does this design address that?Anonymoushttps://www.blogger.com/profile/12115421284110644077noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-80016211762731902472014-12-08T00:56:58.562-08:002014-12-08T00:56:58.562-08:00Note that you can do what you ask with GADTs or Ex...Note that you can do what you ask with GADTs or ExistentialQuantification, although there do appear to be limitations (e.g. no defining of classes within a value etc.):<br /><br /> {-# LANGUAGE ExistentialQuantification, Rank2Types, RecordWildCards #-}<br /> import qualified Data.Set as Set<br /><br /> data SetModule = forall s. SetModule {<br /> empty :: forall a. s a<br /> , insert :: forall a. Ord a => a -> s a -> s a<br /> , null :: forall a. s a -> Bool<br /> }<br /><br /> dataSetM :: SetModule<br /> dataSetM = SetModule {<br /> empty = Set.empty<br /> , insert = Set.insert<br /> , null = Set.null<br /> }<br /><br /><br /> useSets :: SetModule -> Bool<br /> useSets sm@SetModule{..} = null (insert 3 empty)<br />Unknownhttps://www.blogger.com/profile/10482246694886071621noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-16649128933073895792012-08-20T09:27:33.997-07:002012-08-20T09:27:33.997-07:00No, I haven't. I will read it. Thanks!No, I haven't. I will read it. Thanks!Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-68873529690542950182012-08-20T09:15:43.948-07:002012-08-20T09:15:43.948-07:00By the way, in case you haven't seen it, this ...By the way, in case you haven't seen it, this paper is highly relevant:<br /><br />Mark P. Jones, "Using Parameterized Signatures to Express Modular Structure", POPL 1996. Scott Kilpatrickhttps://www.blogger.com/profile/18142789491662676739noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-1040198841641789112012-08-20T08:29:57.026-07:002012-08-20T08:29:57.026-07:00Yeah, several people on reddit pointed this out to...Yeah, several people on reddit pointed this out to me. I agree with your entire comment.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-7089876297617309022012-08-20T07:09:24.964-07:002012-08-20T07:09:24.964-07:00This can't really be considered a replacement ...This can't really be considered a replacement for Haskell modules when it only encapsulates value components as opposed to the full shebang of values, data types, classes, and instances. How would I define, e.g., a Set type and hide the constructors so that clients are forced to use my provided functions to interact with it?<br /><br />What this shows is that Haskell modules could really use a notion of interface -- analogous to your moduley data types -- to allow matching, parameterization, and perhaps even separate compilation.Scott Kilpatrickhttps://www.blogger.com/profile/18142789491662676739noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-12165100815059925122012-07-20T07:51:27.225-07:002012-07-20T07:51:27.225-07:00Oops! :)
Although, I think the Haskell old guard ...Oops! :)<br /><br />Although, I think the Haskell old guard takes for granted how much the community newcomers (like me) understand. If there's no oral tradition then we are forced to reinvent everything ourselves.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-83648186050467984412012-07-20T00:01:27.118-07:002012-07-20T00:01:27.118-07:00What you call "Chris's big insight" ...What you call "Chris's big insight" is well known Haskell folklore.Lennarthttps://www.blogger.com/profile/09187236945238786712noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-15076932593036619482012-07-19T13:22:22.188-07:002012-07-19T13:22:22.188-07:00The import was just a cute thing to refer to your ...The import was just a cute thing to refer to your pseudocode. You could just as well have put those in the same file as the functions you wrote, assuming you rename your hidden primitives and only export the dictionary.<br /><br />So, there is only one thing you cannot conveniently do, which is selectively hide certain members. You can still do something like:<br /><br />Stack push pop emptyS _ = someStackInstance<br /><br />... but this would grow cumbersome if it was a big module and you only wanted to hide one member.<br /><br />However, selectively importing just a few elements is pretty easy:<br /><br />(push, pop) = ((,) <$> push <*> pop) someStackInstance<br /><br />Ordinary modules don't provide ad-hoc polymorphism, but modules as dictionaries do (as I illustrated in the post). This is an advantage of first-class modules over ordinary modules. This lets you just import just one module for bytestrings (i.e. Data.ByteString could now export both strict and lazy bytestring implementations), and then you can very easily switch between both implementations using all the tricks I described above. Ordinary modules don't allow you to locally alias modules or unpack names within a local scope, but first-class modules do.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-4351712214377562862012-07-19T13:03:07.522-07:002012-07-19T13:03:07.522-07:00Why do you use "module" and "import...Why do you use "module" and "import" keywords in your example then?<br /><br />My point is: I can choose what to export with modules but this don't apply for typeclasses. Are they same thing? Modules don't provide had-oc polymorphism by themselves either.Sawadyhttps://www.blogger.com/profile/15647975101406450223noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-59314497219085128152012-07-19T12:55:25.960-07:002012-07-19T12:55:25.960-07:00No, it IS a replacement for the Haskell module sys...No, it IS a replacement for the Haskell module system. Chris's big insight was that modules and type-classes are basically the same thing.<br /><br />For default function implementations, I create a smart constructor where you pass it the minimal subset necessary to define the interface and it fills out the rest.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-10599147100024185862012-07-19T12:52:03.624-07:002012-07-19T12:52:03.624-07:00Oh sorry, I thought that you want to replace the h...Oh sorry, I thought that you want to replace the haskell module system too, but I see that It's only for typeclasses.<br /><br />One last question though, how can I provide a default implementation for some function of the interface (like typeclasses can)Sawadyhttps://www.blogger.com/profile/15647975101406450223noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-54510572722393153992012-07-19T12:46:22.719-07:002012-07-19T12:46:22.719-07:00module Gabriel.Stack (S.Stack, someStackInstance)
...module Gabriel.Stack (S.Stack, someStackInstance)<br /><br />import qualified Sawady.Stack as S<br /><br />data StackLike a s = StackLike {<br /> push :: a -> s -> s,<br /> pop :: s -> s,<br /> emptyS :: s,<br /> isEmptyS :: s -> Bool }<br /><br />someStackInstance :: StackLike a (S.Stack a)<br />someStackInstance = StackLike {<br /> push = S.push,<br /> pop = S.pop,<br /> emptyS = S.emptyS,<br /> isEmptyS = S.isEmptyS }<br /><br />-- Some other module<br />{-# LANGUAGE RecordWildCards #-}<br /><br />import Gabriel.Stack<br /><br />StackLike{..} = someStackInstance<br /><br />Think of these dictionaries as being just like type classes. The above is the dictionary equivalent of a multi-parameter type-class.Gabriella Gonzalezhttps://www.blogger.com/profile/01917800488530923694noreply@blogger.comtag:blogger.com,1999:blog-1777990983847811806.post-14083180098378269222012-07-19T12:38:18.322-07:002012-07-19T12:38:18.322-07:00I'm thinking. A common porpouse of modules is ...I'm thinking. A common porpouse of modules is to hide implementation info to the user, then they are not only interfaces.<br /><br />For example:<br /><br />--------------<br />module (Stack,push,pop,emptyS,isEmptyS) where<br /><br />data Stack a = MkStack {stack :: [a]} -- we don't export the abstraction function<br /><br />push x = MkStack . (x:) . stack<br />pop = MkStack . tail . stack<br />emptyS = MkStack []<br />isEmptyS = null . stack<br />--------------<br /><br />And the user doesn't know that I implement a Stack using a List. I can impose invariants too, because I know that the user cannot get access to any implementation of functions provided by the interface. I can't do the same with these first-class modules that you are talking about, right? (I don't see how)Sawadyhttps://www.blogger.com/profile/15647975101406450223noreply@blogger.com