ckissane added a comment.

In D130516#3694366 <https://reviews.llvm.org/D130516#3694366>, @dblaikie wrote:

> In D130516#3694151 <https://reviews.llvm.org/D130516#3694151>, @MaskRay wrote:
>
>> In D130516#3688236 <https://reviews.llvm.org/D130516#3688236>, @dblaikie 
>> wrote:
>>
>>> In D130516#3688123 <https://reviews.llvm.org/D130516#3688123>, @MaskRay 
>>> wrote:
>>>
>>>> I'd like to make a few arguments for the current namespace+free function 
>>>> design, as opposed to the class+member function design as explored in this 
>>>> patch (but thanks for the exploration!).
>>>> Let's discuss several use cases.
>>>>
>>>> (a) if a use case just calls compress/uncompress. The class design has 
>>>> slightly more boilerplate as it needs to get the algorithm class, a new 
>>>> instance, or a singleton instance.
>>>> For each new use, the number of lines may not differ, but the involvement 
>>>> of a a static class member or an instance make the reader wonder whether 
>>>> the object will be reused or thrown away.
>>>> There is some slight cognitive burden.
>>>> The class design has a non-trivial one-shot cost to have a function 
>>>> returning the singleton instance.
>>>
>>> Though there must've been a condition that dominates this use somewhere - 
>>> I'd suggest that condition could be where the algorithm is retrieved, and 
>>> then passed to this code to use unconditionally.
>>>
>>> If the algorithm object is const and raw pointers/references are used, I 
>>> think it makes it clear to the reader that there's no ownership here, and 
>>> it's not stateful when compressing/decompressing.
>>
>> A pointer to a singleton compression class is isomorphic to an `enum class 
>> CompressionType` variable.
>
> I don't mean to suggest that either design is fundamentally more or less 
> functional - I'm totally OK with/agree that both design directions allow the 
> implementation of all the desired final/end-user-visible functionality.
>
> I'm trying to make a point about which, I think, achieves that goal in a 
> "better" way - that's the space of design discussions, I think - what kinds 
> of (developer, maintenance, etc) costs different designs incur.
>
>> Using an enum variable doesn't lose any usage pattern we can do with a 
>> pointer to a singleton compression class.
>
> I agree that either design doesn't change what's possible - I do, though, 
> think that the "usage patterns" are meaningfully different between the two 
> designs.
>
>> An enum variable allows more patterns, as the allowed values are enumerable 
>> (we don't need to worry about -Wswitch for the uses).
>>
>> Say, we do
>>
>>   auto *algo = !compression::ZlibCompression;
>>   if (!algo)
>>     ...
>>   
>>   
>>   algo->compress(...);
>>
>> either together or apart, the result is similar to the following but with 
>> (IMO) slightly larger cognitive burden:
>>
>>   if (!compression::isAvailable(format))
>>     ...
>>   
>>   compression::compress(format);
>
> Specifically two APIs that are related (it's important/necessary to check for 
> availability before calling compress or decompress) in their contracts but 
> unrelated in their API use makes it easier to misuse the APIs and have a 
> situation where the availability check doesn't cover the usage. That's what I 
> think is important/important to discuss here.
>
>>>> (b) zlib compress/uncompress immediately following an availability check.
>>>>
>>>>   // free function
>>>>   if (!compression::zlib::isAvailable())
>>>>     errs() << "cannot compress: " << 
>>>> compression::zlib::buildConfigurationHint();
>>>>   
>>>>   // class
>>>>   auto *algo = !compression::ZlibCompression;
>>>>   if (!algo->isAvailable()) {
>>>>     errs() << "cannot compress: " << algo->buildConfigurationHint();
>>>>   }
>>>
>>> I think maybe this code might end up looking like:
>>>
>>>   Algo *algo = getAlgo(Zlib)
>>>   if (!algo)
>>>     errs() ...
>>>
>>> It's possible that this function would return non-null even for a 
>>> non-available algorithm if we wanted to communicate other things (like the 
>>> cmake macro name to enable to add the functionality)
>>
>> I think this is similarly achieved with an enum variable.
>> With the class based approach, a pointer has a static type of the ancestor 
>> compression class and a dynamic type of any possible algorithm.
>> This is not different from that: the enum variable may have a value the enum 
>> class supports.
>
> I agree that the code is similar in either case, but with a small difference 
> that is important to me - that accessing the algorithm necessarily (to some 
> degree - you could still have code that doesn't test the 
> condition/dereferences null, the same way that code can dereference an empty 
> Optional without checking first - but at least the API I'm suggesting makes 
> clear there's a connection between availability and usage).
>
>>>> (c) zlib/zstd compress/uncompress immediately following an availability 
>>>> check.
>>>>
>>>>   // free function
>>>>   if (!compression::isAvailable(format))
>>>>     errs() << "cannot compress: " << 
>>>> compression::buildConfigurationHint(format);
>>>>   
>>>>   // class
>>>>   std::unique_ptr<Compression> algo = make_compression(format);
>>>>   if (!algo->isAvailable()) {
>>>>     errs() << "cannot compress: " << algo->buildConfigurationHint();
>>>>   }
>>>
>>> I don't think there's a need for unique_ptr here - algorithms can be 
>>> constant singletons, referenced via raw const pointers/references without 
>>> ownership.
>>>
>>> & this example doesn't include the code that does the 
>>> compression/decompression,  which seems part of the discussion & part I 
>>> find nice in that the type of compression used matches the type used in the 
>>> check necessarily rather than being passed into two APIs independently.
>>
>> Thanks for clarification. Then this fits my "singleton compression classes 
>> are isomorphic to an `enum CompressionType` variable" argument :)
>
> I don't understand what you're saying here. Could you rephrase/expand a bit?

@dblaikie, @MaskRay I think I have worked out something that is the best of 
both worlds:
none compression is represented simply as a none type for use cases that will 
use `Optional<CompressionKind>`.
once you have a CompressionKind itself you can pass it around as a value 
(because a CompressionKind is just a struct containing one uint8_t (a fake 
"enum")).
due to my operator overloading, you can do stuff like this:

  llvm::compression::OptionalCompressionKind OptionalCompressionScheme =
            llvm::compression::getOptionalCompressionKind(CompressionSchemeId);
        if (!OptionalCompressionScheme) {
          return llvm::MemoryBuffer::getMemBuffer(Blob, Name, true);
        }
        llvm::compression::CompressionKind CompressionScheme =
            *OptionalCompressionScheme;
        if (!CompressionScheme) {
          Error("compression class " +
                (CompressionScheme->getName() + " is not available").str());
          return nullptr;
        }
        SmallVector<uint8_t, 0> Uncompressed;
        if (llvm::Error E = CompressionScheme->decompress(
                llvm::arrayRefFromStringRef(Blob), Uncompressed, Record[0])) {
          Error("could not decompress embedded file contents: " +
                llvm::toString(std::move(E)));
          return nullptr;
        }
        return llvm::MemoryBuffer::getMemBufferCopy(
            llvm::toStringRef(Uncompressed), Name);

(excerpt from `ASTReader.cpp`)


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D130516/new/

https://reviews.llvm.org/D130516

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to