150 karma · joined May 22, 2016
(-> {:a 1 :b 2}
(:c 5.5)
inc) if(TryGetSomething(3, out MyType outvar)) { ... } type digits = RegexString (Regex "^[0-9]+$")
where all values of type digits are strings containing only digits? Then let s1 : digits = "1234"
would typecheck but let s2 : digits = "abc"
would not? That would be very surprising given the F# type system. let v : Vect 3 Int = [1,2,3]
example.There aren't many types shown in the examples but it looks like
let myNonEmptyIntSetOpt = [] |> Set.ofList |> NonEmptyIntSet.TryCreate
would just return None at runtime rather than a compilation error?If your code is structured using small static functions, you don't have any dependencies in the first place, just arguments you are passed and transform. You will probably create interfaces for external services you depend on, but you can avoid needing to mock them if you express the transform directly.
> This is very assuming
I'm not assuming anything, since I wrote the static method and I also decided to call it, presumably for the result it calculates. Your argument appears to be that static methods could contain bad code but that applies to all code you depend on.
Static methods usually don't rely on any hidden state at all. The example originally given was for a graph operation which could just take the input graph as an argument and return the result. When your code is composed of small independent functions you don't need mocking and substitution at all. In my experience most uses of mocks come from functions that do too much in the first place.
I don't see how you have any less control over it than any other code you wrote. If you don't want it to write log statements, then don't do that. Most static methods are small and pure so don't need to write log statements anyway.
> Now your testing will dirty whatever database you're using, as well as run 10x slower.
I've never used a logging framework that didn't allow you to configure where log statements were written, or give you control over the logging threshold for individual classes. However if your method is writing logs then presumably there is a reason, which is just as useful in the tests. If you mock it out then you're testing against different code to the one you will actually run against.
> If you start using a custom delegate solution, then your code is not consistent with everything else that uses DI.
Passing functions as arguments directly is 'DI', just without the need to configure that through an external container. Reducing the amount of interfaces (often with a single implementation) and external configuration makes navigating the code easier.
trait Show[T] { def show(v : T): String }
implicit object ShowInt extends Show[Int] {
def show(i: Int) = i.toString
}
def showLine[T](v : T)(implicit show: Show[T]): Unit = {
println(show.show(v))
}It's not irrelevant - you're using Task/Task<T> as an example of the non-generic base type/generic subtype pattern being necessary, but it isn't.
IEnumerable<T> extending IEnumerable is a consequence of C#1 not supporting generics at all, and the non-generic version would not be necessary if it had. I was just using it as a common example of the boilerplate the pattern incurs.
No I'm not thinking that, I'm saying that implementing a non-generic base interface like
public interface IValidator {
bool Validate(object obj);
}
is pointless because you'll have to do the same type checking/casting in Validate you would have to do in a wrapper which implemented IValidator<object>. Requiring a base interface is worse because you'll have to implement the same boilerplate implementation of Validate(object) in every implementing class - see IEnumerable/IEnumerable<T> as an example.> Obviously this library will need to do run-time casting of types
You've linked to a clojure library which is dynamically typed so I don't see how this is relevant. It's not obvious to me why a C# version would require casting.
Task/Task<T> does follow the pattern but they could have simply introduced a Unit type in the BCL and replaced the non-generic Task type with Task<Unit> instead.
> To run all of these validators against a given class you have three (un)reasonable choices:
You only need to write one wrapper class which encapsulates the cast you'd have to do anyway in a non-generic IValidator interface:
public class Wrapper<T, TBase> : IValidator<TBase>
{
private readonly IValidator<T> inner;
public Wrapper(IValidator<T> v)
{
this.inner = v;
}
public bool Validate(TBase b)
{
if(b is T)
{
return this.inner.Validate((T)(object)b);
}
else return false;
}
} Prelude Text.Printf> printf "%d\n" "invalid"
*** Exception: printf: bad formatting char 'd'
The behaviour of F#'s printfn is hard-coded in the compiler and could not be written in F# iteself.The use laziness will avoid the need to construct a large intermediate collection of Person instances. Obviously you'll need to iterate the entire source collection to find all valid Persons but the same applies to the imperative version. The lazy version composes better however and avoids extra work if some downstream caller only requires the first n valid Persons.
type IEq<'a> =
abstract member eq: 'a -> 'a -> bool
but you then need to explicitly pass instances of this interface around into every method that requires it e.g. let allEq (s: 'a seq) (eq: IEq<'a>) = ...
whereas the haskell version would receive the Eq instance for the input type implicitly.Typeclass instances are globally unique for each type, which cannot be enforced with the interface solution. If you have an ordered map type, you can be sure the Ord instance used for insertion is the same that is used for retrieval. With the interface approach, clients cannot know which instance to use since there could be multiple implementations. The Haskell approach has its own problems, such as the proliferation of newtype wrappers to manage the dispatch mechanism.
Interfaces also hide the representation of one of their arguments (the receiver) whereas typeclasses are just a dictionary of functions.