Bir uygulayıcı olarak neden Haskell'i önemsemeliyim? Monad nedir ve neden ona ihtiyacım var? [kapalı]


9

Çözdükleri problemi alamıyorum.



2
Bence bu düzenleme biraz aşırı. Sorunuzun aslında iyi bir soru olduğunu düşünüyorum. Sadece bazı bölümleri biraz ... tartışmacı idi. Bu muhtemelen sadece göremediğiniz bir şeyi öğrenmeye çalışmanın hayal kırıklığının sonucudur.
Jason Baker

@SnOrfus, soruyu piç eden kişiydim. Düzgün düzenlemek için çok tembeltim.
İş,

Yanıtlar:


34

Monadlar ne iyi ne de kötüdür. Onlar sadece. Programlama dillerinin diğer yapıları gibi sorunları çözmek için kullanılan araçlardır. Bunların çok önemli bir uygulaması, tamamen işlevsel bir dilde çalışan programcılar için hayatı kolaylaştırmaktır. Fakat işlevsel olmayan dillerde yararlıdırlar; sadece insanlar nadiren bir Monad kullandıklarını fark ederler.

Monad nedir? Bir Monad'ı düşünmenin en iyi yolu bir tasarım kalıbıdır. I / O durumunda, muhtemelen küresel devletin aşamalar arasında geçmekte olan yüceltilmiş bir boru hattından biraz daha fazlası olduğunu düşünebilirsiniz.

Örneğin, yazdığınız kodu alalım:

do
  putStrLn "What is your name?"
  name <- getLine
  putStrLn ("Nice to meet you, " ++ name ++ "!")

Burada gözle görülenden çok daha fazlası var. Örneğin, fark edeceksiniz putStrLnaşağıdaki imzası vardır: putStrLn :: String -> IO (). Bu neden?

Şöyle düşünün: stdout ve stdin'in okuyabildiğimiz ve yazabileceğimiz tek dosyalar olduğunu varsayalım (basitlik uğruna). Zorunlu bir dilde bu sorun değil. Fakat işlevsel bir dilde, küresel durumu değiştiremezsiniz. İşlev, bir değer (veya değerler) alan ve bir değer (veya değerler) döndüren bir şeydir. Bunun bir yolu, küresel durumu her işleve giren ve çıkan değer olarak kullanmaktır. Böylece ilk kod satırını şuna çevirebilirsiniz:

global_state <- (\(stdin, stdout) -> (stdin, stdout ++ "What is your name?")) global_state

... ve derleyici, öğesinin ikinci öğesine eklenen her şeyi yazdırmayı bilirdi global_state. Şimdi seni tanımıyorum ama böyle programlamaktan nefret ederim. Bunu kolaylaştırmanın yolu Monad'ları kullanmaktı. Bir Monad'da, bir eylemden diğerine bir tür durumu temsil eden bir değer iletirsiniz. Bu nedenle putStrLnbir dönüş türü vardır IO (): yeni küresel durumu döndürüyor.

Öyleyse neden umursuyorsun? Eh, fonksiyonel programlamanın zorunlu programa göre avantajları birkaç yerde ölümle tartışıldı, bu yüzden genel olarak bu soruya cevap vermeyeceğim (ancak fonksiyonel programlama için durumu duymak istiyorsanız bu makaleye bakın ). Bu özel durum için Haskell'in neyi başarmaya çalıştığını anlamanız yardımcı olabilir.

Birçok programcı Haskell'in zorunlu kod yazmasını veya yan etkileri kullanmasını engellemeye çalıştığını düşünüyor. Bu tam olarak doğru değil. Bunu şu şekilde düşünün: Zorunlu bir dil, varsayılan olarak yan etkilere izin veren, ancak gerçekten istiyorsanız (ve gerektiren bazı çarpımlarla uğraşmak istiyorsanız) fonksiyonel kod yazmanıza izin veren bir dildir. Haskell varsayılan olarak tamamen işlevseldir, ancak gerçekten isterseniz zorunlu bir kod yazmanıza izin verir (programınız faydalı olacaksa bunu yaparsınız). Mesele, yan etkileri olan kod yazmayı zorlaştırmak değildir. Yan etkilere sahip olduğunuzdan emin olmanız gerekir (bunu uygulayan tip sistemi ile).


