OOP'a ilginç bir bakış - sayfa 11

 
Georgiy Merts :

"Apotheosis", kendisinin nasıl çalıştığını söyleyemediği fxsaber'ın bir ifadesini düşünüyorum, sadece "kod tekrar tekrar test edildi ve çalışıyor" dedi. Bana göre bu olmamalı:

Bu kod, otfFilingType siparişinin doldurulup doldurulamayacağını kontrol eder ve strSymbol sembolünde mevcutsa, aksi takdirde doğrudur.


Nasıl çalıştığını tam olarak anlayamıyorum. Ve ben sadece fxsaber'ın otoritesine güveniyorum.

Belki biri açıklar?

Anlamak için, bu hantal ifadeyi geri dönüşten bileşenlere ayrıştırmak yeterlidir.

 
Igor Makanu :

"Son, araçları haklı çıkarır" örneğiniz

bu arada, bu kod sadece spagetti gibi görünüyor, ancak bunun şu anda kod tabanındaki muhtemelen en faydalı yazar olduğu ve ben de onun kodlarını olduğu gibi kullandığım gerçeği göz önüne alındığında, başkasının eleştirmesine izin verin

 
Andrei Trukhanovich :

bu arada, bu kod sadece spagetti gibi görünüyor, ancak bunun muhtemelen şu anda kod tabanındaki en yararlı yazar olduğu ve ben de onun kodlarını olduğu gibi kullandığım gerçeği göz önüne alındığında, başkasının eleştirmesine izin verin

Eleştiriden bahsetmiyorum, ne verebileceğini anlamaya çalışıyorum .... ama konuşmanın gösterdiği gibi ve @fxsaber açıkça itiraf etti - baş ağrısından başka bir şey değil


Not: Küçük canavarın orijinal versiyonunun daha görsel görünmesi kuvvetle muhtemeldir;)

 
Igor Makanu :

Not: Küçük canavarın orijinal versiyonunun daha görsel görünmesi kuvvetle muhtemeldir;)

ZIP'den alınmıştır (ilk sürümü içerir).

   static bool VirtualOrderSelect( const TICKET_TYPE Index, const int Select, const int Pool = MODE_TRADES )
  {
     return (VIRTUAL::SelectOrders ? VIRTUAL::SelectOrders. OrderSelect (Index, Select, Pool) :
           #ifdef __MQL5__
             #ifdef __MT4ORDERS__
               :: OrderSelect (Index, Select, Pool)
             #else // __MT4ORDERS__
               false
             #endif // __MT4ORDERS__
           #else // __MQL5__
             :: OrderSelect (Index, Select, Pool)
           #endif // __MQL5__
           );
  }

Ancak bundan sonra, giderek daha fazla işlevsellik gerekliydi ve kod yavaş yavaş etle kaplandı. Doldurma işlevi ile farklıydı. Orada hiçbir şey büyümedi, hemen yazıldı.

 
Vasiliy Sokolov :

Derleyicinin tam olarak nasıl yapacağını sadece kendisi bilir. Modern derleyicilerde yerleşik çarpıcı buluşsal yöntemler vardır. Ortalama kodlayıcıya uyum sağlarlar ve neye ihtiyacı olduğunu zaten daha iyi bilirler. Bir derleyici için yapılacak en iyi şey, kısa fonksiyonlarla basit, anlaşılır kod yazmaktır. Derleyicinin birçok düğüm fonksiyonundan oluşan kaynak kod grafiğini analiz ederek nihai programı oluşturması çok daha kolay ve verimlidir. Bu sadece performans üzerinde olumlu bir etkiye sahip olacaktır, çünkü gerekli işlevler doğru yerlerde sıralanacaktır.

Kesinlikle doğru.

MQL5 hakkında konuşursak, 10-20-30 yıl önceki "optimizasyon" yöntemlerini unutabiliriz. Mümkün olduğunca okunabilir kod yazmanız gerekir. Kalitenin ölçüsü, hacker çanları ve ıslıkları ve saf gösterişler değil, odur.

Niye ya?

Evet, çünkü derleyici 5-10 döngü kod yeniden sıralamasından geçecek, düzinelerce optimizasyon modelinin kullanımından bahsetmeden, grafiklerin sonlarını şaşırtıcı derecede net ve kısa bir şekilde gösterecektir.

