Dropping a typeclass instance and the package version policy - haskell

I've been asked to drop my dependency on system-filepath.
My package defines a typeclass Arguable, and defines an instance for Filesystem.Path's FilePath type. No system-filepath means no Filesystem.Path means no FilePath, so by dropping this dependency, I'd be changing my API to no longer provide the Arguable instance.
How does that line up with the PVP? Is this a major version change?

Yes, it's a major version change. The Haskell wiki page on the PVP states about A.B.C version numbers (relevant phrase bolded):
If any entity was removed, or the types of any entities or the definitions of datatypes or classes were changed, or orphan instances were added or any instances were removed, then the new A.B must be greater than the previous A.B. Note that modifying imports or depending on a newer version of another package may cause extra orphan instances to be exported and thus force a major version change.
Otherwise, if only new bindings, types, classes, non-orphan instances or modules (but see below) were added to the interface, then A.B may remain the same but the new C must be greater than the old C. Note that modifying imports or depending on a newer version of another package may cause extra non-orphan instances to be exported and thus force a minor version change.
Otherwise, A.B.C may remain the same (other version components may change).

Related

Difference between "::mysql::server" and "mysql::server"

I was going through an old puppet code. It was using mysql puppet module to install mysql-server.
I came across this
class { '::mysql::server':
}
and this
class { 'mysql::server':
}
Now I'm confused. Do they both mean the same thing or there's any difference between the two?
This is a really good question. The short answer is that they are the same, and that :: isn't needed for class names.
I'd always assumed the initial :: was needed to avoid scope ambiguity (where include bar in class foo would include ::foo::bar rather than ::bar) but checking the docs, they say that, for example, include must use the class's full name.
A working example:
$ cat scope.pp
class foo {
class bar {
notice("foo::bar")
}
class { 'bar':
}
}
class bar {
notice("bar")
}
class { 'foo':
}
$ puppet apply scope.pp
Notice: Scope(Class[Bar]): bar
I'd note that while this is true for class scope, it certainly isn't true for variable scope in Puppet, as below.
$ cat var_scope.pp
$bar = "bar"
class foo {
$bar = "foo::bar"
notice($::bar)
notice($bar)
}
include foo
notice($bar)
$ puppet apply var_scope.pp
Notice: Scope(Class[Foo]): bar
Notice: Scope(Class[Foo]): foo::bar
Notice: Scope(Class[main]): bar
Do they both mean the same thing or there's any difference between the two?
TL;DR: They mean the same thing for classes and defined types. That the form with the leading :: is supported can be viewed as either a backwards-compatibility feature, an internal consistency feature, or both. For variables, however, the leading :: indicates a top-scope variable, which might or might not be what you get if you use the bare variable name.
To clarify some details of the fine answer that #Jon already presented, we have to consider the behavior of Puppet version 3 and earlier. This is no longer documented on Puppet's main documentation site, but we can find the relevant docs in Puppet's online archive of obsolete documentation. Specifically, we want to look at Puppet namespaces and their behavior. The docs are an interesting read if you're into that sort of thing, especially the historical perspective on how Puppet 3 ended up where it was, but here's a somewhat whimsical version of events:
In the beginning, the Forge was formless and void, and there were no modules. Everyone wrote their own code for everything, and the devops faithful were sorely oppressed, reinventing many wheels.
In those days, the idea of modules was conceived. The modules of that day were built using the features then abroad in the land, such as the import function, which has since departed. But with code sharing came name collisions, and to that, the people responded with namespacing. And Reductive Labs looked on namespacing and saw that it was good.
But not everything was clear to Reductive or the people, and in ignorance, Reductive brought forth relative name resolution. And relative name resolution scrutinized names and namespaces very deeply, attempting to resolve even qualified names relative to every namespace in scope. Some of the people rejoiced at the convenience, but the wise among them soon grew troubled. It became clear to them that relative name resolution looked too deeply and saw too much. It sometimes saw things it was not meant to see, and opened paths for the faithful to fall into error.
So the wise intervened. They proclaimed that relative namespacing should be shackled and overcome, tamed by feeding it only names anchored to the one true anonymous namespace, that which existed before anything else Puppet. And the form of the shackles was the leading double colon, ::. And although relative name resolution often performed the same work unshackled, many heeded the wise, and were praised for it.
And Reductive, then naming itself Puppet Labs, regretted creating relative name resolution and urged the people to follow the counsel of the wise. But when it brought forth the Third Age of Puppet, it could not bring itself to trouble those among the people who paid no attention, so it allowed relative name resolution to live.
But at the dawn of the Fourth Age, Puppet, no longer labs, found the courage to at last slay relative name resolution, and it was no more. Since that day, Puppet no longer urges the leading double colon on purveyors and users of classes and types, yet it honors the legacy of past wisdom, and has pity on those who are slow to unlearn it.
Yet Puppet, in its merciful beneficence, has chosen class variables from among all named things as the only ones to bear two names. They have scopes that transcend namespace, and in their scopes they may be known by the simple names of their definitions. Like all named things, however, they can be known anywhere by their namespaced names, formed from their simple names and their class names, in either form. But what, then, of the variables of the top scope? By what name can they be known when they are hidden in the shadows? Here the leading double colon yet serves. Its mark of the top scope is not redundant for variables, and some among the wise make their code clear by using it always for such variables.

