Cost limitation issues
I experimented with the implementation of Clojure converters in F # and quickly ran into a terrible value constraint error.
The whole essence of converters should be composite. This is some sample code:
type Reducer<'a,'b,'c> = ('a -> 'b -> 'a) -> 'a -> 'c -> 'a module Transducers = [<GeneralizableValue>] let inline map proj : Reducer<'result,'output,'input> = fun xf -> fun result input -> xf result (proj input) let inline conj xs x = x :: xs let inline toList xf input = List.fold (xf conj) [] input let xform = map (fun i -> i + 9) >> map (fun a -> a * 5) //let xs = toList xform [1;2] // if you apply this, type will be fixed to 'a list // which makes xform unusable with eg 'a seq
GeneralizableValue
should have raised the value constraint, but does nothing. Your job is to compile this code without applying toList
(type inference will set the type to 'a list
, so you couldn't use the same xform with seq
) and without changing the xform's type (at least not in a way that it doesn't get complicated ). Is this just not possible in F #?
source to share
Why does the map
s annotation [<GeneralizableValue>]
affect what xform
is subject to the value constraint? (is map
already generic anyway, since it is defined by a lambda, also I don't see the dot of all inline
s).
If your requirements are:
-
xform
should be a generic but not explicitly annotated type function -
xform
defined by the operator application ((>>)
in this case)
then you're out of luck; Body is xform
not a generic expression (see §14.7 in the F # spec), so a value constraint applies here.
Also, I would say it makes sense. Imagine that the value constraint does not apply and that we changed the definition map
:
let map proj : Reducer<_,_,_> =
printfn "Map called!"
fun xf result input ->
xf result (proj input)
Now enter these definitions one by one:
let xform<'a> : Reducer<'a,int,int> = map (fun i -> i + 9) >> map (fun a -> a * 5)
let x1 = xform (+)
let x2 = xform (*)
let x3 = xform (fun s i -> String.replicate i s)
When do you expect to be printed "Map called!"
? Does the actual behavior match your expectations? I think it's a good thing that F # is forcing you to get out of the way of treating obscene values as generic values.
So, you won't get exactly what you want. But maybe there is another encoding that will work just as well for your use cases. If each reducer will have a common result type, you can do this instead:
type Reducer<'b,'c> = abstract Reduce<'a> : ('a -> 'b -> 'a) -> 'a -> 'c -> 'a
module Transducers =
let map proj =
{ new Reducer<_,_> with
member this.Reduce xf result input = xf result (proj input) }
let (>!>) (r1:Reducer<'b,'c>) (r2:Reducer<'c,'d>) =
{ new Reducer<_,_> with
member this.Reduce xf result input = (r1.Reduce >> r2.Reduce) xf result input }
let conj xs x = x :: xs
let toList (xf:Reducer<_,_>) input = List.fold (xf.Reduce conj) [] input
let xform = map (fun i -> i + 9) >!> map (fun a -> a * 5)
Unfortunately, you need to elevate each operator, for example (>>)
, to the level of a reducer before you can use it, but that at least works for your example as it is xform
no longer a generic value, the generated value with a generic method.
source to share