If you generalise even further to any iterator iter, like so
function f(fn: a->b, lst: iter a) -> iter b
Then you restrict it even further: it must apply fn to every element in order.
This is just scratching the surface, there's a lot of things you can obtain for free just by using a generic type. See this paper or this blog post for a much more accessible writeup.
I never got too far into Haskell and never even started with OCaml, but I can say for a fact that in practice what will happen with C++ is that the function will not be identity; it will just fail to instantiate for some types.
But simple types (i.e. non dependent types) are only capable of expressing extremely weak statements. It’s almost not even worth it in this respect - there are other reasons types are useful.
Yesssssss, theorems for free! One of my favorite papers of all time. The gift that keeps on giving! As soon as I started reading your comment, I was preparing to reply with a link to the paper. 😄
From the article I really take issue with the following advice, it's just completely backwards.
function say_hello_to(name: string)
print("Hello, {name}.")
is better than
function say_hello_to(foo: Foo)
print("Hello, {foo.name}.")
because passing only the data that the function actually needs to operate already tells the reader about all the things that the function does not do, e.g., doing something with .age, etc.
Is it really better though? You can pass literally any string into the method. Whereas for the other implementation, it restricts behavior to your domain type. You, as a caller, should not concern yourself with the internal logic of a method that operates on a business object. This is just abstraction leakage.
Using string here is actually MUCH less expressive. If you want constraints on this method while not passing into the entire object, use a custom value type instead. Such as as a "Name" type with a backing string value.
Parametric polymorphism is by itself an incredibly useful tool. Simple lambda calculi like System F or even Hindley-Milner can be surprisingly powerful in their ability for the programmer to impose rigorous constraints on a program with this one tool.
Haunting_Swimming_62@reddit
Abstraction often leads to more reusable and clearer code. Consider the following function: function f(x: a) -> a There really is only one possible implementation of f. By simply using a generic type you already get certain guarantees about what the function can or cannot do. Consider the following slightly more practical function f(fn: a->b, lst: [a]) -> [b] The only way this function can be implemented is by applying fn to (some subset) of lst! However, it may reverse the list, or choose some subset of elements.
If you generalise even further to any iterator iter, like so function f(fn: a->b, lst: iter a) -> iter b
Then you restrict it even further: it must apply fn to every element in order.
This is just scratching the surface, there's a lot of things you can obtain for free just by using a generic type. See this paper or this blog post for a much more accessible writeup.
Nona_Suomi@reddit
> Consider the following function:
> There really is only one possible implementation of f.
Um what. That is a real big stretch of the notion of possible.
For one, I can just arbitrarily match against any finite subset of types for `a` and return different things for each.
vytah@reddit
Those examples assume no runtime type information. So languages like Haskell, OCaml, Rust, C++.
Dragdu@reddit
I never got too far into Haskell and never even started with OCaml, but I can say for a fact that in practice what will happen with C++ is that the function will not be identity; it will just fail to instantiate for some types.
vytah@reddit
But for those that it instantiates (and given no specialisations, those can introduce random exceptions everywhere), it will be an identity.
Dragdu@reddit
Nope. The point is that if we accept that it will not instantiate for all types, there is suddenly a lot of possible implementations. Take this
it has type sig of T -> T, but will fail to instantiate for the majority of types.
vytah@reddit
... or return an empty iterable.
Haunting_Swimming_62@reddit
Not every iterable has a notion of "empty iterable". For example, take
struct NonEmpty { first: a, rest: [a] }
Clearly this makes for a sensible iterable. However there is no way to construct an empty NonEmpty!
editor_of_the_beast@reddit
But simple types (i.e. non dependent types) are only capable of expressing extremely weak statements. It’s almost not even worth it in this respect - there are other reasons types are useful.
consultio_consultius@reddit
Jump aboard the HKT train. Choo! Choo!
gwillen@reddit
Yesssssss, theorems for free! One of my favorite papers of all time. The gift that keeps on giving! As soon as I started reading your comment, I was preparing to reply with a link to the paper. 😄
BuriedStPatrick@reddit
From the article I really take issue with the following advice, it's just completely backwards.
Is it really better though? You can pass literally any string into the method. Whereas for the other implementation, it restricts behavior to your domain type. You, as a caller, should not concern yourself with the internal logic of a method that operates on a business object. This is just abstraction leakage.
Using
stringhere is actually MUCH less expressive. If you want constraints on this method while not passing into the entire object, use a custom value type instead. Such as as a "Name" type with a backing string value.Dobias@reddit (OP)
Thanks for the great remark! I fully agree, and thus have adjusted the article accordingly.
trmetroidmaniac@reddit
Parametric polymorphism is by itself an incredibly useful tool. Simple lambda calculi like System F or even Hindley-Milner can be surprisingly powerful in their ability for the programmer to impose rigorous constraints on a program with this one tool.