MQL5 derleyicisi, insanların zil ve ıslıklarla +%2 hız yapma girişimlerini komik buluyor.


İlgilenenler için, matematiksel hesaplamalar dallar olmadan yapılır ve bir komut için (128 bit veri) aynı anda iki kök hesaplanır

Bu kod, aşağıdaki montajcı SSE koduna dönüşür:

         D1= sqrt ((X1-X)*(X1-X)+(Y1-Y)*(Y1-Y));
         D2= sqrt ((X2-X)*(X2-X)+(Y2-Y)*(Y2-Y));
         D3= sqrt ((X3-X)*(X3-X)+(Y3-Y)*(Y3-Y));
         D4= sqrt ((X4-X)*(X4-X)+(Y4-Y)*(Y4-Y));
         D5= sqrt ((X5-X)*(X5-X)+(Y5-Y)*(Y5-Y));
         D6= sqrt ((X6-X)*(X6-X)+(Y6-Y)*(Y6-Y));
         D7= sqrt ((X7-X)*(X7-X)+(Y7-Y)*(Y7-Y));
         D8= sqrt ((X8-X)*(X8-X)+(Y8-Y)*(Y8-Y));
        ...
         sqrtsd   xmm1, xmm1
        unpcklpd        xmm4, xmm4
        movapd  xmm3, xmmword ptr [rsp + 432 ]
        unpcklpd        xmm3, xmmword ptr [rsp + 384 ]
        subpd   xmm3, xmm4
        mulpd   xmm3, xmm3
        unpcklpd        xmm0, xmm0
        movapd  xmm5, xmmword ptr [rsp + 416 ]
        unpcklpd        xmm5, xmmword ptr [rsp + 400 ]
        subpd   xmm5, xmm0
        mulpd   xmm5, xmm5
        addpd   xmm5, xmm3
        sqrtpd   xmm8, xmm5
        movapd  xmm5, xmmword ptr [rsp + 464 ]
        subpd   xmm5, xmm4
        mulpd   xmm5, xmm5
        movapd  xmm7, xmm9
        subpd   xmm7, xmm0
        mulpd   xmm7, xmm7
        addpd   xmm7, xmm5
        movapd  xmm6, xmm10
        unpcklpd        xmm6, xmm11
        subpd   xmm6, xmm4
        movapd  xmm3, xmmword ptr [rsp + 368 ]
        unpcklpd        xmm3, xmmword ptr [rsp + 352 ]
        subpd   xmm3, xmm0
        movapd  xmm4, xmm8
        shufpd  xmm4, xmm4, 1
        sqrtpd   xmm5, xmm7
        mulpd   xmm6, xmm6
        mulpd   xmm3, xmm3
        addpd   xmm3, xmm6
        sqrtpd  xmm15, xmm3
        movapd  xmm0, xmm14
        unpcklpd        xmm0, xmmword ptr [rsp + 336 ]
        subpd   xmm0, xmm2
        mulpd   xmm0, xmm0
        movapd  xmm2, xmm0
        shufpd  xmm2, xmm2, 1
        addsd   xmm2, xmm0
        movapd  xmm0, xmm15
        shufpd  xmm0, xmm0, 1
         sqrtsd   xmm12, xmm2
