[MSH] bug? function returns string-array with one element as strin

Discussion in 'Scripting' started by dreeschkind, Mar 28, 2006.

  1. dreeschkind

    dreeschkind Guest

    Hi everyone, I'm using MSH since the first beta and this is finally my first
    post here :)
    Can somebody please confirm, if the following behavior is a bug or a feature?
    I'm trying to write a function that returns a string-array [string[]].
    In case the array has only one element, the result turns out to be
    transformed into a single string [string].
    Thus, the array-method ".count" doesn't seem work.

    ****************************************************
    # function returning string-array with one element
    MSH C:\>function foo { return @("stringarray") }
    MSH C:\>$bar = foo
    MSH C:\>$bar
    stringarray
    MSH C:\>$bar.count
    <no result>
    ****************************************************
    # correct sting-array with one element
    MSH C:\>$bar = @("stringarray")
    MSH C:\>$bar
    stringarray
    MSH C:\>$bar.count
    1
    ****************************************************
    # function returning correct string-array with two elements
    MSH C:\>function foo { return @("string","array") }
    MSH C:\>$bar = foo
    MSH C:\>$bar
    string
    array
    MSH C:\>$bar.count
    2
    ****************************************************


    I guess there is some behind-the-scenes-monad-magic involved like in
    get-member, which likes to transform object-types, too.

    ****************************************************
    MSH C:\>@("foo","bar") | get-member


    TypeName: System.String

    [...]
    ****************************************************

    I'm using MSH-Version:

    Major Minor Build Revision
    ----- ----- ----- --------
    1 0 7487 0
     
    dreeschkind, Mar 28, 2006
    #1
    1. Advertisements

  2. Given this:

    MSH:76 # (@("hi")).count
    1

    I'd say that Monad is doing something special on return strings from a
    function. Not sure what exactly.
    The get-member cmdlet only spits out the member info once for a type even if
    it gets pinged multiple times by objects of that same type. Try this:

    MSH:78 # @("foo","bar") | foreach {$_.GetType().Name}
    String
    String

    In fact that comes in handy enough that I have a filter called "typename"
    that is defined like so:

    filter typename { $_.GetType().Name }
     
    Keith Hill [MVP], Mar 28, 2006
    #2
    1. Advertisements

  3. dreeschkind

    dreeschkind Guest

    I understand that the get-member cmdlet spits out the member info only once
    for a type if it gets pinged multiple times by objects of that same type,
    since nobody would like to see the member info for the String-type for each
    string element in the pipe.
    But I still think that the way MSH handles arrays is quite confusing.
    When I type:

    MSH C:\>@("foo","bar") | get-member

    I wouldn't expect to get member info for the String-type. What I would
    expect is to get member info for Object[]-type, since I piped only one single
    object which is in fact an array. MSH even tells me that when I type:

    MSH C:\>(@("foo","bar")).GetType().Name
    Object[]

    In an consistent environment I would expect to get member info on the
    String-type ONLY in case I type for example:

    MSH C:\>(@("foo","bar"))[0] | get-member

    Since only the elements of the array are of the String-type:

    MSH MSH:\>((@("foo","bar"))[0]).GetType().Name
    String

    I don't think it's a good idea to 'hide' the existence of arrays from the
    user, since they are common data types in other programming languages. Maybe
    it's just me who got confused about that aspect.
     
    dreeschkind, Mar 28, 2006
    #3
  4. I wouldn't expect to get member info for the String-type. What I would
    as the first element gets enumerated, this will work also :

    ,@("foo","bar") | get-member

    or

    get-member -i @("foo","bar")
    I have the same opinion as you do,
    see also my former question in the thread :

    (MSH) enumerating DataRows.

    and also I have problems with $args[0] and get-member, also with big WMI
    query's (also a former thread, also workaround there but you not always
    know in front ), columns of a datarow (contain reference to the complete
    datatable, that get enumerated (typedata can be used as workaround)

    so I do agree with you and I hope there could be a way to force MSH to
    do only 1 level enumerating or something.

    but I hope the workarounds help

    gr /\/\o\/\/



     
    /\\/\\o\\/\\/, Mar 28, 2006
    #4
  5. dreeschkind

    dreeschkind Guest

    While playing with com objects after reading the following article in the
    Monad Technology Blog:
    http://blogs.gotdotnet.com/monad/archive/2006/03/26/561337.aspx
    I encountered another problem with MSH array handling:

    First, I fetched an array of recent documents from Microsoft Word:
    ****************************************************
    MSH
    C:\>$w=[System.Runtime.InteropServices.Marshal]::GetActiveObject("Word.Application")

    MSH C:\>$w.RecentFiles


    Application : System.__ComObject
    Creator : 1297307460
    Parent : System.__ComObject
    Name : foo.doc
    Index : 1
    ReadOnly : False
    Path : E:\Eigene Dateien

    Application : System.__ComObject
    Creator : 1297307460
    Parent : System.__ComObject
    Name : bar.rtf
    Index : 2
    ReadOnly : False
    Path : C:\Dokumente und Einstellungen\dreeschkind\Desktop
    ****************************************************

    Then I checked how many elements the array has:
    ****************************************************
    MSH C:\>($w.RecentFiles).count
    2
    ****************************************************

    Then I tried to index the first elemet and got an error:
    ****************************************************
    MSH C:\>($w.RecentFiles)[0]
    Unable to index into an object of type System.__ComObject.
    At line:1 char:18
    + ($w.RecentFiles)[0 <<<< ]
    ****************************************************

    As a workaround I tried to use the select-object cmdlet which worked,
    but this cmdlet has only options -first and -last, there is no option to
    select a specific object by index.
    ****************************************************
    MSH C:\>$w.RecentFiles | select -first 1


    Application : System.__ComObject
    Creator : 1297307460
    Parent : System.__ComObject
    Name : foo.doc
    Index : 1
    ReadOnly : False
    Path : E:\Eigene Dateien
    ****************************************************

    Any clue why MSH tries to "index into an object of type System.__ComObject"?
    I think Monad-Magic destroyed my array again... :(
     
    dreeschkind, Mar 28, 2006
    #5
  6. That works but it's a tad cryptic, eh?
    Yeah I've commented before that perhaps there should be some special piping
    syntax to control Monads collection/array shredding behavior. I'm just not
    sure what that would look like besides ",@("foo","bar")".
     
    Keith Hill [MVP], Mar 28, 2006
    #6
  7. I ran into this same issue a while back. While you are trying to form a
    mental model for how Monad works this sort of behavior really bothers you
    (as it did for me). However as I've used Monad daily since October I'm
    finding that I rarely care about array or collection members. What I'm
    mostly interested in is what is in the array. Still I do undertand your
    point. What I am getting at is *perhaps* this is a case of what's logical
    in the abstract verus useful on a daily basis.
     
    Keith Hill [MVP], Mar 28, 2006
    #7
  8. dreeschkind

    lee.holmes Guest

    There are a couple of points that contribute to the difficulty of this area. Since pipelines are such a very core scenario, support for them introduces nuances into other parts of the shell and language.

    The first point to clarify is that the @() syntax should be considered to be the tool that lets you "make sure this expression is an array." It works more like a casting operator than it does a creation operator. Because of that, it is something that _consumers_ of data use, not _producers_.

    For example,

    MSH C:\temp> function foo { return "stringarray" }
    MSH C:\temp> $bar = foo
    MSH C:\temp> @($bar).Count
    1

    MSH C:\temp> function foo { return "hello","world" }
    MSH C:\temp> $bar = foo
    MSH C:\temp> $bar.Count
    2

    As for your get-member question, this is because of the way that pipelines act in Monad. When you pass a collection of objects down the pipeline, we pass each item in that collection down the pipeline. When you pass a single object down the pipeline, we visit only that object. The shell would feel extremely broken if the following didn't work:

    MSH C:\temp> "Hello","World" | foreach { $_ }
    Hello
    World
    MSH C:\temp> "Hello" | foreach { $_ }
    Hello

    Now, get-member adds an additional level to this by only showing the members for unique types passed down the pipeline. That's why passing an array of strings to get-member only shows the members once. Otherwise, the following scenario would produce lots of duplicate and useless output:

    MSH C:\winnt\system32> dir | gm


    Hope this helps.

    --
    Lee Holmes [MSFT]
    Microsoft Command Shell Development
    Microsoft Corporation
    This posting is provided "AS IS" with no warranties, and confers no rights.





    -----Original Message-----
    From: Keith Hill [MVP]
    Posted At: Monday, March 27, 2006 9:51 PM
    Posted To: microsoft.public.windows.server.scripting
    Conversation: [MSH] bug? function returns string-array with one element as strin
    Subject: Re: [MSH] bug? function returns string-array with one element as strin


    Given this:

    MSH:76 # (@("hi")).count
    1

    I'd say that Monad is doing something special on return strings from a
    function. Not sure what exactly.
    The get-member cmdlet only spits out the member info once for a type even if
    it gets pinged multiple times by objects of that same type. Try this:

    MSH:78 # @("foo","bar") | foreach {$_.GetType().Name}
    String
    String

    In fact that comes in handy enough that I have a filter called "typename"
    that is defined like so:

    filter typename { $_.GetType().Name }
     
    lee.holmes, Mar 28, 2006
    #8
  9. dreeschkind

    dreeschkind Guest

    MSH C:\>function foo { return @("stringarray") }
    MSH C:\>$bar = foo
    MSH C:\>$bar.count
    <no result>

    Ok, your trick works somehow, but I'm still confused about this _consumers_
    / _producers_ thing. This is completely behind-the-scenes and not very
    intuitive in my opinion even to a programmer with 13 years of experience in
    quite a lot of different programming languages. I don't understand why I need
    to "make sure this expression is an array" again after I return my _array_
    from a function. A type _is_ a type and an array _is_ and should _stay_ an
    array, nothing except the programmer should control/change that. Maybe
    someone can clarify this aspect in the next version of the documentation,
    since I guess it's to late now to change the way how the pipe is working.
    I can't follow you on that one. Both examples shoud work without
    'destroying' arrays in the middle of the pipe. In both exampes only _one_
    object is passed: in the first one an array of string and in the second one a
    string. The shell would not feel broken, if the foreach-cmdlet decided to
    split only incoming arrays on the one hand and on the other hand not to split
    any other type. So your examples should work. And this way my example

    @("foo","bar") | get-member

    could give member info of the array-type instead of the string-type, because
    the get-member cmdlet doesn't need to split incoming arrays.
    If I wanted member info of an element in an array I could always index it
    before passing it to get-member:

    (dir)[0] | gm

    I understand that MSH likes to make things easy for admins. But I think most
    of them are clever enough to understand what an array is and that "dir" in
    most cases will produce an array, so no need to 'hide' it. The 'magic' that
    happens in the pipe is sometimes very confusing and hard to predict, which
    doesn't make it easy for those who are used to 'predictable' results in
    programming.
     
    dreeschkind, Mar 28, 2006
    #9
  10. ($w.RecentFiles).item(1)

    gr /\/\o\/\/
     
    /\/\o\/\/ [MVP], Mar 29, 2006
    #10
  11. dreeschkind

    applepwc Guest

    MSH 11> @($w.recentfiles)[0]


    Application : System.__ComObject
    Creator : 1297307460
    Parent : System.__ComObject
    Name : TracingQuickStart.doc
    Index : 1
    ReadOnly : True
    Path : E:\Monad\Document\monad_b3_docs

    MSH 13> @($w.recentfiles) | select -first 2
    #will also work

    ####################
    I think just $w.recentfiles isn't array object(see gm -in $w.recentfiles).So
    I use @() force it to array.
     
    applepwc, Mar 29, 2006
    #11
  12. Sorry for not being more clear.



    The primary thing to keep in mind is that Monad unrolls collections as you
    pass them between stages in the pipeline. When you pass an array down the
    pipeline, we pass each individual element down the pipeline.



    That's the "producer" side of things. It's the operation that produces
    data. For example:



    function CreateData { return 1,2,3,4,5 }

    or

    function CreateData { return 1 }



    The consumer side is the point at which you use the data. If you want to
    treat it explicitly as an array, you would:



    @(CreateData).Get(1)



    Now, if you want to explicitly pass an array down the pipeline, our comma
    operator is what you want. The comma operator creates lists, so you would
    create a list (to be enumerated) that contains a single element (that is an
    array.) Like this:



    ,@(CreateData) | gm



    Or just



    function CreateData { return ,@(1) }

    CreateData | gm





    As for the get-member cmdlet, please feel free to file your feedback at the
    MS Connect site. We feel that the current experience is best for users, but
    have been wrong before :) Only votes can let us know that.



    You do have this option, though:



    [C:\]

    MSH:173 > filter Get-ForeachMember { $input | get-member; "-"*80 }



    [C:\]

    MSH:175 > "1","2" | Get-ForeachMember



    Hope this helps



    --

    Lee Holmes [MSFT]

    Microsoft Command Shell Development

    Microsoft Corporation

    This posting is provided "AS IS" with no warranties, and confers no rights.


     
    Lee Holmes [MSFT], Mar 31, 2006
    #12
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.