Статическое приведение между типами для общих вложенных записей - PullRequest
3 голосов
/ 18 мая 2019

Вложенная запись F # с параметром универсального типа, как мне статически приводить между типами во вложенной структуре, эквивалентными обходу и выполнению 'T |> 'K, например, float |> int?

В настоящее время я наивно прохожу вложенные записи иявное преобразование типа с помощью from:float |> to:int или эквивалентно int(from).Однако это не очень красиво.

type Person<'T> = {Id : int; Value : 'T}
type Family<'T> = {Id : 'T; People : seq<Person<'T>>}

let fam1 = {Id = 1.0; People = [{Id = 1.1; Value = 2.9}; {Id = 1.2; Value = 4.4}]} : Family<float>
let fam2 = {Id = 2.0; People = [{Id = 2.1; Value = 3.9}; {Id = 2.2; Value = 5.4}]} : Family<float>

let partyFloat = seq{ yield fam1; yield fam2}

// In general, how to do this from a type T to a type K where conversion using T |> K will work
let partyInt : seq<Family<int>> = partyFloat

Как статически и / или лениво конвертировать в seq<Family<int>>?В моем случае с реальным миром у меня есть тип DiffSharp D, который можно преобразовать в число с плавающей точкой с помощью D |> float или float(D).

Ответы [ 2 ]

3 голосов
/ 18 мая 2019

Нет волшебного способа разыгрывать внутренности типов, вы должны написать свой собственный.

Идиоматично для F # и функционального программирования в целом (и я лично рекомендую это тоже) писать маленькие функции для простых преобразований данных, а затем собирать их вместе:

let mapPerson f p = { Id = p.Id; Value = f p.Value }
let mapFamily f fm = { Id = f fm.Id; People = Seq.map (mapPerson f) fm.People }
let mapParty f = Seq.map (mapFamily f)

let partyInt = mapParty int partyFloat

Ноконечно, вы можете сделать это за один большой беспорядок:

let partyInt = 
    partyFloat
    |> Seq.map (fun fm -> 
        { Id = int fm.Id
          People = 
             fm.People
             |> Seq.map (fun p ->
                 { Id = p.Id; Value = int p.Value }
             )
        }
    )
0 голосов
/ 18 мая 2019

Похоже, вы запрашиваете ковариацию, т.е. это должно скомпилироваться

let vs : obj list = ["1"; "2"]

F # не поддерживает ковариацию (или контравариантность) и, вероятно, никогда не поддержит. C # делает так, чтобы вы могли написать что-то вроде этого

using System.Collections.Generic;

interface IPerson<out T>
{
  int Id { get; }
  T Value { get; }
}

interface IFamily<out T>
{
  int Id { get; }
  IEnumerable<IPerson<T>> Members { get; }
}

static class Program
{
  static IFamily<string> CreateFamily()
  {
    return null;
  }
  static void Main(string[] args)
  {
    IFamily<string> familyOfString = CreateFamily();
    IFamily<object> familyOfObject = familyOfString;
  }
}

Тем не менее, существует функциональная модель, которая может помочь нам, называемая полиморфными линзами.

Learning curve of popular haskell concepts (Изображение из темы Reddit: https://www.reddit.com/r/haskell/comments/2qjnho/learning_curves_for_different_programming/)

Раньше я думал, что полиморфные линзы невозможны в F # из-за отсутствия типов более высокого ранга. Однако там есть скрытый драгоценный камень: http://www.fssnip.net/7Pk

Веса Карвонен (IIRC, он также отстает от гопака, так что он довольно крут) реализует полиморфные линзы в F #, используя несколько довольно интересных трюков.

Затем мы можем достаточно просто отобразить внутренние значения неизменяемой структуры.

let input : Family<int> = 
  {
    Id      = 1
    Members = [{ Id = 10; Value = 123}; { Id = 11; Value = 456}]
  }
printfn "%A" input

let output : Family<string> =
  input
  |> over Family.membersL (overAll Person.valueL ((+) 1 >> string))
printfn "%A" output

Полный исходный код

// ----------------------------------------------------------------------------
// The code below taken from: http://www.fssnip.net/7Pk
//  by Vesa+Karvonen - http://www.fssnip.net/authors/Vesa+Karvonen
// ----------------------------------------------------------------------------

type LensFunctor<'a> =
  | Over of 'a
  | View
  member t.map a2b =
    match t with
     | Over a -> Over (a2b a)
     | View -> View

type Lens<'s,'t,'a,'b> = ('a -> LensFunctor<'b>) -> 's -> LensFunctor<'t>

module Lens =
  let view l s =
    let r = ref Unchecked.defaultof<_>
    s |> l (fun a -> r := a; View) |> ignore
    !r

  let over l f =
    l (f >> Over) >> function Over t -> t | _ -> failwith "Impossible"
  let set l b = over l <| fun _ -> b
  let (>->) a b = a << b

  let lens get set = fun f s ->
    (get s |> f : LensFunctor<_>).map (fun f -> set f s)

  let fstL f = lens fst (fun x (_, y) -> (x, y)) f
  let sndL f = lens snd (fun y (x, _) -> (x, y)) f

// ----------------------------------------------------------------------------
// The code above taken from: http://www.fssnip.net/7Pk
//  by Vesa+Karvonen - http://www.fssnip.net/authors/Vesa+Karvonen
// ----------------------------------------------------------------------------

  let overAll l f = List.map (over l f)

open Lens

type Person<'T> = { Id : int; Value : 'T }

module Person =
  let idS    i p          = { p with Id = i     }
  let valueS v { Id = i } = { Id = i; Value = v }

  let idL    f = lens (fun {Id    = i } -> i) idS    f
  let valueL f = lens (fun {Value = v } -> v) valueS f

type Family<'T> = { Id : int; Members : Person<'T> list }

module Family =
  let idS      i f          = { f with Id = i     }
  let membersS m { Id = i } = { Id = i; Members = m }

  let idL      f = lens (fun {Id      = i } -> i) idS      f
  let membersL f = lens (fun {Members = m } -> m) membersS f


[<EntryPoint>]
let main argv = 
  let input = 
    {
      Id      = 1
      Members = [{ Id = 10; Value = 123}; { Id = 11; Value = 456}]
    }
  printfn "%A" input

  let output =
    input
    |> over Family.membersL (overAll Person.valueL ((+) 1 >> string))
  printfn "%A" output

  0
...