6
Bu son paragraf altındır. Bunu biraz ayıklamak ve yorumlamak için: "Zorunlu bir dil, varsayılan olarak yan etkilere izin veren, ancak gerçekten isterseniz işlevsel kod yazmanıza izin veren bir dildir. İşlevsel bir dil, varsayılan olarak tamamen işlevseldir, ancak zorunlu kod yazmanıza izin verir eğer gerçekten istiyorsan. "
Frank Shearar

Bağlandığınız makalenin başlangıçta "işlevsel programlamanın bir sonucu olarak değişmezlik" fikrini özellikle reddettiğini belirtmek gerekir .
Mason Wheeler

@MasonWheeler: Bu paragrafları, değişmezliğin önemini göz ardı etmek değil, işlevsel programlamanın üstünlüğünü göstermek için ikna edici bir argüman olarak reddetmek olarak okudum . Aslında, gotomakalede biraz sonra (yapılandırılmış programlama için bir argüman olarak) ortadan kaldırılması konusunda da aynı şeyi söylüyor ve "meyvesiz" gibi argümanları karakterize ediyor. Yine de hiçbirimiz gizlice gotogeri dönmek istemiyoruz . Basitçe, gotoonu yaygın olarak kullanan insanlar için gerekli olmadığını iddia edemezsiniz.
Robert Harvey

7

