Tür tabanlı değişmezlere işlevsel programlama yanıtı nedir?


9

Değişmez kavramının birçok programlama paradigmasında var olduğunun farkındayım. Örneğin, döngü değişmezleri OO, fonksiyonel ve prosedürel programlama ile ilgilidir.

Bununla birlikte, OOP'de bulunan çok yararlı bir tür, belirli bir türdeki verilerin değişmezidir. Başlığında "tip tabanlı değişmezler" diyorum. Örneğin, bir Fractiontipte bir numeratorve olabilir ve denominatordeğişmezi ile gcd'leri her zaman 1'dir (yani, fraksiyon azaltılmış bir formdadır). Bunu ancak türünün kapsüllenmesini sağlayarak, verilerin serbestçe ayarlanmasına izin vermeyerek garanti edebilirim. Buna karşılık, asla azaltılmış olup olmadığını kontrol etmek zorunda değilim, böylece eşitlik kontrolleri gibi algoritmaları basitleştirebilirim.

Öte yandan, Fractionbu garantiyi kapsülleme yoluyla sağlamadan sadece bir tür beyan edersem, bu tür üzerinde, fraksiyonun azaldığını varsayan herhangi bir işlevi güvenle yazamıyorum, çünkü gelecekte başka biri gelip bir yol ekleyebilir indirgenmemiş bir fraksiyonun elde edilmesi.

Genellikle, bu tür değişmezliğin olmaması aşağıdakilere yol açabilir:

  • Ön koşulların birden fazla yerde kontrol edilmesi / sağlanması gerektiğinden daha karmaşık algoritmalar
  • Bu tekrarlanan ön koşullar nedeniyle KURU ihlalleri aynı temel bilgiyi temsil eder (değişmezin doğru olması gerekir)
  • Derleme zamanı garantileri yerine çalışma zamanı hataları yoluyla ön koşulları zorunlu kılmak

Benim sorum şu: bu tür değişmeze işlevsel programlama cevap. Aşağı yukarı aynı şeyi başarmanın işlevsel-deyimsel bir yolu var mı? Yoksa fonksiyonel programlamanın faydaları daha az alakalı kılan bir yönü var mı?


birçok işlevsel dil bunu önemsiz bir şekilde yapabilir ... Scala, F # ve OOP ile güzel oynayan diğer diller, Ama Haskell de ... temel olarak türleri tanımlamanıza izin veren herhangi bir dil ve davranışları bunu destekler.
AK_

@AK_ F #'nin bunu yapabileceğinin farkındayım (IIRC olsa da küçük bir çember atlama gerektiriyor) ve Scala'nın bir başka paradigma dili olarak yapabileceğini tahmin etti. Haskell'in yapabileceği ilginç bir bağlantı var mı? Gerçekten aradığım şey, bir özellik sunan belirli diller yerine işlevsel-deyimsel cevaptır. Ama elbette, deyimsel olandan bahsetmeye başladığınızda işler oldukça bulanık ve öznel olabilir, bu yüzden onu sorudan çıkardım.
Ben Aaronson

