Every programmer thinks their favorite language has a problem. I am no exception. I used to think that F# misses an ML module system or Haskell-style typeclasses, but I came to realize that the complaint is really about a feature called higher-kinded polymorphism. Without this feature, there is a lot of very beautiful, general, and type-safe code in OCaml and Haskell that cannot be expressed in type-safe F#. It can be expressed, but you are facing a tradeoff:
- Sacrifice generality – this is what workflow builders do. In effect, F# has no true monads, because you cannot easily write code generalized over an arbitrary monad.
- Sacrifice type safety by using downcasts and rely on convention.
Option #1 is by far the most popular, but very detrimental to the F# ecosystem, as it encourages duplication of specific code that could have otherwise been expressed by a single, general definition.
Option #2 is controversial. I have attempted a few encodings I personally have not yet found a practical one, and in practice usually fall back to Option #1.
To illustrate, suppose we have this Haskell code:
class Functor f where | |
fmap :: (a -> b) -> f a -> f b | |
instance Functor [] where | |
fmap = map |
type Box<'T1,'T2> = { Value : obj } | |
module List = | |
let Box (list: list<'T>) : Box<list<obj>,'T> = { Value = list } | |
let Unbox (x: Box<list<obj>,'T>) : list<'T> = x.Value :?> _ | |
type IFunctor<'T> = | |
abstract member Map : ('T1 -> 'T2) -> Box<'T,'T1> -> Box<'T,'T2> | |
let ListFunctor = | |
{ | |
new IFunctor<list<obj>> with | |
member this.Map f x = | |
List.Box << List.map f << List.Unbox <| x | |
} |
As you can see, this approach (1) has a run-time cost and (2) gets annoying very quickly as the complexity of the code grows.
It is sad.
No comments:
Post a Comment