2016-12-13 68 views
5

czy istnieje możliwość stworzenia FMap dla zapisów tak, że można zastosować tę samą funkcję rejestrowania pól podobna bur różnych typówJak zdefiniować FMap na strukturze rekordu z F #

powiedzmy mam rekord typ pola Item i zapis X i funkcja transform

type Item<'a, 'b> = Item of 'a * 'b 

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i 
    Item (x, sprintf "%A" y) 

type X<'a> = { 
    y: Item<'a, int> 
    z: Item<'a, bool> 
} 
with 
    member inline this.fmap(f) = 
     { 
      y = f this.y 
      z = f this.z 
     } 

teraz linia z = f this.z skarży się, że dany typ powinien być Item<'a, int> ale jego typu Item<'a, bool>. Oczywiście, gdy inwertor typu
zdecydował, że funkcja f jest typu Item<'a, int> -> Item<...>, ale chcę, aby f był polimorficzny. Jak mogę to zrobić?

Hacki typu zła są mile widziane!

+1

Funkcja genericity działa tylko w definicji. Gubi się, gdy stajesz się "wartością" i zaczynasz ją omijać. Jeśli chcesz zachować ogólność, użyj interfejsu. –

+0

Więc f powinna być dowolną funkcją "a ->" a gdzie "może być ciągiem znaków lub boolem? Nie ma wielu funkcji, które mógłbym myśleć poza '' id''. – Gustavo

Odpowiedz

2

Oczywistym rozwiązaniem jest użycie bimap zamiast fmap a następnie wirte dwukrotnie funkcja w miejscu rozmówcy:

type Item<'a, 'b> = Item of 'a * 'b 

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i 
    Item (x, sprintf "%A" y) 

type X<'a> = { 
    y: Item<'a, int> 
    z: Item<'a, bool> 
} 

with 
    member inline this.bimap(f, g) = 
     { 
      y = f this.y 
      z = g this.z 
     } 

Inną alternatywą (tu jest zły typ Hack) to zamiast przekazywać funkcji, przechodzą co Nazywam "Invokable", której funkcja jest zawijana w typ za pomocą jednej metody o nazwie Invoke. Coś jak delegat, ale statyczne.

Oto przykład. Używam $ zamiast Invoke dla uproszczenia:

let inline fmap invokable ({y = y1; z = z1}) = {y = invokable $ y1; z = invokable $ z1} 


type Id = Id with 
    static member ($) (Id, Item (a,b)) = Item (id a, id b) 

type Default = Default with 
    static member ($) (Default, Item (a:'t,b:'u)) = 
     Item (Unchecked.defaultof<'t>, Unchecked.defaultof<'u>) 

let a = {y = Item ('1', 2); z = Item ('3', true) } 

let b = fmap Id a 
let c = fmap Default a 

Teraz problemem jest to, że nie można myśleć o wielu innych przydatnych funkcji. Czy możesz?

W przeciwnym razie, jeśli uczynić go bardziej ogólna:

type X<'a, 'b, 'c> = { 
    y: Item<'a, 'b> 
    z: Item<'a, 'c> 
} 

wtedy można na przykład użyć Invokable tak:

type ToList = ToList with static member ($) (ToList, Item (a,b)) = Item ([a], [b]) 

let d = fmap ToList a 
// val d : X<char list,int list,bool list> = {y = Item (['1'],[2]); 
             z = Item (['3'],[true]);} 

Zobacz także this related question. Przedstawiony tam przypadek jest prostszy, ale problem jest taki sam.

Również powiązany jest z this one.

+0

BTW Nie wiem, czy już zauważyłeś, ale większość funkcji polimorficznych w FsControl to Invokables;) – Gustavo

+0

Pierwsze podziękowania należą się Gustavo Fyodorowi i @kvb - myślę, że naprawdę dla mnie ten czas kliknął. Zatem ** rozwiązanie ** polega na stworzeniu pewnego rodzaju polimorficznego typu delegata zamiast funkcji func i niektórych powiązanych metod i wywołanie tej metody podczas wywoływania/stosowania, gdy propozycja gustavo jest nieco bardziej skondensowana. Jednak - czy nie wszyscy myślą, że to wszystko jest bardzo ciężkie? Mam na myśli popychanie funkcji polimorficznej jako pierwszorzędnego parametru funkcyjnego, nie jest to naprawdę coś szczególnego ezoterycznego!dlaczego inferrer typu interferrer ingeruje w takich przypadkach? Tylko pytanie retoryczne ;-) – robkuz

+0

@robkuz nie jest tylko wnioskiem typu F #. Domyślam się, że wszystkie języki, w których występuje hiperferencja typu Hindly-Miner, zachowują się tak samo w odniesieniu do tego. Nawet Haskell, ale jeśli przeczytasz pierwszy link, który ci wysłałem, wydaje się, że F # jest w tych przypadkach bardziej elastyczny niż Haskell;) – Gustavo

2

zgadzam się z @Fyodor że za pomocą interfejsu jest najczystszym rozwiązanie, jeśli chcesz wyrazić polimorficzny argument:

type Item<'a, 'b> = Item of 'a * 'b 

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i 
    Item (x, sprintf "%A" y) 

type ITransform<'a,'x> = abstract Apply : Item<'a,'b> -> Item<'x,'b> 

type X<'a> = { 
    y: Item<'a, int> 
    z: Item<'a, bool> 
} 
with 
    member inline this.fmap(f:ITransform<_,_>) = 
     { 
      y = f.Apply this.y 
      z = f.Apply this.z 
     } 
{ y = Item(1,2); z = Item(3,true) }.fmap 
    { new ITransform<_,_> with member __.Apply(Item(i,x)) = Item(i+1, x) } 
+0

Mam następujące pytanie na ten temat tutaj https://stackoverflow.com/questions/45088899/how-can-i-map-over-a-record-using-interfaces – robkuz

Powiązane problemy