Ön koşulun derleme zamanında kontrol edilemediği durumlarda, yapıcıyı kontrol etmek deyimseldir. Bir PrimeNumbersınıf düşünün . Her işlem için birden fazla yedeklilik kontrolü yapmak çok pahalı olacaktır, ancak derleme zamanında yapılabilecek bir tür test değildir. (Eğer çarpma demek, asal sayılar üzerinde gerçekleştirmek istiyoruz operasyonların bir sürü bir oluşturmuyor kapatılmasını yani sonuç muhtemelen asal garanti edilmez, (ı fonksiyonel programlama kendim bilmiyorum çünkü yorum olarak Gönderme)..
rwong

Görünüşte ilgisiz bir soru, ama ... Varsayımlar veya birim testleri daha mı önemli?
rwong

@rwong Evet, bazı güzel örnekler var. Aslında hangi nihai noktaya gittiğinizi% 100 net değilim.
Ben Aaronson

Yanıtlar:


2

OCaml gibi bazı işlevsel diller, soyut veri türlerini uygulamak için yerleşik mekanizmalara sahiptir ve bu nedenle bazı değişmezleri zorlar . Bu tür mekanizmalara sahip olmayan diller, değişmezleri zorlamak için kullanıcıya “halının altına bakmamaya” güvenir.

OCaml'de soyut veri türleri

OCaml'de modüller bir programı yapılandırmak için kullanılır. Bir modülün bir uygulaması ve imzası vardır , ikincisi modülde tanımlanan değerlerin ve türlerin bir özeti iken, birincisi gerçek tanımları sağlar. Bu, .c/.hC programcılarına aşina olan diptik ile gevşek bir şekilde karşılaştırılabilir .

Örnek olarak, Fractionmodülü şu şekilde uygulayabiliriz :

# module Fraction = struct
  type t = Fraction of int * int
  let rec gcd a b =
    match a mod b with
    | 0 -> b
    | r -> gcd b r

  let make a b =
   if b = 0 then
     invalid_arg "Fraction.make"
   else let d = gcd (abs a) (abs b) in
     Fraction(a/d, b/d)

  let to_string (Fraction(a,b)) =
    Printf.sprintf "Fraction(%d,%d)" a b

  let add (Fraction(a1,b1)) (Fraction(a2,b2)) =
    make (a1*b2 + a2*b1) (b1*b2)

  let mult (Fraction(a1,b1)) (Fraction(a2,b2)) =
    make (a1*a2) (b1*b2)
end;;

module Fraction :
  sig
    type t = Fraction of int * int
    val gcd : int -> int -> int
    val make : int -> int -> t
    val to_string : t -> string
    val add : t -> t -> t
    val mult : t -> t -> t
  end

Bu tanım artık şu şekilde kullanılabilir:

# Fraction.add (Fraction.make 8 6) (Fraction.make 14 21);;
- : Fraction.t = Fraction.Fraction (2, 1)

Herkes, yerleşik güvenlik ağını atlayarak doğrudan tip kesirinin değerlerini üretebilir Fraction.make:

# Fraction.Fraction(0,0);;
- : Fraction.t = Fraction.Fraction (0, 0)

Bunu önlemek için, türün somut tanımını şu şekilde gizlemek mümkündür Fraction.t:

# module AbstractFraction : sig
  type t
  val make : int -> int -> t
  val to_string : t -> string
  val add : t -> t -> t
  val mult : t -> t -> t
end = Fraction;;

module AbstractFraction :
sig
  type t
  val make : int -> int -> t
  val to_string : t -> string
  val add : t -> t -> t
  val mult : t -> t -> t
end

An oluşturmanın tek yolu işlevi AbstractFraction.tkullanmaktır AbstractFraction.make.

Şemadaki soyut veri türleri

Şema dili, OCaml ile aynı soyut veri türleri mekanizmasına sahip değildir. Kapsülleme elde etmek için kullanıcıya “halının altına bakmamaya” dayanır.

fraction?Şema'da, girdiyi doğrulama fırsatı veren değerleri tanımak gibi tahminleri tanımlamak gelenekseldir . Deneyimlerime göre, baskın kullanım, her kütüphane çağrısında girişi doğrulamak yerine, bir değer oluşturuyorsa kullanıcının girişini doğrulamasına izin vermektir.

Ancak, döndürülen değerlerin soyutlanmasını zorunlu kılmak için, stratejiler uygulandığında değeri veren bir kapanış döndürmek veya kütüphane tarafından yönetilen bir havuzdaki bir değere referans döndürmek gibi çeşitli stratejiler vardır - ancak uygulamada hiçbirini görmedim.


+1 Ayrıca, tüm OO dillerinin kapsüllemeyi zorlamadığını da belirtmek gerekir.
Michael Shaw

5

Kapsülleme, OOP ile gelen bir özellik değildir. Uygun modülerleştirmeyi destekleyen herhangi bir dilde vardır.

Kabaca Haskell'de bunu nasıl yapacağınız aşağıda açıklanmıştır:

-- Rational.hs
module Rational (
    -- This is the export list. Functions not in this list aren't visible to importers.
    Rational, -- Exports the data type, but not its constructor.
    ratio,
    numerator,
    denominator
    ) where

data Rational = Rational Int Int

-- This is the function we provide for users to create rationals
ratio :: Int -> Int -> Rational
ratio num den = let (num', den') = reduce num den
                 in Rational num' den'

-- These are the member accessors
numerator :: Rational -> Int
numerator (Rational num _) = num

denominator :: Rational -> Int
denominator (Rational _ den) = den

reduce :: Int -> Int -> (Int, Int)
reduce a b = let g = gcd a b
             in (a `div` g, b `div` g)

Şimdi, bir Rasyonel oluşturmak için, değişmezi zorlayan oran fonksiyonunu kullanırsınız. Veriler değişmez olduğu için, değişmezi daha sonra ihlal edemezsiniz.

Ancak bunun size bir maliyeti vardır: artık kullanıcının payda ve pay kullanımı ile aynı yapısöküm bildirimini kullanması mümkün değildir.


4

Bunu aynı şekilde yaparsınız: kısıtlamayı uygulayan bir kurucu yapın ve yeni bir değer oluşturduğunuzda bu kurucuyu kullanmayı kabul edin.

multiply lhs rhs = ReducedFraction (lhs.num * rhs.num) (lhs.denom * rhs.denom)

Ancak Karl, OOP'de yapıcıyı kullanmayı kabul etmek zorunda değilsiniz . Gerçekten?

class Fraction:
  ...
  Fraction multiply(Fraction lhs, Fraction rhs):
    Fraction result = lhs.clone()
    result.num *= rhs.num
    result.denom *= rhs.denom
    return result

Aslında, FP'de bu tür kötüye kullanma fırsatları daha azdır. Sen var çünkü değişmezlik, son yapıcısı koymak. İnsanların enkapsülasyonu, beceriksiz meslektaşlara karşı bir tür koruma olarak düşünmeyi veya iletişim kurma zorunluluğunu ortadan kaldırmayı diliyorum. Bunu yapmaz. Sadece kontrol etmeniz gereken yerleri sınırlar. İyi FP programcıları da kapsülleme kullanır. Sadece belirli türdeki modifikasyonları yapmak için birkaç tercih edilen fonksiyonu iletme şeklinde gelir.


Örneğin, C # 'da kod yazmak mümkündür (ve deyimsel), orada ne yaptığınıza izin vermez. Bence bir değişmezi uygulamaktan sorumlu tek bir sınıf ile herkes tarafından yazılan her işlev arasında, aynı değişmezi uygulamak zorunda olan belirli bir tür kullanan her şey arasında oldukça açık bir fark var.
Ben Aaronson

@ BenAaronson " Zorla " ve " değişmez " yayma arasında bir fark olduğunu fark edin .
rwong

1
+1. Bu teknik FP'de daha da güçlüdür, çünkü değişmez değerler değişmez; böylece türleri kullanarak "bir kez ve herkes için" şeyler kanıtlayabilirsiniz. Değişken nesnelerle bu mümkün değildir çünkü onlar için geçerli olan daha sonra doğru olmayabilir; yapabileceğiniz en iyi şey savunma durumunu nesnenin durumunu tekrar kontrol etmek.
Doval

@Doval Görmüyorum. Çoğu (?) Büyük OO dilinin değişkenleri değişmez hale getirmenin bir yolu olduğunu bir kenara bırakmak. OO var: Bir örnek oluşturmak, sonra benim işlev bu örneğin değerlerini değişmez uygun olabilir veya olmayabilir bir şekilde değiştirir. FP'de: Bir örnek oluştur, sonra işlevim değişmezle uyumlu olabilecek veya olmayabilecek şekilde farklı değerlere sahip ikinci bir örnek oluşturur. Değişmezliğin, değişmezimin türün tüm örnekleri için uygun olduğuna daha fazla güvenmemi nasıl sağladığını görmüyorum
Ben Aaronson

2
@BenAaronson Değişmezlik, tipinizi doğru bir şekilde uyguladığınızı kanıtlamanıza yardımcı olmaz (yani tüm işlemler belirli bir değişmezi korur.) Söylediğim, değerler hakkında gerçekleri yaymanıza izin vermesidir. Bazı koşulları (örneğin bu sayı çifttir) bir tipte (yapıcıda kontrol ederek) kodlarsınız ve üretilen değer, orijinal değerin koşulu karşıladığının kanıtıdır. Değişken nesnelerle mevcut durumu kontrol edersiniz ve sonucu bir boole'de tutarsınız. Bu boolean, nesne mutasyona uğramadıkça, durumun yanlış olması için iyidir.
Doval
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.