<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Can a ghc-plugin do that?<br>
<br>
Yes, see
<a class="moz-txt-link-freetext" href="https://hackage.haskell.org/package/overloaded-0.3.1/docs/Overloaded-Symbols.html">https://hackage.haskell.org/package/overloaded-0.3.1/docs/Overloaded-Symbols.html</a><br>
<br>
- Oleg<br>
</p>
<div class="moz-cite-prefix">On 8.12.2023 17.50, Max Ulidtko wrote:<br>
</div>
<blockquote type="cite" cite="mid:YOUC5S.FKWSUGJUCW9T3@gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<div id="geary-body" dir="auto">
<div>Greetings @cafe,</div>
<div><br>
</div>
<div>Transparent wrapping of string literals into custom types,
driven automatically by type inference, is of course very
useful. I'm getting huge mileage off GHC's <font
face="monospace">OverloadedStrings</font> when working with
e.g. the <font face="monospace">Text</font> type:</div>
<div><br>
</div>
<div>
<pre><span class="kr">class</span> <span class="kt">IsString</span> <span class="n">a</span> <span class="kr">where</span>
<span class="n">fromString</span> <span class="ow">::</span> <span class="kt">String</span> <span class="ow">-></span> <span class="n">a</span></pre>
<pre><span class="n">
</span></pre>
<pre><span class="n">instance IsString Text {- ... -}</span></pre>
<pre><span class="n">
</span></pre>
<pre><span class="n">greeting :: Text</span></pre>
<pre><span class="n">greeting = "Hello"</span></pre>
</div>
<div><br>
</div>
<div>But I hope there's possibly room for improvement. I've got
a bag of several use-cases; perhaps the simplest is the
following.</div>
<div><br>
</div>
<div>Consider a type called <font face="monospace">NonEmptyText</font>
which is a (wrapper around) Text, but with an added invariant
that the contained text is never the empty string "".</div>
<div><br>
</div>
<div>Depending on codebase, such a type can be very useful too,
allowing to offload the burden of keeping track of the
invariant onto machine; the typechecker will show me all the
spots to handle, current or future ones. IOW, this type
enables writing a non-boolean-blind null check, <font
face="monospace">toNonEmptyText :: ToText str => str
-> Maybe NonEmptyText</font> — which makes not only the
programmer, but also the compiler, aware of the null-check
result.</div>
<div><br>
</div>
<div>I'm focusing the example on Text to somewhat maintain
generality of the idea. <font face="monospace">NonEmpty Char</font>
can be an alternative formulation of "non-empty String";
length-indexed vectors could possibly handle this too. But
there're all sorts of invariants besides non-emptiness one may
wish to express.</div>
<div><br>
</div>
<div>I'll omit other examples; but to summarize, I've got plenty
of practical motivation to get "smart literals".</div>
<div><br>
</div>
<div>And to the point: <font face="monospace">IsString</font>
as it stands, forces us to write much unsatisfactory
instances:</div>
<div><br>
</div>
<div>
<div>
<pre>instance IsString NonEmptyText where</pre>
<pre> <span class="n">fromString (c:cs)</span> = NonEmptyText.new c cs</pre>
<pre> fromString [] = error "unavoidable runtime panic" -- argh</pre>
</div>
</div>
<div><span class="n"><br>
</span></div>
<div>... despite <font face="monospace">fromString</font> being
invoked, presumably with OverloadedStrings, on a string
literal — which is statically known at compile-time.</div>
<div><br>
</div>
<div>I'd imagine upgrading the interface with GHC's own
TypeLits:</div>
<div><br>
</div>
<div>
<div>
<pre>class KnownSymbol lit => IsString' str lit where</pre>
<pre> fromString' :: proxy lit -> str</pre>
<pre>
</pre>
</div>
</div>
<div>This 2-parameter formulation of IsString enables pretty
straightforward user code, along the lines of:</div>
<div><br>
</div>
<div><font face="monospace">type family MyInvariant (s ::
Symbol) :: Constraint where</font></div>
<div><font face="monospace">Â MyInvariant "" = TypeError ('Text
"Empty string literal")</font></div>
<div><font face="monospace">Â MyInvariant _s = ()</font></div>
<div><font face="monospace"><br>
</font></div>
<div><font face="monospace">instance (MyInvariant lit,
KnownSymbol lit) => IsString' NonEmptyText lit where</font></div>
<div><font face="monospace">Â fromString' = nothingImpossible .
NeT.fromText . toText . symbolVal where</font></div>
<div><font face="monospace">Â Â nothingImpossible (Just x) = x</font></div>
<div><font face="monospace">Â Â nothingImpossible Nothing =
error "typelevel invariant violated"</font></div>
<div><font face="monospace"><br>
</font></div>
<div><font face="monospace">text1, text2 :: NonEmptyText</font></div>
<div><font face="monospace">text1 = fromString' $ Proxy @"hello,
types"</font></div>
<div><font face="monospace">text2 = fromString' $ Proxy @"" Â Â
    -- compile error here, as expected!</font></div>
<div><br>
</div>
<div>With the primes, this is possible to write today. With <font
face="monospace">AllowAmbiguousTypes</font>, the unwieldy
syntax can be abbreviated somewhat, e.g. <font
face="monospace">smartLiteral @"overloaded string literal
with compiler-checked invariant"</font> — but is still
rather awkward... in the same way that ubiquitous T.pack is
annoying enough to want to enable OverloadedStrings and stop
seeing it all over the place.</div>
<div><br>
</div>
<div>The question is, could this <font face="monospace">IsString'</font>
somehow become the <font face="monospace">IsString</font>
that <font face="monospace">OverloadedStrings</font> is
friends with?</div>
<div>I guess, not soon, with compatibility in mind... But in
principle? Is it even worth trying to pursue the GHC Proposal
path?</div>
<div>Perhaps a new extension <font face="monospace">OverloadedTypelevelStrings</font>?</div>
<div><br>
</div>
<div>Relatedly... Can a GHC Plugin do this?</div>
<div><br>
</div>
<div>And overall, to zoom out of "XY Problem" pitfalls: am I
missing a neater way to have "partial" IsString instances,
those that can reject some literals at compile-time?</div>
<div><br>
</div>
<div>Having this code work is the goal:</div>
<div><br>
</div>
<div>
<div><font face="monospace">text1, text2 :: NonEmptyText</font></div>
<div><font face="monospace">text1 = "hello, types"</font></div>
<div><font face="monospace">text2 = "" -- compile error</font></div>
</div>
<div><br>
</div>
<div>Best regards,</div>
<div>Max</div>
</div>
<br>
<fieldset class="moz-mime-attachment-header"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
<a class="moz-txt-link-freetext" href="http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe">http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe</a>
Only members subscribed via the mailman list are allowed to post.</pre>
</blockquote>
</body>
</html>