<div dir="ltr">Also, if you do want to experiment with this in ghci you need to set some flags in .ghci:<div><br></div><div>-- ghci binds 'it' to the last expression by default, but it assumes it lives in Type. this blocks overloaded printing<br>:set -fno-it<br><br></div><div>-- replace System.IO.print with whatever 'print' is in scope. You'll need a RuntimeRep polymorphic 'print' function, though.<br>:set -interactive-print print<br><br></div><div>-- we don't want standard Prelude definitions. The above Lev trick for ifThenElse was required because turning on RebindableSyntax broke if.<br>:set -XRebindableSyntax -XNoImplicitPrelude<br><br></div><div>etc.</div><div><br></div><div>With that you can get surprisingly far. It is rather nice being able to use (+) and a Show and the like on primitive Int#s and what have you.</div><div><br></div><div>For me the main win is that I can do things like install Eq on (MutableByteArray# s) and the like and stop having to use random function names to access that functionality.</div><div><br></div><div>You can also use the new UnliftedDataTypes and/or UnliftedNewtypes to do things like pass around a Natural# that is stored in a couple of registers and then build support for it. This is also included in that repo above.</div><div><br></div><div><br></div><div><div>-Edward</div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, May 9, 2022 at 12:24 PM Edward Kmett <<a href="mailto:ekmett@gmail.com">ekmett@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">It is rather shockingly difficult to get it to work out because of the default definitions in each class.<div><br></div><div>Consider just<br></div><div><font face="monospace"><br>class Eq (a :: TYPE r) where<br>  (==), (/=) :: a -> a -> Bool</font><br><br>That looks good until you remember that<br><br><font face="monospace">  x == y = not (x /= y)</font></div><div><font face="monospace">  x /= y = not (x == y)</font></div><div><br></div><div>are also included in the class, and cannot be written in a RuntimeRep polymorphic form!</div><div><br></div><div>The problem is that x has unknown rep and is an argument. We can only be levity polymorphic in results.</div><div><br></div><div>So you then have to do something like</div><div><font face="monospace"><br>  default (==) :: EqRep r => a -> a -> Bool<br>  (==) = eqDef<br>  default (/=) :: EqRep r => a -> a -> Bool<br>  (/=) = neDef</font></div><div><br></div><div><font face="monospace"><br>class EqRep (r :: RuntimeRep) where<br>  eqDef, neDef :: forall (a :: TYPE r). Eq a => a -> a -> Bool<br></font></div><div><br></div><div>and then bury them in a class that actually knows about the RuntimeRep.</div><div><br></div><div>We can lift the Prelude.Eq into the modified Eq above pointwise inside kind Type.</div><div><font face="monospace"><br></font></div><font face="monospace">instance Prelude.Eq a => Eq (a :: Type) where<br>  (==) = (Prelude.==)<br>  (/=) = (Prelude./=)</font><div><div><br></div></div><div>and/or we can instantiate EqRep at _all_ the RuntimeReps.</div><div><br></div><div>That is where we run into a problem. You could use a compiler plugin to discharge the constraint (which is something I'm actively looking into) or you can do something like write out a few hand-written instances that are all completely syntactically equal:</div><div><font face="monospace"><br></font></div><div><font face="monospace">instance EqRep LiftedRep where</font></div><div><font face="monospace">  eqDef x y = not (x /= y)</font></div><div><font face="monospace">  neDef x y = not (x == y)</font></div><div><font face="monospace"><br></font></div><div><font face="monospace">instance EqRep ... where</font></div><div><font face="monospace">   ...</font></div><div><br></div><div>The approach I'm taking today is to use backpack to generate these EqRep instances in a canonical location. It unfortunately breaks GHC when used in sufficient anger to handle TupleRep's of degree 2 in full generality, because command line lengths for each GHC invocation starts crossing 2 megabytes(!) and we break operating system limits on command line lengths, because we don't have full support for passing arguments in files from cabal to ghc.</div><div><br></div><div>The approach I'd like to take in the future is to discharge those obligations via plugin.</div><div><br></div><div><br></div><div>There are more tricks that you wind up needing when you go to progress to handle things like Functor in a polymorphic enough way.</div><div><font face="monospace"><br></font></div><div><font face="monospace">type Lev (a :: TYPE r) = () => a</font></div><div><br></div><div>is another very useful tool in this toolbox, because it is needed when you want to delay a computation on an argument in a runtime-rep polymorphic way.</div><div><br></div><div>Why? Even though a has kind TYPE r. Lev a always has kind Type!</div><div><br></div><div>So I can pass it in argument position independent of RuntimeRep.</div><div><font face="monospace"><br></font></div><div><font face="monospace">ifThenElse :: forall r (a :: TYPE r). Bool -> Lev a -> Lev a -> a</font></div><div><font face="monospace">ifThenElse True x _ = x</font></div><div><font face="monospace">ifThenElse False _ y = y</font></div><div><br></div><div>Note this function didn't need any fancy FooRep machinery and it has the right semantics in that it doesn't evaluate the arguments prematurely! This trick is needed when you want to go convert some kind of RuntimeRep polymorphic Maybe or List for one RuntimeRep to one for another RuntimeRep unless you want to deal with an explosive number of instances parameterized on pairs of RuntimeReps.</div><div><br></div><div><div><div><a href="https://github.com/ekmett/unboxed" target="_blank">https://github.com/ekmett/unboxed</a> is a repo of me experimenting with this from last year some time.</div><br></div><div>I'm also giving a talk at Yow! LambdaJam in a week or so on this!</div></div><div><b><br></b></div><div>-Edward</div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, May 9, 2022 at 11:27 AM Clinton Mead <<a href="mailto:clintonmead@gmail.com" target="_blank">clintonmead@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">Hi All<div><br></div><div>It seems to me to be a free win just to replace:</div><div><br></div><div>`class Num a where`</div><div><br></div><div>with </div><div><br></div><div>`class Num (a :: (r :: RuntimeRep)) where`</div><div><br></div><div>And then one could define `Num` instances for unlifted types.</div><div><br></div><div>This would make it possible to avoid using the ugly `+#` etc syntax for operations on unlifted types. </div><div><br></div><div>`Int#` and `Word#` could have `Num` instances defined just as `Int` and `Word` already have.</div><div><br></div><div>I presume there's a reason why this hasn't been done, but I was wondering why?</div><div><br></div><div>Thanks,</div><div>Clinton</div></div>
_______________________________________________<br>
Glasgow-haskell-users mailing list<br>
<a href="mailto:Glasgow-haskell-users@haskell.org" target="_blank">Glasgow-haskell-users@haskell.org</a><br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/glasgow-haskell-users" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bin/mailman/listinfo/glasgow-haskell-users</a><br>
</blockquote></div>
</blockquote></div>