Puppet Include vs Class and Best Practices

When should I be using an include vs a class declaration? I am exploring creating a profile module right now, but am struggling with methodology and how I should lay things out.
A little background, I'm using the puppet-labs java module which can be found here.
My ./modules/profile/manifests/init.pp looks like this:
class profile {
## Hiera Lookups
$java_version = hiera('profile::jdk::package')
class {'java':
package => $java_version,
}
}
This works fine, but I know that I can also remove the class {'java': block of the code and instead use include java. My question relates around two things. One, if I wanted to use an include statement for whatever reason, how could I still pass the package version from hiera to it? Second, is there a preferred method of doing this? Is the include something I really shouldn't be using, or are there advantages and disadvantages to each method?
My long term goal will be building out profile like modules for my environment. Likely I would have a default profile that applies to all of my servers, and then profiles for different application load outs. I could include the profiles into a role and apply things to my individual nodes at that level. Does this make sense?
Thanks!
When should I be using an include vs a class declaration?
Where a class declares another, internal-only class that belongs to the same module, you can consider using a resource-like class declaration. That leverages your knowledge of the implementation details of the module, as you need to be able to prove that no other declaration of the class in question will be evaluated before the resource-like one. If ever that constraint is violated, catalog building will fail.
Under all other circumstances, you should use include or one of its siblings, require and contain.
One, if I wanted to use an include statement for whatever reason, how
could I still pass the package version from hiera to it?
Exactly the same way you would specify any other class parameter via Hiera. I already answered that for you.
Second, is
there a preferred method of doing this?
Yes, see above.
Is the include something I
really shouldn't be using, or are there advantages and disadvantages
to each method?
The include is what you should be using. This is your default, with require and contain as alternatives for certain situations. The resource-like declaration syntax seemed good to the Puppet team when they first introduced it, in Puppet 2.6, along with parameterized classes themselves. But it turns out that that syntax introduced deep design problems into the language, and it has been a source of numerous bugs and headaches. Automatic data binding was introduced in Puppet 3 in part to address many of those, allowing you to assign values to class parameters without using resource-like declarations.
The resource-like syntax has the single advantage -- if you want to consider it one -- that the parameter values are expressed directly in the manifest. Conventional Puppet wisdom holds that it is better to separate data from code, however, so as to avoid needing to modify manifests as configuration requirements change. Thus, expressing parameter values directly in the manifest is a good idea only if you are confident that they will never change. The most significant category of such cases is when a class has read data from an external source (i.e. looked it up via Hiera), and wants to pass those values on to another class.
The resource-like syntax has the great disadvantage that if a resource-like declaration of a given class is evaluated anywhere during the construction of a catalog for a given target node, then it must be the first declaration of that class that is evaluated. In contrast, any number of include-like declarations of the same class can be evaluated, whether instead of or in addition to a resource-like declaration.
Classes are singletons, so multiple declarations have no more effect on the target node than a single declaration. Allowing them is extremely convenient. Evaluation order of Puppet manifests is notoriously hard to predict, however, so if there is a resource-like declaration of a given class somewhere in the manifest set, it is very difficult in the general case to ensure that it is the first declaration of that class that is evaluated. That difficulty can be managed in the special case I described above. This falls into the more general category of evaluation-order dependencies, and you should take care to ensure that your manifest set is free of those.
There are other issues with the resource-like syntax, but none as significant as the evaluation-order dependency.
Clarification with respect to automated data binding
Automated data binding, mentioned above, associates keys identifying class parameters with corresponding values for those parameters. Compound values are supported if the back end supports them, which the default YAML back end in fact does. Your comments on this answer suggest that you do not yet fully appreciate these details, and in particular that you do not recognize the significance of keys identifying (whole) class parameters.
I take your example of a class that could on one hand be declared via this resource-like declaration:
class { 'elasticsearch':
config => { 'cluster.name' => 'clustername', 'node.name' => 'nodename' }
}
To use an include-like declaration instead, we must provide a value for the class's "config" parameter in the Hiera data. The key for this value will be elasticsearch::config (<fully-qualified classname> :: <parameter name>). The associated value is wanted puppet-side as a hash (a.k.a. "associative array", a.k.a. "map"), so that's how it is specified in the YAML-format Hiera data:
elasticsearch::config:
"cluster.name": "clustername"
"node.name": "nodename"
The hash nature of the value would be clearer if there were more than one entry. If you're unfamiliar with YAML, then it would probably be worth your while to at least skim a primer, such as the one at yaml.org.
With that data in place, we can now declare the class in our Puppet manifests simply via
include 'elasticsearch'

What exactly is considered a breaking change to a PureScript library?

The Rust community has a fairly detailed description of their interpretation of Semantic Versioning.
The PureScript community has this, which includes:
We should write a semver tutorial for beginners, specifically its use in PureScript and the way we rely on ~-versions.
The odd thing is that looking at an assortment of 65 randomish purescript libraries, they all use ^-versions rather than ~-versions, but I have been unable to find any newer documentation and we recently had our build broken due to a mismatch in expectations.
Does the PureScript community have a reasonably consistent interpretation of semver, specifically regarding what is or isn't considered a breaking change? If so, what is it?
We don't have an exhaustive list anywhere, no. Now's as good a time as any to start one!
Taking advantage of features that require a newer compiler than when the current version was released.
Adding a dependency.
Removing a dependency.
Bumping a dependency's major version.
Deleting or renaming a module.
Removing a member (that means anything - type, value, class, kind, operator) from a module (either by hiding the export or deleting it).
Changing a type signature of an existing function or value in a way that means it won't unify with the previous version (so it is allowable to make types more general, but not less so).
Adding, removing, or altering the kind of type variables for a type.
Adding, removing, or altering data constructors for a type (unless the type does not export its constructors).
Adding or removing members of a type class declaration.
Changing the expected type parameters of a class.
Adding or altering functional dependencies of a class.
Changing the laws of a class.
Removing instances of a class.
Pretty much anything other than adding new members (or re-exports) to a module is considered a breaking change!
Occasionally we've made changes that are technically breaking (due to type signature changes), but done so to fix something that was completely unusable without the fix. In those cases they've gone out as patch bumps, but those cases are very rare. They tend only to occur when the FFI is involved.
Re: ~ vs ^... I think at the time the linked page was made there wasn't the option to use ^ in Bower (or it didn't default to that at least). ^ is the preferred/recommended range to use for libraries now.