Isıracağım!!! Monadlar kendi başlarına Haskell için gerçekten bir neden değildir (Haskell'in ilk sürümlerinde bile yoktu).

Sorunuz biraz "sözdizimine baktığımda çok sıkıldım.

Haskell programcısının evrimi bir şaka, ciddiye alınması gerekmiyor.

Haskell'deki bir programın amacı için bir Monad, Monad tip sınıfının bir örneğidir, yani, belirli bir küçük operasyon setini desteklemek için gerçekleşen bir tiptir. Haskell, Monad tip sınıfını uygulayan tipler, özellikle sözdizimsel destek için özel desteğe sahiptir. Pratik olarak bunun sonucu, "programlanabilir yarı kolon" olarak adlandırılan şeydir. Bu işlevselliği Haskell'in diğer özelliklerinden bazılarıyla birleştirdiğinizde (birinci sınıf işlevler, varsayılan olarak tembellik), belirli şeyleri geleneksel olarak dil özellikleri olarak kabul edilen kütüphaneler olarak uygulama yeteneğidir. Örneğin, bir istisna mekanizması uygulayabilirsiniz. Süreklilikler ve eşgüdümler için bir kütüphane olarak destek uygulayabilirsiniz. Haskell, dilin değişken değişkenler için desteği yok:

"Belki / Kimlik / Güvenli Bölünme monadları ???" Belki monad, bir kütüphane olarak istisna işlemeyi (çok basit, sadece bir istisna) nasıl uygulayabileceğinize bir örnektir.

Haklısınız, mesaj yazmak ve kullanıcı girişini okumak çok benzersiz değil. IO "özellik olarak monad" ın berbat bir örneğidir.

Ancak yinelemek için, dilin geri kalanından ayrı olarak tek başına bir "özellik" (örn. Monads) hemen yararlı görünmüyor (C ++ 0x'un yeni bir büyük özelliği, değer referanslarıdır, alabileceğiniz anlamına gelmez. sözdizimi sizi sıkıyor ve mutlaka yardımcı programı görmek çünkü onları C ++ bağlamı dışında. Bir programlama dili, bir grup özelliği bir kovaya atarak elde ettiğiniz bir şey değildir.


Aslında, haskell, ST monad aracılığıyla değişebilir değişkenleri destekliyor (dilin kendi kurallarına göre oynayan birkaç garip saf olmayan sihirli bölümden biri).
sara

4

Programcılar tüm programları yazar, ancak benzerlikler burada biter. Programcıların çoğu programcının hayal edebileceğinden çok daha farklı olduğunu düşünüyorum. Statik değişken yazma ve yalnızca çalışma zamanı türleri, komut dosyası derleme vs, C stili ve nesne yönelimi gibi uzun soluklu bir "savaş" yapın. Bir kampın daha düşük olduğunu rasyonel olarak tartışmanın imkansız olduğunu göreceksiniz, çünkü bazıları anlamsız veya hatta düpedüz benim için kullanılamayan gibi görünen bazı programlama sistemlerinde mükemmel kodu çalıyor.

Bence farklı insanlar sadece farklı düşünüyorlar ve eğer sözdizimsel şeker ya da özellikle kolaylık sağlamak için var olan ve aslında önemli bir çalışma süresi maliyetine sahip soyutlamalardan etkilenmediyseniz, elbette bu dillerden uzak durun.

Ancak en azından vazgeçtiğiniz kavramları tanımanızı tavsiye ederim. Lambda ifadeleriyle ilgili büyük bir şeyin ne olduğunu gerçekten anladıkları sürece , tamamen saf-saf-C olan birine karşı hiçbir şeyim yok . Çoğu kişinin hemen bir hayran olmayacağından şüpheleniyorum, ama en azından lambdaslarla çözülmesi çok daha kolay olan mükemmel bir sorun bulduğunda akıllarının arkasında olacaklar.

Ve her şeyden önce, fanboy konuşması, özellikle ne hakkında konuştuklarını gerçekten bilmeyen insanlar tarafından rahatsız edilmekten kaçının.


4

Haskell, Referans Şeffaflığı uygular : aynı parametreler verildiğinde, bu işlevi kaç kez çağırırsanız arayın, her işlev her zaman aynı sonucu döndürür.

Bu, örneğin, Haskell'de (ve Monad'lar olmadan) rastgele bir sayı üreteci uygulayamayacağınız anlamına gelir. C ++ veya Java'da, rasgele üretecin ara "tohum" değerini depolayarak global değişkenleri kullanarak bunu yapabilirsiniz.

Haskell'de küresel değişkenlerin muadili Monadlardır.


Peki ... ya rasgele bir sayı üreteci isteseydiniz? Bu bir işlev de değil mi? Olmasa bile, kendime rastgele bir sayı üreteci nasıl alabilirim?
İş

@ İşin Bir monadın içinde rastgele bir sayı üreteci (temel olarak bir durum izleyici) yapabilir veya asla kullanılmaması gereken Haskell'in şeytanı olan unsafePerformIO'yu kullanabilirsiniz (ve rastgelelığı kullanırsanız muhtemelen programınızı kırarsınız) içeride!)
alternatif

Haskell'de, ya temel olarak RNG'nin şu andaki durumu olan bir 'RandGen' etrafından geçiyorsunuz. Böylece yeni bir rasgele sayı üreten işlev bir RandGen alır ve yeni RandGen ve üretilen sayı ile bir grup döndürür. Alternatif olarak, bir min ve maks değeri arasında rasgele sayıların bir listesini istediğiniz yeri belirtmektir. Bu, tembel olarak değerlendirilen sonsuz bir sayı akışını döndürür, böylece yeni bir rastgele sayıya ihtiyaç duyduğumuzda bu listeden geçebiliriz.
Qqwy

Aynı şekilde onları başka bir dilde alırsınız! bazı sözde rasgele sayı üreteci algoritması tutun ve sonra bir değer ile tohum ve "rastgele" sayıları dışarı haşhaş reap! Tek fark, C # ve Java gibi dillerin sistem saatini veya bunun gibi şeyleri kullanarak sizin için otomatik olarak PRNG'yi tohumlamasıdır. Bu ve haskell'de de "sonraki" numarayı almak için kullanabileceğiniz yeni bir PRNG almanız gerçeği, oysa C # / Java'da, tüm bunlar Randomnesnede değişken değişkenler kullanılarak dahili olarak yapılır .
sara

