farlee2121 / FsSpec

FsSpec represents value constraints as data to reuse one constraint declaration for validation, data generation, error explanation, and more.
25 stars 0 forks source link

Name already taken #1

Open wekempf opened 2 years ago

wekempf commented 2 years ago

There's already an FsSpec, a BDD testing framework for F#, that will cause problems and confusion. I suggest you find a better name?

I found this project via F# weekly and the blog post at https://spencerfarley.com/draft/fsspec/. It intrigued me immediately. Are you aware of clojure.spec? If not, there's lots to learn from there, as it's a very similar system that's been battle hardened. I very much like the idea of being able to specify constraints as data and will be watching this project with interest, and may even contribute. Sorry, I know this is commentary and not part of the issue, but I don't see any other way to interact yet.

wekempf commented 2 years ago

Just noticed from docs that, yes, you do know about clojure.spec.

Thought I'd follow up with a name suggestion... fsharp.spec. In line with clojure.spec and has no ambiguity with FsSpec the testing library.

farlee2121 commented 2 years ago

Glad the project intrigues you!

The library is currently usable and what it does is well covered with tests. Now the library needs use and feedback to feel out the rough edges in practical application. I'd love to hear of your experiences if give the library a try!

As to the name, do you have a link to the other FsSpec library?

wekempf commented 2 years ago

There's actually multiple GitHub repos with the name, but the one that matters is https://github.com/chaliy/fsspec which is a .NET/F# project. Granted, they don't have a NuGet package but I've seen the library referenced elsewhere. May not be the end of the world since they didn't create a package on nuget.org, but courtesy and avoiding confusion are reasons to at least consider it.

farlee2121 commented 2 years ago

Hmm. That project doesn't have a NuGet package and hasn't been touched in 12 years.

I'll keep it in mind, but it doesn't feel pressing to make a change. It's yet to be seen if this project will gain traction anyway.

I understand FSharp.spec, but I also hesitate because this library differs considerably from Clojure's spec system. There is no intent to take this library in the direction of set semantics or integrate it as part of the type system. Clojure.spec is built on predicates/expressions, which I considered and rejected as a sustainable approach for this library. Clojure.spec leans into Design-by-Contract-style assertions while this library embraces type-driven development.

farlee2121 commented 2 years ago

This does have me thinking about how this library enables Clojure.spec-style constraint "inheritance".

One could build a core collection of common constraints or otherwise build up constraints in a readable way.

let markdown = //...
let sanitizedMarkdown = markdown &&& //...
let recipeIngredientSpec = sanitizedMarkdown &&& notEmpty

Do these semantic compositions of constraints need special representation? (maybe leaf type LabeledSpec (name*wrappedSpec) It could improve message generation. It's not clear what other likely use it would serve... A very interesting line of thought

wekempf commented 2 years ago

I'm a little confused by some of your clojure.spec descriptions. How is clojure.spec "part of the type system", especially when Clojure is a dynamic language? I'm also unsure what you mean by "Design-by-Contract-style assertions"? A constrained type is as much a "Design-by-Contract style assertion" as clojure.spec's are, and they are far more "part of the type system" than the wrappers added by clojure.spec. There are certainly differences between this and clojure.spec, where you're working within the type system of a strongly typed language and clojure.spec embraces the dynamic nature of the language it's working within. But in the end both are doing runtime type verification (even if, due to the nature of the strongly typed language, the runtime type verification here is confined only to data creation, rather than sprinkled everywhere). IMHO, neither is doing DbC, as you can't specify something like "when you pop from the stack the stack will have one less item" as you would in Eiffel's DbC.

farlee2121 commented 2 years ago

Thanks for sharing your thoughts. I'm enjoying how this makes me think differently.

I'm not experienced in Eiffel, so I may not have the same understanding of DbC as you. But here are some of my reasonings

Clojure.spec as DbC -> Clojure specs are primarily for use in decorating functions with pre- and post-conditions (including relationships between input and output). These conditions will throw exceptions if violated, as with DbC I've experienced in other places.

Clojure.spec is more a part of the type system -> F# certainly has a more rigorous type system and more types are leveraged to achieve a type-driven style. However, FsSpec is not essential to make any of that type-driven magic work. It can make it nicer, but all the type safety would be fine without it. Clojure, on the other hand, is dynamic as you've said. Spec is the language's approach to defining and enforcing types. I consider it similar to the optional type system TypeScript brings to Javascript or Sorbet to Ruby.

This does have me realizing how easy it would be to make descriptive assertions using spec data. Something like

let divide dividend divisor = 
  Spec.assert(NonNegativeInt, divisor)
  divident/divisor

The assertion can both check satisfaction and generate a descriptive exception from the spec. There are valid cases for this, even if I strongly prefer total functions over exceptions.

It's easy enough I think individual users could implement it if they want it, and we can integrate it if there is enough demand. Something like

module Spec = 
  let assert' spec value =
    let valueExplanation = Spec.explain spec value
    if Explanation.isOk valueExplanation.Explanation
    then ()
    else failwith (valueExplanation |> Formatters.prefix_allresults)

Maybe also make a custom exception type.