What exactly is considered a breaking change to a library crate?

Rust crates use Semantic Versioning. As a consequence, each release with a breaking change should result in a major version bump. A breaking change is commonly considered something that may break downstream crates (code the depends on the library in question).
However, in Rust a whole lot has the potential of breaking downstream crates. For example, changing (including merely adding to) the set of public symbols is possibly a breaking change, because downstream crates can use glob-imports (use foo::*;) to pull symbols of our library into their namespace. Thus, adding symbols can break dependent crates as well; see this example.
Similarly, changing (adding or changing the version) the set of our dependencies can break downstream builds. You can also imagine that the downstream crate relies on a specific size of one of our public types. This is rarely, if at all, useful; I just want to show: everything could be a breaking change, if only the downstream crate tries hard enough.
Is there any guideline about this? What exactly is considered a breaking change and what not (because it's considered "the user's fault")?
There is a Rust RFC on this subject: RFC 1105: API Evolution. It's applicable to any Rust library project, and it covers all kinds of changes (not just breaking changes) and how they impact semantic versioning. I'll try to summarize the key points from the RFC in order to not make this answer a link-only answer. :)
The RFC acknowledges that pretty much any change to a library can cause a client to suddenly stop compiling. As such, it defines a set of major changes, which require a bump of the major version number, and a set of minor changes, which require a bump of the minor version number; not all breaking changes are major changes.
The key attribute of a minor change is that there must be a way that clients can avoid the breakage in advance by altering slightly their source code (e.g. change a glob import to a non-glob import, disambiguate an ambiguous call with UFCS, etc.) in such a way that the code is compatible with the version prior to the change and with the version that includes the change (assuming that it's a minor release). A minor change must also not force downstream crates to make major breaking changes in order to resolve the breakage.
The major changes defined in the RFC (as of commit 721f2d74) are:
Switching your project from being compatible with the stable compiler to only being compatible with the nightly compiler.
Renaming, moving or removing any public item in a module.
Adding a private field to a struct when all current fields are public.
Adding a public field to a struct that has no private fields.
Adding new variants to an enum.
Adding new fields to an enum variant.
Adding a non-defaulted item to a trait.
Any non-trivial change to the signature of a trait item.
Implementing a fundamental trait on an existing type.
Tightening bounds on an existing type parameter.
Adding or removing arguments to a function.
Any other breaking change that is not listed as a minor change in the RFC.
The minor changes defined in the RFC (as of commit 721f2d74, breaking unless specified) are:
Altering the use of Cargo features on a crate.
Adding new public items in a module.
Adding or removing private fields in a struct when at least one already exists (before and after the change) [not breaking].
Turning a tuple struct with all private fields (with at least one field) into a normal struct, or vice versa.
Adding a defaulted item to a trait.
Adding a defaulted type parameter to a trait [not breaking].
Implementing any non-fundamental trait on an existing type.
Adding any item to an inherent impl.
Changing an undocumented behavior of a function.
Loosening bounds on an existing type parameter [not breaking].
Adding defaulted type parameters to a type or trait [not breaking].
Generalizing an existing struct or enum field by replacing its type by a new type parameter that defaults to the previous type [breaking until issue 27336 is fixed].
Introducing a new type parameter to an existing function.
Generalizing a parameter or the return type of an existing function by replacing the type by a new type parameter that can be instantiated to the previous type.
Introducing new lint warnings/errors.
See the RFC for explanations and examples.

Why can't I replace libraries distributed with GHC? What would happen if I did?

I see in this answer and this one that "everything will break horribly" and Stack won't let me replace base, but it will let me replace bytestring. What's the problem with this? Is there a way to do this safely without recompiling GHC? I'm debugging a problem with the base libraries and it'd be very convenient.
N.B. when I say I want to replace base I mean with a modified version of base from the same GHC version. I'm debugging the library, not testing a program against different GHC releases.
Most libraries are collections of Haskell modules containing Haskell code. The meaning of those libraries is determined by the code in the modules.
The base package, though, is a bit different. Many of the functions and data types it offers are not implemented in standard Haskell; their meaning is not given by the code contained in the package, but by the compiler itself. If you look at the source of the base package (and the other boot libraries), you will see many operations whose complete definition is simply undefined. Special code in the compiler's runtime system implements these operations and exposes them.
For example, if the compiler didn't offer seq as a primitive operation, there would be no way to implement seq after-the-fact: no Haskell term that you can write down will have the same type and semantics as seq unless it uses seq (or one of the Haskell extensions defined in terms of seq). Likewise many of the pointer operations, ST operations, concurrency primitives, and so forth are implemented in the compiler themselves.
Not only are these operations typically unimplementable, they also are typically very strongly tied to the compiler's internal data structures, which change from one release to the next. So even if you managed to convince GHC to use the base package from a different (version of the) compiler, the most likely outcome would simply be corrupted internal data structures with unpredictable (and potentially disastrous) results -- race conditions, trashing memory, space leaks, segfaults, that kind of thing.
If you need several versions of base, just install several versions of GHC. It's been carefully architected so that multiple versions can peacefully coexist on a single machine. (And in particular installing multiple versions definitely does not require recompiling GHC or even compiling GHC a first time, which seems to be your main concern.)

Resources