4

Eski bir soru ama gerçekten iyi bir soru, bu yüzden cevaplayacağım.

Monadları, nasıl yürütüldükleri üzerinde tam kontrole sahip olduğunuz kod blokları olarak düşünebilirsiniz: her bir kod satırının ne dönmesi gerektiği, yürütmenin herhangi bir noktada durması gerekip gerekmediği, her satır arasında başka bir işlem olup olmayacağı.

Monad'ların başka türlü zor olmasını sağlayan şeylere örnekler vereceğim. Bu örneklerin hiçbiri Haskell'de değil, çünkü Haskell bilgim biraz titrek, ama hepsi Haskell'in monadların kullanımına nasıl ilham verdiğinin örnekleri.

ayrıştırıcılar

Normalde, bir programlama dili uygulamak gibi bir tür ayrıştırıcı yazmak istiyorsanız, ya BNF belirtimini okumak ve ayrıştırmak için bir sürü döngüsel kod yazmak zorunda kalacaksınız ya da bir derleyici derleyici kullanmak zorunda kalacaksınız. Flex, Bison, yacc vb. gibi. Ama monad'larla Haskell'de bir çeşit "derleyici ayrıştırıcı" yapabilirsiniz.

Ayrıştırıcılar, monadlar veya yacc, bizon gibi özel amaçlı diller olmadan gerçekten yapılamaz.

Örneğin, IRC protokolü için BNF dil belirtimini aldım :

message    =  [ ":" prefix SPACE ] command [ params ] crlf
prefix     =  servername / ( nickname [ [ "!" user ] "@" host ] )
command    =  1*letter / 3digit
params     =  *14( SPACE middle ) [ SPACE ":" trailing ]
           =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]

nospcrlfcl =  %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
                ; any octet except NUL, CR, LF, " " and ":"
middle     =  nospcrlfcl *( ":" / nospcrlfcl )
trailing   =  *( ":" / " " / nospcrlfcl )

SPACE      =  %x20        ; space character
crlf       =  %x0D %x0A   ; "carriage return" "linefeed"

Ve F #'da (monad'ları destekleyen başka bir dil) yaklaşık 40 kod satırına kadar kırdı:

type UserIdentifier = { Name : string; User: string; Host: string }

type Message = { Prefix : UserIdentifier option; Command : string; Params : string list }

let space = character (char 0x20)

let parameters =
    let middle = parser {
        let! c = sat <| fun c -> c <> ':' && c <> (char 0x20)
        let! cs = many <| sat ((<>)(char 0x20))
        return (c::cs)
    }
    let trailing = many item
    let parameter = prefixed space ((prefixed (character ':') trailing) +++ middle)
    many parameter

let command = atLeastOne letter +++ (count 3 digit)

let prefix = parser {
    let! name = many <| sat (fun c -> c <> '!' && c <> '@' && c <> (char 0x20))   //this is more lenient than RFC2812 2.3.1
    let! uh = parser {
        let! user = maybe <| prefixed (character '!') (many <| sat (fun c -> c <> '@' && c <> (char 0x20)))
        let! host = maybe <| prefixed (character '@') (many <| sat ((<>) ' '))
        return (user, host)
    }
    let nullstr = function | Some([]) -> null | Some(s) -> charsString s | _ -> null
    return { Name = charsString name; User = nullstr (fst uh); Host = nullstr (snd uh) }
}

let message = parser {
    let! p = maybe (parser {
        let! _ = character ':'
        let! p = prefix
        let! _ = space
        return p
    })
    let! c = command
    let! ps = parameters
    return { Prefix = p; Command = charsString c; Params = List.map charsString ps }
}

