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

      

Play at dotnetfiddle

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 #?

+3


source to share


3 answers


How about annotating xform

explicitly?



   [<GeneralizableValue>]
   let xform<'t> : Reducer<'t, _, _> = map (fun i -> i + 9) >> map (fun a -> a * 5) >> map (fun s -> s + 1)

      

+4


source


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.

+4


source


As suggested above and in the error message itself, can you add the arguments explicitly?

let xform x = x |> map ...

      

F # plays so well with precise loose approaches

+3


source







All Articles