Sonuçta bu bir sanat eseri. Montajcı komutunun 4 çağrısı için 8 kök hesaplanır. Bir aramada iki çift sayı hesaplandı.

  • Bir dizideki işlemler sırasında, çift -> tamsayı indeks dönüşümünde kontroller, dallanma ve kayıplarla her şey normal şekilde gider

  • Bu örnekte dizilerle çalışırken, performans üzerinde çok kötü bir etkisi olan sürekli bir FPU / ALU karışımı vardır.

  • Dinamik bir diziye erişimin optimizasyonu övgünün ötesinde mükemmeldir. Ancak FPU/ALU işlemlerini karıştırmak + çift çevirmek -> tamsayı + dallara ayırmak zaman kaybettirir

  • Genel sonuç: MQL5'teki matematik, mükemmel optimizasyon sayesinde kazanır. Kaybeden diziler değil, matematik kazanır.


     
    Georgiy Merts :

    Nedenmiş ?

    Tam tersi, iki "if" ile - "veya" operatöründen çok daha kolay.

    Açıkçası, önce bir koşulu izlemek ve yürütme durumunda işlevden ayrılmak ve ardından başka bir koşulu kontrol etmek ve yürütme durumunda da bırakmak daha kolaydır. Mantıksal bir "veya" ("ve" ile kolayca karıştırılabilen) aracılığıyla karmaşık bir koşulun sonucu olarak ne olduğunu anlamak ve her iki iade seçeneğini de takip etmek.

    Aşağıda "bunun gerekçesinin hata ayıklama olduğunu" okumak oldukça komik, çünkü bu, bu tür kodun çok daha anlaşılır olduğu anlamına geliyor (aksi halde neden hata ayıklamada?).

    "Apotheosis", kendisinin nasıl çalıştığını söyleyemediği fxsaber'ın bir ifadesini düşünüyorum, sadece "kod tekrar tekrar test edildi ve çalışıyor" dedi. Bana göre bu olmamalı:

    Bu kod, otfFilingType siparişinin doldurulup doldurulamayacağını kontrol eder ve strSymbol sembolünde mevcutsa, aksi takdirde doğrudur.


    Nasıl çalıştığını tam olarak anlayamıyorum. Ve ben sadece fxsaber'ın otoritesine güveniyorum.

    Belki biri açıklar?

    Bir keresinde oturdum ve aşamalı olarak ayırdım, bir parça kağıt ile bir kaleme ihtiyacım var gibi görünüyor)

    Bu analiz neden yararlıydı - Numaralandırmaların yapısı değişirse her şeyin bozulacağını anladım) çünkü. numaralandırma değerlerinin tamsayı oranları kullanılır (ki, numaralandırma fikrine göre, sadece kapsüllenmelidir). Bundan kendim kaçınmaya çalışıyorum, maksimum ilişki az ya da çok. WinAPI ile uğraşanlar için muhtemelen tanıdık gelecektir.

     
    Andrey Khatimlianskii :

    Anlamak için, bu hantal ifadeyi geri dönüşten bileşenlere ayrıştırmak yeterlidir.

    Evet, denedim. Ancak, sökmek için yeterli motivasyon yoktu ...

     
    Renat Fatkhullin :

    Kesinlikle doğru.

    MQL5 hakkında konuşursak, 10-20-30 yıl önceki "optimizasyon" yöntemlerini unutabiliriz. Mümkün olduğunca okunabilir kod yazmanız gerekir. Kalitenin ölçüsü, hacker çanları ve ıslıkları ve saf gösterişler değil, odur.

    Aynen öyle. Bu sonuca uzun zaman önce vardım. Ama derleyicinin daha iyisini yapacağını düşündüğüm için değil. Ve koddaki sorunların ana kaynağı kişinin kendisi olduğu için, yani kodun mümkün olduğunca basit ve şeffaf olması için mümkünse kodu yazmanız gerekir.

    Bunu "şeffaf bir şekilde" yapmanın imkansız olduğu durumlarda, neden böyle olduğu ve başka türlü değil, ayrıntılı yorumlar gereklidir.

    Pekala, derleyici... bunu yeterince verimli bir şekilde yapmasa bile, programın çalışmasının bazı yönlerini hesaba katmamanız nedeniyle potansiyel bir hatadan daha az sorun teşkil eder.

     
    fxsaber :

    Kod çok basit ve kısadır ( açıklama ). FP'ye yazarsanız, karşılaştırmak ilginç olacaktır.

    Rica ederim. FP stilinde C#:


    using System;
    using System.Linq;
    using System.Collections.Generic;
    using static FeesCalculator.FeesCalcs;
    using System.Text;
    using static System.Math;
    
    namespace FeesCalculator
    {
         public static class EnumerableExt
        {
             /// <summary>
             /// Call of function 'action' for each element if sequence (mapping).
             /// </summary>
             /// <typeparam name="T"></typeparam>
             /// <param name="seq">Enumerable sequence</param>
             /// <param name="action">Need action</param>
             public static void Map<T>( this IEnumerable<T> seq, Action<T> action)
            {
                foreach (var item in seq)
                    action(item);
            }
        }
         /// <summary>
         /// Инвестиционный результат
         /// </summary>
         public record AccountRecord
        {
             public double Investor { get; init; }
             public double Manager { get; init; }
             public double Summary { get; init; }
    
             public static AccountRecord operator *(AccountRecord r, double v)
            {
                 return new AccountRecord
                {
                    Investor = r.Investor * v,
                    Manager = r.Manager * v,
                    Summary = r.Summary * v
                };
            }
             public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(nameof(Investor)).Append( " = " ).Append(Investor.ToString( "F4" )).Append( ' ' );
                sb.Append(nameof(Manager)).Append( " = " ).Append(Manager.ToString( "F4" )).Append( ' ' );
                sb.Append(nameof(Summary)).Append( " = " ).Append(Summary.ToString( "F4" ));
                 return sb.ToString();
            }
        }
         /// <summary>
         /// Параметры оферты
         /// </summary>
         public record PammSet
        {
             /// <summary>
             /// Доходность за расчетный период
             /// </summary>
             public double Perfomance { get; init; }
             /// <summary>
             /// Вознаграждение управляющего
             /// </summary>
             public double Gift { get; init; }
             /// <summary>
             /// Количество расчетных периодов
             /// </summary>
             public int N { get; init; }
             /// <inheritdoc/>
             public override string ToString()
            {
                StringBuilder str = new StringBuilder();
                str.Append( "Доходность за расчетный период: " ).Append((Perfomance * 100.0 ).ToString( "F1" )).Append( "%\t\n" );
                str.Append( "Вознаграждение управляющего: " ).Append((Gift * 100.0 ).ToString( "F1" )).Append( "%\t\n" );
                str.Append( "Количество расчетных периодов: " ).Append(N);
                 return str.ToString();
            }
    
        }
         /// <summary>
         /// Формулы расчета
         /// </summary>
         public class FeesCalcs
        {
             /// <summary>
             /// Если управляющий снимает деньги в конце каждого расчетного периода
             /// </summary>
             /// <param name="Performance"></param>
             /// <param name="Gift"></param>
             /// <param name="N"></param>
             /// <returns></returns>
             public static AccountRecord Set1(PammSet set)
            {
                Func< double > investor = () => Pow( 1 + (set.Perfomance) * ( 1 - set.Gift), set.N);
                Func< double > manager = () => (investor() - 1 ) * set.Gift / ( 1 - set.Gift);
                AccountRecord ac = new AccountRecord
                {
                    Investor = investor(),
                    Manager = manager(),
                    Summary = investor() + manager()
                };
                 return ac;
            }
             /// <summary>
             /// Если ничего не делалось в расчетные периоды
             /// </summary>
             /// <param name="Performance"></param>
             /// <param name="Gift"></param>
             /// <param name="N"></param>
             /// <returns></returns>
             public static AccountRecord Set2(PammSet set)
            {
                Func< double > summary = () => Pow(set.Perfomance + 1 , set.N);
                Func< double > manager = () => (summary() - 1 ) * set.Gift;
                 double s = summary();
                 double d = manager();
                AccountRecord ac = new AccountRecord
                {
                    Summary = summary(),
                    Manager = manager(),
                    Investor = summary() - manager()
                };
                 return ac;
            }
             /// <summary>
             /// Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их
             /// </summary>
             /// <param name="Performance"></param>
             /// <param name="Gift"></param>
             /// <param name="N"></param>
             /// <returns></returns>
             public static AccountRecord Set3(PammSet set)
            {
                Func< double > summary = () => Pow(set.Perfomance + 1 , set.N);
                Func< double > investor = () => Pow( 1 + (set.Perfomance) * ( 1 - set.Gift), set.N);
                 return new AccountRecord
                {
                    Summary = summary(),
                    Investor = investor(),
                    Manager = summary() - investor()
                };
            }
             /// <summary>
             /// Сопостовление: описание - функция расчета
             /// </summary>
             public static readonly Dictionary< string , Func<PammSet, AccountRecord>> DescrToAccountRecord = new Dictionary< string , Func<PammSet, AccountRecord>>
            {
                { "Если управляющий снимает деньги в конце каждого расчетного периода (1)" , Set1 },
                { "Если ничего не делалось в расчетные периоды (2)" , Set2 },
                { "Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3)" , Set3 },
            };
        }
         class Program
        {
             static void Main( string [] args)
            {
                var _ = args.Select((a) => double .Parse(a)).Take( 3 ).ToList();
                PammSet pamm = new PammSet
                {
                    Perfomance = _[ 0 ]/ 100.0 ,
                    Gift = _[ 1 ]/ 100.0 ,
                    N = ( int )_[ 2 ]
                };
                Console.WriteLine($ "Данные для инвестиционного счета со следующими характеристиками:\n\n{pamm}\n" );
                Console.WriteLine( "Конечный результат для Инвестора, Управляющего и Суммарно:\n" );
                Func<KeyValuePair< string , Func<PammSet, AccountRecord>>, string > f = (kvp) => kvp.Key + ".\n" + kvp.Value(pamm).ToString() + "\n" ;
                Enumerable.Select(DescrToAccountRecord, f).Map((s) => Console.WriteLine(s));
                Console.WriteLine(pamm);
                Func< int , PammSet> toPamm = (n) => new PammSet { Perfomance = pamm.Perfomance, Gift = pamm.Gift, N = n };
                Func<PammSet, IEnumerable<AccountRecord>> toAccs = (n) => DescrToAccountRecord.Select((s) => s.Value(n));
                var accounts = Enumerable.Repeat( 1 , pamm.N).Select((a, b) => a + b).Select(toPamm).Select(toAccs);
                Func<AccountRecord, string > toString = (ar) => ar.Investor.ToString( "F2" ) + "\t" +
                                                               ar.Manager.ToString( "F2" ) + "\t" +
                                                               ar.Summary.ToString( "F2" ) + "\t" ;
                 int period = default ;
                accounts.Map
                (
                    (en) => 
                    {
                        Console.Write($ "{++period}:\t" );
                        en.Map((ar) =>
                            Console.Write(toString(ar)));
                        Console.WriteLine();
                    }
                );
            }
        }
    }
    

    FP elbette çok çarpıktır (FP kodlayıcısı çarpık kodlayıcıyı alt FP dilinde C# yazdığı için) Ama amaç modern OOP YaP'larda bunun FP'de mümkün olduğunu göstermektir. Sınırlı tabi ki bu F# veya Haskell değil ama kimse FP tarzında yazmayı yasaklamıyor. Değişmezlik, daha yüksek dereceli işlevler, kapatmalar, eşlemeler vb. kullanın. - her şey mümkün, lütfen. Sadece bundan gelen kod bir nedenden dolayı ideal hale gelmiyor.

    ps Kodun genel yapısı kasıtlı olarak orijinali taklit eder. FP'de iyi bir şekilde, karmaşık nesneler ve foreach bir Haritayı taklit etmeden tamamen farklı bir şekilde gerekli olsa da, sanatçı elinden gelenin en iyisini yaptı.

     
    Vasiliy Sokolov :

    Rica ederim. FP stilinde C#:

    Bunun bir alışkanlık ve sözdizimi bilgisi meselesi olduğunu anlıyorum, ancak orijinalin yazarı olmama rağmen kodu girmek benim için çok zor.

    Ticaret, otomatik ticaret sistemleri ve ticaret stratejilerinin test edilmesi hakkında forum

    OOP'a ilginç bir bakış

    fxsaber , 2021.01.29 13:39

    Ayak bezi.

     #property strict
    #property script_show_inputs
    
    input double inPerformance = 100 ; // Сколько процентов дает система за период
    input double inGift = 50 ;         // Награда управляющего в процентах (< 100)
    input int inN = 12 ;               // Сколько расчетных периодов
    
    struct BASE
    {
       double Investor;
       double Manager;
       double Summary;
    
       // Если управляющий снимает деньги в конце каждого расчетного периода.
       void Set1( const double Performance, const double Gift, const int N )
      {
         this .Investor = :: MathPow ( 1 + (Performance - 1 )* ( 1 - Gift), N);
         this .Manager = ( this .Investor - 1 ) * Gift / ( 1 - Gift);
         this .Summary = this .Investor + this .Manager;
    
         return ;
      }
    
       // Если ничего не делалось в расчетные периоды.
       void Set2( const double Performance, const double Gift, const int N )
      {
         this .Summary = :: MathPow (Performance, N);
         this .Manager = ( this .Summary - 1 ) * Gift;
         this .Investor = this .Summary - this .Manager;
    
         return ;
      }
    
       // Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.
       void Set3( const double Performance, const double Gift, const int N )
      {
         this .Summary = :: MathPow (Performance, N);
         this .Investor = :: MathPow ( 1 + (Performance - 1 )* ( 1 - Gift), N);
         this .Manager = this .Summary - this .Investor;
    
         return ;
      }
    
       void operator *=( const double Deposit = 1 )
      {
         this .Investor *= Deposit;
         this .Manager *= Deposit;
         this .Summary *= Deposit;
    
         return ;
      }
    #define TOSTRING(A) #A + " = " + :: DoubleToString (A, 4 ) + " "
       string ToString( const bool FlagName = true ) const
      {
         return (FlagName ? TOSTRING(Investor) + TOSTRING(Manager) + TOSTRING(Summary)
                        : :: StringFormat ( "||%-12.4f||%-12.4f||%-12.4f||" , this .Investor, this .Manager, this .Summary));
      }
    #undef TOSTRING
    
    };
    
    struct PAMM
    {
       double Performance; // Доходность за расчетный период
       double Gift;         // Вознагражение управляющего.
       int N;               // Сколько расчетных периодов.
    
      BASE Base1; // Если управляющий снимает деньги в конце каждого расчетного периода.
      BASE Base2; // Если ничего не делалось в расчетные периоды.
      BASE Base3; // Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.
    
       void Set( const double dPerformance, const double dGift, const int iN )
      {
         this .Performance = dPerformance;
         this .Gift = dGift;
         this .N = iN;
    
         this .Base1.Set1( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
         this .Base2.Set2( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
         this .Base3.Set3( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
      }
    
       void operator *=( const double Deposit = 1 )
      {
         this .Base1 *= Deposit;
         this .Base2 *= Deposit;
         this .Base3 *= Deposit;
    
         return ;
      }
    
       string GetDescription( void ) const
      {
         return ( "Доходность за расчетный период " + :: DoubleToString ( this .Performance, 1 ) + "%\n" +
               "Вознагражение управляющего " + :: DoubleToString ( this .Gift, 1 ) + "%\n" +
               "Количество расчетных периодов. " + ( string ) this .N);
      }
    
       string ToString( const bool FlagName = true , const bool FlagDescription = true ) const
      {
         return (FlagDescription ? "Данные для инвестиционного счета со следующими характеристиками:\n\n" + this .GetDescription() +
                                 "\n\nКонечный результат для Инвестора, Управляющего и Суммарно:"
                                 "\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода (1).\n" + this .Base1.ToString(FlagName) +
                                 "\n\nЕсли ничего не делалось в расчетные периоды (2).\n" + this .Base2.ToString(FlagName) +
                                 "\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3).\n" + this .Base3.ToString(FlagName)
                               : this .Base1.ToString(FlagName) + this .Base2.ToString(FlagName) + this .Base3.ToString(FlagName));
      }
    };
    
    void OnStart ()
    {
      PAMM Pamm;
    
      Pamm.Set(inPerformance, inGift, inN);
    
       Print (Pamm.ToString());
    
       string Str = Pamm.GetDescription() + "\n   " ;
    
       for ( int i = 1 ; i <= 3 ; i++ )
        Str += :: StringFormat ( "||%-12s||%-12s||%-12s||" , "Investor" + ( string )i, "Manager" + ( string )i, "Summary" + ( string )i);
    
       for ( int i = 1 ; i <= inN; i++ )
      {
        Pamm.Set(inPerformance, inGift, i);
    
        Str += StringFormat ( "\n%-2d:" , i) + Pamm.ToString( false , false );
      }
    
       Print (Str);
    }

    Resmi olarak, bu OOP'dir. Ama en ilkel kısmı. Ancak, onsuz yaşayamazdı. Belki aptalca, beyin başka bir şeye dönüştü ve ben bunu perçinledim.