F # 'ın monad sözdizimi Haskell ile karşılaştırıldığında oldukça çirkin ve muhtemelen bunu biraz geliştirebilirdim - ama eve götürmenin asıl amacı, ayrıştırıcı kodunun BNF ile aynı olmasıdır. Bu sadece monadlar (veya bir ayrıştırıcı jeneratör) olmadan çok daha fazla çalışma yapmakla kalmaz, aynı zamanda spesifikasyona neredeyse hiç benzemezdi ve bu nedenle hem okumak hem de korumak korkunç olurdu.

Özel çoklu görev

Normalde, çoklu görevin bir işletim sistemi özelliği olduğu düşünülür - ancak monad'larla, her bir talimat monadından sonra programın zamanlayıcıya kontrolü aktaracağı ve daha sonra yürütülecek başka bir monad seçeceği şekilde kendi zamanlayıcınızı yazabilirsiniz.

Bir adam oyun döngülerini kontrol etmek için bir "görev" monad yaptı (yine F #'da), böylece her şeyi her biri üzerinde hareket eden bir devlet makinesi olarak yazmak yerineUpdate() çağrıda tüm talimatları tek bir işlevmiş gibi yazabiliyordu. .

Başka bir deyişle, şöyle bir şey yapmak yerine:

class Robot
{
   enum State { Walking, Shooting, Stopped }

   State state = State.Stopped;

   public void Update()
   {
      switch(state)
      {
         case State.Stopped:
            Walk();
            state = State.Walking;
            break;
         case State.Walking:
            if (enemyInSight)
            {
               Shoot();
               state = State.Shooting;
            }
            break;
      }
   }
}

Şöyle bir şey yapabilirsiniz:

let robotActions = task {
   while (not enemyInSight) do
      Walk()
   while (enemyInSight) do
      Shoot()
}

SQL'den LINQ

LINQ to SQL aslında bir monad örneğidir ve Haskell'de benzer işlevler kolayca uygulanabilir.

Tüm bunları tam olarak hatırlamadığım için ayrıntılara girmeyeceğim, ancak Erik Meijer bunu oldukça iyi açıklıyor .


1

GoF modellerine aşina iseniz, monadlar, radyoaktif bir porsuk tarafından ısırılan steroidlere uygulanan Dekoratör deseni ve Oluşturucu deseni gibidir.

Yukarıda daha iyi cevaplar var, ancak gördüğüm bazı avantajlar:

  • monadlar çekirdek türünü değiştirmeden bazı çekirdek türlerini ek özelliklerle süsler. Örneğin, bir monad String'i "kaldırabilir" ve "isWellFormed", "isProfanity" veya "isPalindrome" gibi değerler ekleyebilir.

  • benzer şekilde, monadlar basit bir tipin bir toplama tipinde toplanmasına izin verir

  • Monadlar, işlevlerin bu üst düzey alana geç bağlanmasına izin verir

  • monad'lar, keyfi işlevlerin ve bağımsız değişkenlerin, rasgele bir veri türüyle, daha üst düzey alanda karıştırılmasına izin verir

  • Monadlar saf, durumsuz işlevlerin saf olmayan, durumsal bir temel ile harmanlanmasına izin verir, böylece sorunun nerede olduğunu takip edebilirsiniz

Java'da bir monadın bilinen bir örneği Liste'dir. String gibi bazı çekirdek sınıfları alır ve liste hakkında bilgi ekleyerek List'in monad alanına "kaldırır". Daha sonra bu alana get (), getFirst (), add (), empty (), vb. Gibi yeni işlevler bağlar.

Büyük ölçekte, bir program yazmak yerine, sadece büyük bir Builder (GoF deseni olarak) yazdığınızı ve sonunda build () yönteminin programın üretmesi gereken her türlü cevabı tükettiğini hayal edin. Ve orijinal kodu yeniden derlemeden ProgramBuilder yeni yöntemler ekleyebilirsiniz. Bu yüzden monadlar güçlü bir tasarım modelidir.

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.