5

Minęło trochę czasu odkąd kodowane SML, i natknąłem się na ten problem, który brzmi prosto, ale mam psychicznego bloku z rozwiązywaniem:OCaml odpowiednik javascript „zastosuj”

Napisz funkcję, która przyjmuje w funkcji f ze zmienną liczbą argumentów, które zwracają wartość boolowską (tj. f jest typu 'a -> 'b -> 'c -> ... -> bool) i zwracają funkcję g, która reprezentuje negację f (tj. (f x1 x2 .. xn) == not (g x1 x2 .. xn) dla wszystkich poprawnych zestawów parametrów).

To był inspirowany przez następujący blok kodu, który rozwiązuje ten problem w JavaScript:

function negate(func) { 
    return function() { 
    return !func.apply(null, arguments); 
    }; 
} 

(od http://eloquentjavascript.net/1st_edition/chapter6.html)

Jednak nie widzę sposobu, aby zaimplementować to w SML (The słowo kluczowe "arguments" lub odpowiednik nie jest dostępne) ze względu na fakt, że funkcja f nie ma zadanej liczby argumentów. Znalazłem linki, które mówią o radzeniu sobie z funkcjami ze zmienną liczbą argumentów (takich jak https://blogs.janestreet.com/variable-argument-functions/), ale chciałbym wiedzieć, czy istnieje prostszy/bardziej "naturalny" sposób radzenia sobie z tym konkretnym problemem.

+3

Podstawowym problemem jest to, że JavaScript nie ma systemu typu, a OCaml ma. Zaczynając od nienaturalnych wymagań, ciężko jest dotrzeć do "naturalnego" kodu OCaml. Co jest warte, nie uważam tego rodzaju kodu JavaScript za "elokwentny". Lepszym terminem jest "koszmarny". Tylko moja osobista opinia. –

+2

"JavaScript nie ma systemu typu"? 'alert (1 ===" 1 ")' mówi inaczej, ale dlaczego typ argumentu ma znaczenie dla routera arytycznego? OP: możesz stworzyć "piramidę arytmetyczną" i obsługę ręczną na poziomie 0-5, co jest wystarczające dla większości programów. nie tak fajne, ale ostatecznie prawdopodobnie szybciej do wykonania w każdym razie. Wersja js będzie lepiej wyłączyć przy użyciu funkcji bind() i/lub Function.prototype, aby uczynić make negate() metodą funkcji zamiast używania wrappera anon dla każdej personalizacji. – dandavis

Odpowiedz

4

Jestem programistą JavaScript i zawsze utrzymywałem, że variadic arguments are harmful. Jeśli nie mamy funkcji variadycznych w JavaScript (po prostu trzymaj się z dala od obiektu arguments), to każda funkcja w JavaScript nadająca się do zastosowania w systemie typu Hindley Milner (minus funkcje specyficzne dla API, takie jak funkcje DOM) może być łatwo przekształcona w równoważną funkcję w OCaml.

Co to jest odpowiednik OCaml funkcji apply?Uważam, że to normalne aplikacji funkcja:

let apply f x = f x (* equivalent of apply in JavaScript *) 

Jak to normalne aplikacji funkcja równoważna do funkcji w JavaScript apply? Rozważmy:

let s f g x = f x (g x) (* the S combinator from the SKI combinator calculus *) 

Ta funkcja będzie napisany w JavaScript, co następuje:

var s = function (f) { 
    return function (g) { 
     return function (x) { 
      return f(x)(g(x)); 
     }; 
    }; 
}; 

Należy pamiętać, że każda definicja funkcji i wywołanie funkcji jest wyraźnie napisane w formie curry.

Jest to różnica pomiędzy JavaScript i SML:

  1. W SML wszystkie funkcje są domyślnie curry i trzeba być wyraźnie uncurry im.
  2. W JavaScript wszystkie funkcje są domyślnie nieskrępowane i należy je wyraźnie przekierować.

Przyjrzyjmy się więc nieskomplikowanym wariantom kombinatora S. Po pierwsze, OCaml:

let s (f, g, x) = f (x, g (x)) (* sml convention is to use uncurried functions *) 

Równowartość w JavaScript:

var s = function (f, g, x) { 
    return f(x, g(x)); 
}; 

Zauważ, że normalne stosowanie funkcja jest taka sama zarówno w SML i JavaScript. Dla curry funkcji:

let result = s f g x (* equivalent to `((s f) g) x` *) 

Równowartość w JavaScript:

var result = s(f)(g)(x); 

dla funkcji uncurried:

let result = s (f, g, x) 

Równowartość w JavaScript:

var result = s(f, g, x); 

Więc co zFunkcja? Jak to jest równoważne z aplikacją normalnej funkcji?

W SML, można to zrobić:

let args = (f, g, x) (* args is a tuple *) 

let result = s args (* normal function application *) 

Równowartość w JavaScript jest:

var args = [f, g, x];   // args is an array 

var result = s.apply(null, args); // normal function application 

Jak widać krotki w SML są równoważne do tablic w JavaScript. Tablice w JavaScript są wszechstronne. Mogą być używane jako listy lub krotki, w zależności od kontekstu.

Parametr args podany na apply może być dowolnym obiektem tablicowym i traktowany jest jako pojedynczy argument krotki. Każda funkcja w JavaScript może być traktowana jako funkcja pojedynczego argumentu. Funkcje wieloparametrowe w JavaScript mogą być traktowane jako funkcja argumentu tuple z jednym parametrem. Funkcja JavaScript jest tylko specjalną formą aplikacji o normalnym działaniu.

Co to oznacza?Rozważyć:

var negate = function (f) { 
    return function() { 
     return !f.apply(null, arguments); 
    }; 
}; 

Jeżeli rozważymy arguments być ukryte parametrów funkcji wewnętrznej następnie odpowiednikiem wyżej funkcje w SML jest:

let negate f = fun arguments -> not (f arguments) (* arguments is explicit *) 

to można uprościć do:

let negate f x = not (f x) 

Można teraz powiedzieć, że będzie to działać tylko dla funkcji pojedynczego argumentu. Nie o to chodzi. Typ podpis negate jest:

val negate : ('a -> bool) -> 'a -> bool 

Stąd, może pracować dla każdego typu 'a tym krotek. Jest to odpowiednik JavaScriptu, w którym funkcje wieloparametrowe są tylko jednoparametrowymi funkcjami argumentu tuple.

Wreszcie, jedynym prawdziwym problemem jest przekształcenie funkcji curry w funkcje nieskomplikowane, aby można je było uzyskać pod negate. Niestety, nie ma ogólnego sposobu na uwolnienie funkcji w OCaml. Więc trzeba rodzinę funkcje uncurry curry funkcji wielu arities:

let uncurry2 f (x, y) = f x y 

let uncurry3 f (x, y, z) = f x y z 

      . 
      . 
      . 
      . 

Po negując funkcje można curry je z powrotem. Jednak podobnie jak w przypadku uncurry, nie ma możliwości generalnie funkcji curry. Stąd ponownie trzeba rodzinę curry funkcji:

let curry2 f x y = f (x, y) 

let curry3 f x y z = f (x, y, z) 

     . 
     . 
     . 
     . 

Jedynym sposobem, aby utworzyć rodzajowe curry lub uncurry funkcji jest wykorzystanie dynamicznie wpisywanych języków (jak Lisp lub JavaScript) lub zależny wpisywanych języków (jak Idris lub Agda) . System typu OCaml (system typu Hindley Milner) jest zbyt restrykcyjny, aby umożliwić takie funkcje.

+0

Nie powinieneś robić ".apply (this, arguments)", aby naprawdę przekazać * wszystkie * argumenty funkcji? :-) – Bergi

+0

@Bergi Nie wiedziałbym, jak to przekonwertować na OCaml, ale tak, najlepiej powinieneś. = P –

+1

Przypuszczam, że jest to coś w stylu 'apply f (self, x)' - krotka z odbiornika i rzeczywiste "argumenty". Kiedy więc będziemy używać naszych funkcji JS, możemy dać im dodatkowy parametr 'self', jak to mają w Pythonie. – Bergi

2

Funkcja, która przyjmuje kilka argumentów w ocaml, jest w rzeczywistości funkcją, która przyjmuje jeden argument i zwraca inną funkcję. Jest curry.

Co chcesz zrobić jest możliwe uncurried funkcji, czyli funkcji, które mają tylko jeden argument (który może być krotka):

Na przykład, chcesz f : (a * b * c) -> bool zamiast f : a -> b -> c -> bool. Ale musisz "ręcznie" przekształcić swoje funkcje.

Możesz mieć odpowiednie funkcje, takie jak let uncurry f (x,y) = f x y, ale to tylko transportuje problem, ponieważ musisz to zrobić dla dowolnej liczby argumentów.

Może można negować funkcje, które pobierają argumenty jako argumenty. Mam na myśli, że nie znam szczegółów tego, co próbujesz zrobić.

+0

Nie próbując robić nic szczególnego. Ciekawe, czy coś można zrobić bez modyfikacji samych funkcji. Myślę że nie? – user2258552

+1

Nie. Nie jest to możliwe w ogólny sposób, w jaki go opisałeś. Typ argumentów może być polimorficzny, liczba argumentów nie może (co jest tylko niewielką niedogodnością opisaną przez Theo). – lambdapower

0

To nie jest trudne, ale musisz napisać (i na stronach połączeń, wybierz) wyraźną definicję dla każdego arith ręcznie, ponieważ OCaml brakuje niezbędnego mechanizmu do abstrakcji podobne definicje różnych arities.

Należy pamiętać, że jest to wzór, który już istnieje w kodzie OCaml: patrz List.map(2), List.iter(2) itp

Definicje może wyglądać tak:

let negate f = (fun a -> not (f a)) 
let negate2 f = (fun a b -> not (f a b)) 
let negate3 f = (fun a b c -> not (f a b c)) 
(* etc *) 

pamiętać, że systemy typu, które umożliwiają tego rodzaju polimorfizm jest możliwy: w rzeczywistości rakieta może być w stanie wyrazić tę samą definicję.

+0

Moje pytanie dotyczyło skonstruowania ogólnie funkcji negowania - ponieważ przekazywane do niej funkcje mogą mieć dowolną liczbę argumentów, to rozwiązanie nie zadziałałoby. – user2258552